testsAndMisc/linux_configuration/periodic_background/lib/atomic_install.sh
Krzysztof kuhy Rudnicki b57d5f9064 Install live scripts by atomic rename
The periodic setup wrote /usr/local/bin scripts with sed > target,
truncating the file in place. bash reads scripts lazily, so the running
hosts-file-monitor could execute a half-new file.

New lib/atomic_install.sh renders into a temp file beside the target,
syntax-checks it, sets mode/owner and renames it over the target (new
inode). A bad template fails closed and leaves the live file untouched.
Applied to the maintenance script, hosts monitor, browser wrapper and
auto-update script; the hosts monitor service is restarted if running.

Test: test_services_atomic_install.sh (inode changes, old inode keeps
old bytes, syntax error and missing template leave target untouched).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYWDKuhbBAoNETfoB9
2026-11-12 12:59:59 +01:00

51 lines
1.8 KiB
Bash
Executable File

#!/bin/bash
# Atomic install of live /usr/local/bin scripts for the periodic system setup.
#
# Sourced by setup_periodic_system.sh and periodic_browser.sh.
#
# Why not `sed ... > /usr/local/bin/x.sh`: that truncates the file in place.
# bash reads a script lazily, so a service already running it (the hosts
# monitor loops forever) can execute a half-new file. Rendering beside the
# target and renaming over it gives the target a NEW inode; a running instance
# keeps reading the old one until it exits or is restarted.
# Fail-closed: a template that is missing or fails `bash -n` leaves the live
# file untouched and returns non-zero.
# install_rendered_script <template> <dest> [sed args...]
# Renders <template> through sed with the given args into a temp file in
# dirname(<dest>) (same filesystem, so mv is a rename), syntax-checks it, sets
# mode 0755 and root ownership when run as root, then renames it over <dest>.
install_rendered_script() {
local template="$1" dest="$2"
shift 2
local tmp
[[ -f $template ]] || {
echo "Error: template not found: $template" >&2
return 1
}
tmp="$(mktemp "${dest}.XXXXXX")" || return 1
# `-e ''` keeps sed valid when the caller passes no expressions.
if sed -e '' "$@" "$template" >"$tmp" &&
bash -n "$tmp" &&
chmod 0755 "$tmp" &&
{ [[ $EUID -ne 0 ]] || chown root:root "$tmp"; } &&
mv -f "$tmp" "$dest"; then
return 0
fi
rm -f "$tmp"
echo "Error: not installing $dest (render or syntax check failed)" >&2
return 1
}
# restart_if_active <unit>
# Restarts a unit onto a freshly installed script, but only if it is running:
# the installer must not start a service the user stopped on purpose.
restart_if_active() {
local unit="$1"
if systemctl is-active "$unit" >/dev/null 2>&1; then
systemctl restart "$unit"
echo "✓ Restarted $unit onto the new script"
fi
return 0
}