Skip to main content

max / makenotwork

3.1 KB · 77 lines History Blame Raw
1 #!/usr/bin/env bash
2 # Install a staged companion binary and restart its unit. Runs as ROOT, invoked
3 # by Sando's deploy step over the node's executor as:
4 #
5 # sudo /usr/local/lib/mnw/install-companion.sh <src> <dst> <service>
6 #
7 # where <src> is the companion binary inside the just-rsynced release bundle
8 # (e.g. /opt/mnw/releases/<ver>/companions/mnw-cli), <dst> is the unit's
9 # ExecStart path (e.g. /opt/mnw-cli/mnw-cli), and <service> is the systemd unit
10 # to restart (e.g. mnw-cli.service). This wrapper exists so the deploy user's
11 # sudo grant is ONE auditable script rather than a broad install/systemctl grant.
12 #
13 # Install (one-time per node, as root):
14 # sudo install -d /usr/local/lib/mnw
15 # sudo install -m 0755 install-companion.sh /usr/local/lib/mnw/install-companion.sh
16 # # then add the scoped sudoers line — see mnw-companion.sudoers
17 #
18 # The install is atomic (install(1) writes a temp then renames), so a running
19 # companion never execs a half-written file. The service is restarted only after
20 # a successful install; a failed install leaves the unit on its current binary.
21 set -euo pipefail
22
23 if [[ $# -ne 3 ]]; then
24 echo "usage: install-companion.sh <src-binary> <dst-path> <service>" >&2
25 exit 2
26 fi
27
28 SRC="$1"
29 DST="$2"
30 SERVICE="$3"
31
32 # Guard rails: refuse a src outside a release bundle or a dst outside /opt, and a
33 # service name that isn't a bare unit. These bound what a caller (already behind
34 # the single sudoers grant) can install/restart — defense in depth, not the
35 # primary control. Being the ONLY control on a NOPASSWD grant, though, they have
36 # to actually hold.
37 #
38 # Normalise BEFORE testing the prefix. These were once glob-only tests against
39 # the raw arguments, which `..` walks straight out of: `/opt/../etc/systemd/
40 # system/x.service` matches `/opt/*`, and `.../releases/v/companions/../../../
41 # ../etc/shadow` matches the src pattern — i.e. `install -m 0755` as root to
42 # anywhere on the filesystem. `realpath -m` resolves `..` and symlinks without
43 # requiring the path to exist (the dst usually does not on a first install).
44 #
45 # /opt itself is resolved too, so the comparison still holds if it is a symlink.
46 SRC="$(realpath -m -- "$SRC")"
47 DST="$(realpath -m -- "$DST")"
48 OPT_ROOT="$(realpath -m -- /opt)"
49
50 case "$SRC" in
51 */releases/*/companions/*) : ;;
52 *) echo "install-companion: refusing src outside a release bundle: $SRC" >&2; exit 3 ;;
53 esac
54 case "$DST" in
55 "$OPT_ROOT"/*) : ;;
56 *) echo "install-companion: refusing dst outside $OPT_ROOT: $DST" >&2; exit 3 ;;
57 esac
58 case "$SERVICE" in
59 *[/[:space:]]*|"") echo "install-companion: bad service name: $SERVICE" >&2; exit 3 ;;
60 *.service) : ;;
61 *) echo "install-companion: service must end in .service: $SERVICE" >&2; exit 3 ;;
62 esac
63
64 if [[ ! -f "$SRC" ]]; then
65 echo "install-companion: no binary at $SRC" >&2
66 exit 4
67 fi
68
69 # Installs the normalised paths, not the raw arguments, so there is no gap
70 # between what was checked and what is written.
71 echo "install-companion: installing $SRC -> $DST"
72 install -m 0755 "$SRC" "$DST"
73
74 echo "install-companion: restarting $SERVICE"
75 systemctl restart "$SERVICE"
76 echo "install-companion: done ($SERVICE live on $(basename "$DST"))"
77