191 lines
7.2 KiB
Bash
Executable File
191 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# panama-server against a throwaway HOME, a fixture catalog, and a stubbed
|
|
# systemctl -- the behaviors that protect a machine's live services:
|
|
#
|
|
# * enable seeds .env from .env.example and REFUSES to start while a
|
|
# CHANGE_ME placeholder survives -- a service brought up with the
|
|
# placeholder as its database password runs, wrongly, forever
|
|
# * a completed enable links the tracked files (compose.yml, helper scripts,
|
|
# the unit and any helper units), reloads systemd, enables the unit and
|
|
# each helper timer, and never links .env.example
|
|
# * disable removes the units and nothing else: the .env and data stay
|
|
# * relink refreshes links for enabled services only, and names a service
|
|
# whose definition changed since the machine last looked -- without
|
|
# restarting anything
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
panama_server="$repo_dir/bin/panama-server"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
[[ -x "$panama_server" ]] || { printf 'panama-server contract: %s is not executable\n' "$panama_server" >&2; exit 1; }
|
|
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
home="$work/home"
|
|
catalog="$work/catalog"
|
|
stub_dir="$work/bin"
|
|
calls="$work/calls"
|
|
mkdir -p "$home" "$stub_dir" "$catalog/Example"
|
|
: >"$calls"
|
|
|
|
cat >"$catalog/Example/compose.yml" <<'YML'
|
|
services:
|
|
example:
|
|
image: docker.io/library/nginx:latest
|
|
environment:
|
|
DB_PASSWORD: ${DB_PASSWORD}
|
|
YML
|
|
cat >"$catalog/Example/.env.example" <<'ENV'
|
|
DB_PASSWORD=CHANGE_ME
|
|
ENV
|
|
cat >"$catalog/Example/podman-example.service" <<'UNIT'
|
|
[Unit]
|
|
Description=Podman Compose: Example
|
|
[Service]
|
|
Type=oneshot
|
|
WorkingDirectory=%h/Server/Example
|
|
ExecStart=/usr/bin/podman compose up -d
|
|
ExecStop=/usr/bin/podman compose down
|
|
RemainAfterExit=yes
|
|
[Install]
|
|
WantedBy=default.target
|
|
UNIT
|
|
cat >"$catalog/Example/refresh-example" <<'SH'
|
|
#!/usr/bin/env bash
|
|
SH
|
|
chmod +x "$catalog/Example/refresh-example"
|
|
cat >"$catalog/Example/example-refresh.service" <<'UNIT'
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=%h/Server/Example/refresh-example
|
|
UNIT
|
|
cat >"$catalog/Example/example-refresh.timer" <<'UNIT'
|
|
[Timer]
|
|
OnCalendar=daily
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
|
|
# The stub records every invocation; is-active answers 'inactive' so nothing
|
|
# here depends on a real systemd.
|
|
cat >"$stub_dir/systemctl" <<STUB
|
|
#!/usr/bin/env bash
|
|
printf 'systemctl %s\n' "\$*" >>"$calls"
|
|
case "\$*" in
|
|
*is-active*) echo inactive; exit 3 ;;
|
|
*is-enabled*) exit 1 ;;
|
|
esac
|
|
exit 0
|
|
STUB
|
|
cat >"$stub_dir/podman" <<STUB
|
|
#!/usr/bin/env bash
|
|
printf 'podman %s\n' "\$*" >>"$calls"
|
|
exit 0
|
|
STUB
|
|
chmod +x "$stub_dir/systemctl" "$stub_dir/podman"
|
|
|
|
run() {
|
|
HOME="$home" XDG_CONFIG_HOME="$home/.config" XDG_STATE_HOME="$home/.local/state" \
|
|
PANAMA_SERVER_DIR="$catalog" PANAMA_SERVER_TARGET="$home/Server" \
|
|
PATH="$stub_dir:$PATH" "$panama_server" "$@"
|
|
}
|
|
|
|
unit_link="$home/.config/systemd/user/podman-example.service"
|
|
helper_link="$home/.config/systemd/user/example-refresh.service"
|
|
timer_link="$home/.config/systemd/user/example-refresh.timer"
|
|
|
|
# ── enable stops on placeholders ─────────────────────────────────────────────
|
|
|
|
run enable Example >/dev/null 2>&1 \
|
|
&& note 'enable exited zero with CHANGE_ME still in the .env'
|
|
|
|
[[ -f "$home/Server/Example/.env" ]] \
|
|
|| note 'enable did not seed .env from .env.example'
|
|
[[ -L "$home/Server/Example/compose.yml" ]] \
|
|
|| note 'enable did not link compose.yml into ~/Server'
|
|
[[ -e "$unit_link" ]] \
|
|
&& note 'enable installed the unit despite the placeholder refusal'
|
|
grep -q 'systemctl .*enable' "$calls" \
|
|
&& note 'enable reached systemctl despite the placeholder refusal'
|
|
|
|
# ── a filled .env enables ────────────────────────────────────────────────────
|
|
|
|
printf 'DB_PASSWORD=s3cret\n' >"$home/Server/Example/.env"
|
|
: >"$calls"
|
|
run enable Example >/dev/null 2>&1 \
|
|
|| note 'enable failed with a completed .env'
|
|
|
|
[[ -L "$unit_link" ]] \
|
|
|| note 'enable did not link the unit into the systemd user directory'
|
|
grep -q 'systemctl --user daemon-reload' "$calls" \
|
|
|| note 'enable did not daemon-reload'
|
|
grep -q 'systemctl --user enable --now podman-example.service' "$calls" \
|
|
|| note 'enable did not enable --now the unit'
|
|
[[ "$(cat "$home/Server/Example/.env")" == "DB_PASSWORD=s3cret" ]] \
|
|
|| note 'enable rewrote an .env that already existed'
|
|
[[ -L "$home/Server/Example/refresh-example" ]] \
|
|
|| note 'enable did not link the helper script into ~/Server'
|
|
[[ -e "$home/Server/Example/.env.example" ]] \
|
|
&& note 'enable linked .env.example into ~/Server'
|
|
[[ -L "$helper_link" && -L "$timer_link" ]] \
|
|
|| note 'enable did not link the helper units'
|
|
grep -q 'systemctl --user enable --now example-refresh.timer' "$calls" \
|
|
|| note 'enable did not enable the helper timer'
|
|
|
|
# ── relink names a changed definition, restarts nothing ──────────────────────
|
|
|
|
printf ' # a changed line\n' >>"$catalog/Example/compose.yml"
|
|
: >"$calls"
|
|
relink_out="$(run relink 2>&1)" || note 'relink failed'
|
|
grep -q 'Example' <<<"$relink_out" \
|
|
|| note 'relink did not name the service whose definition changed'
|
|
grep -q 'restart' "$calls" \
|
|
&& note 'relink restarted something; the restart is deliberately manual'
|
|
|
|
# A second relink with nothing new must not cry wolf.
|
|
relink_out="$(run relink 2>&1)" || note 'a repeat relink failed'
|
|
grep -q 'restart podman-example' <<<"$relink_out" \
|
|
&& note 'relink reports the same change twice'
|
|
|
|
# ── disable removes the unit and only the unit ───────────────────────────────
|
|
|
|
mkdir -p "$home/Server/Example/data"
|
|
printf 'precious\n' >"$home/Server/Example/data/keep"
|
|
: >"$calls"
|
|
run disable Example >/dev/null 2>&1 || note 'disable failed'
|
|
|
|
[[ -e "$unit_link" ]] \
|
|
&& note 'disable left the unit installed'
|
|
[[ -e "$helper_link" || -e "$timer_link" ]] \
|
|
&& note 'disable left a helper unit installed'
|
|
grep -q 'systemctl --user disable --now example-refresh.timer' "$calls" \
|
|
|| note 'disable did not stop the helper timer'
|
|
grep -q 'systemctl --user disable --now podman-example.service' "$calls" \
|
|
|| note 'disable did not stop the unit'
|
|
[[ -f "$home/Server/Example/.env" ]] \
|
|
|| note 'disable removed the .env'
|
|
[[ -f "$home/Server/Example/data/keep" ]] \
|
|
|| note 'disable removed data'
|
|
|
|
# ── an unknown service is a real answer ──────────────────────────────────────
|
|
|
|
run enable NoSuchThing >/dev/null 2>&1 \
|
|
&& note 'enabling an unknown service exited zero'
|
|
|
|
# ── Report ───────────────────────────────────────────────────────────────────
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
|
printf 'panama-server contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'panama-server contract: PASS\n'
|