deploy: 9-account/2-host packaging + static release build (Redmine 494)

Everything needed for one ~10-minute Charles window on ultix-streaming
and ultix-offstage, with zero root/systemd on the target accounts:

- deploy/accounts.tsv: the fleet authority (account, host, vertical,
  Redmine project, quota group, ports). Port scheme events=4100+index,
  serve=8090+index with a global 0-8 index so per-account daemons on
  one host never collide; loop state is per-account under ~/.mopac.
- deploy/install-account.sh <account>: idempotent installer run AS the
  target user; renders harness.toml from deploy/harness.toml.in, writes
  a 0600 env-secrets template, refuses to overwrite existing config or
  secrets (re-run = the upgrade path), generates mopac-start/stop.
- deploy/runbook.md: exact Charles sequence (build, stage, install,
  secrets bootstrap, verify, start via nohup or cron @reboot, rollback)
  with the account-port table, per-host time estimates and assumptions.
- Makefile: release target (digest-pinned docker builder, CGO off,
  linux/amd64 static, stripped) plus check/deploy-test entrypoints.
- deploy/tests.sh: 13 packaging tests (TSV scheme, template substitution
  for all 9 accounts, idempotence, refuse-to-overwrite, binary lookup,
  rendered config loads via a dry-run); README deploy section.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
2026-08-29 05:50:41 -05:00
parent fc518c475e
commit b7799ea0de
7 changed files with 889 additions and 1 deletions
+180
View File
@@ -0,0 +1,180 @@
#!/bin/sh
# deploy/tests.sh — packaging tests for the 9-account deploy (Redmine
# 494). Pure POSIX shell, no docker, no network, no root:
#
# T1 accounts.tsv: 9 rows, spec-exact account/host lists, 8 columns,
# unique accounts, port scheme events=4100+idx / serve=8090+idx,
# (host,port) uniqueness across the fleet
# T2 render: installer runs for ALL 9 accounts against a stub binary
# in fake HOMEs; harness.toml has no unsubstituted @PLACEHOLDER@s
# and carries the row's vertical/ports/project; env is 0600;
# start/stop helpers exist, executable, correct ports
# T3 idempotence: re-run exits 0, harness.toml + env byte-identical
# T4 refuse-to-overwrite: hand-edited harness.toml marker survives
# T5 unknown account -> non-zero exit
# T6 binary lookup order (../bin/harness-linux-amd64 staging layout)
# T7 generated TOML actually loads: `once --dry-run --demo` via the
# dev binary when bin/harness exists (skipped otherwise)
#
# Usage: ./deploy/tests.sh (or: make deploy-test)
set -u
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
TSV="$ROOT/deploy/accounts.tsv"
INSTALL="$ROOT/deploy/install-account.sh"
TEMPLATE="$ROOT/deploy/harness.toml.in"
PASS=0
FAIL=0
ok() { PASS=$((PASS + 1)); echo "ok $1"; }
bad() { FAIL=$((FAIL + 1)); echo "FAIL $1"; }
check() { # desc condition-already-evaluated-by-caller
if [ "$2" = 0 ]; then ok "$1"; else bad "$1"; fi
}
# ---------------------------------------------------------------- T1
t1() {
# 9 data rows
n=$(awk -F'\t' '$1 !~ /^#/ && NF >= 8 {c++} END {print c + 0}' "$TSV")
check "T1 accounts.tsv has 9 account rows" "$([ "$n" -eq 9 ]; echo $?)"
# exact account/host sets from the spec
spec_streaming="reachableceo TSGBOD TSGCOO TSGCTO TSGCCO"
spec_offstage="reachableceo-offstage COSRCEO-Personal COSRCEO-Biz COSWFO"
got_streaming=$(awk -F'\t' '$1 !~ /^#/ && $2 == "ultix-streaming" {print $1}' "$TSV" | sort | tr '\n' ' ')
got_offstage=$(awk -F'\t' '$1 !~ /^#/ && $2 == "ultix-offstage" {print $1}' "$TSV" | sort | tr '\n' ' ')
want_streaming=$(printf '%s\n' $spec_streaming | sort | tr '\n' ' ')
want_offstage=$(printf '%s\n' $spec_offstage | sort | tr '\n' ' ')
check "T1 ultix-streaming accounts match the spec" "$([ "$got_streaming" = "$want_streaming" ]; echo $?)"
check "T1 ultix-offstage accounts match the spec" "$([ "$got_offstage" = "$want_offstage" ]; echo $?)"
# unique account names, unique host:index, unique (host,events/serve) ports
dup=$(awk -F'\t' '$1 !~ /^#/ {print $1}' "$TSV" | sort | uniq -d | wc -l | tr -d ' ')
check "T1 account names unique" "$([ "$dup" -eq 0 ]; echo $?)"
dup=$(awk -F'\t' '$1 !~ /^#/ {print $2" "$7}' "$TSV" | sort | uniq -d | wc -l | tr -d ' ')
check "T1 (host, events_port) unique" "$([ "$dup" -eq 0 ]; echo $?)"
dup=$(awk -F'\t' '$1 !~ /^#/ {print $2" "$8}' "$TSV" | sort | uniq -d | wc -l | tr -d ' ')
check "T1 (host, serve_port) unique" "$([ "$dup" -eq 0 ]; echo $?)"
# port derivation + row sanity (also enforced by the installer itself)
badrows=$(awk -F'\t' '$1 !~ /^#/ {
if (NF != 8 || $3 !~ /^[0-9]+$/ || $7 != 4100 + $3 || $8 != 8090 + $3) bad++
} END {print bad + 0}' "$TSV")
check "T1 every row: 8 cols, events=4100+index, serve=8090+index" "$([ "$badrows" -eq 0 ]; echo $?)"
}
# ---------------------------------------------------------------- T2-T6
t2_t6() {
TMP=$(mktemp -d /tmp/mopac-deploy-test.XXXXXX) || { bad "T2 mktemp"; return; }
trap 'rm -rf "$TMP"' EXIT INT TERM
# stub binary (real one not needed to test the packaging)
printf '#!/bin/sh\necho stub-harness "$@"\n' >"$TMP/harness-linux-amd64"
chmod 755 "$TMP/harness-linux-amd64"
rc_all=0
for acct in $(awk -F'\t' '$1 !~ /^#/ {print $1}' "$TSV"); do
row=$(awk -F'\t' -v a="$acct" '$1 == a {print}' "$TSV")
idx=$(printf '%s\n' "$row" | awk -F'\t' '{print $3}')
vert=$(printf '%s\n' "$row" | awk -F'\t' '{print $4}')
proj=$(printf '%s\n' "$row" | awk -F'\t' '{print $5}')
ev=$((4100 + idx)); sv=$((8090 + idx))
FAKEHOME="$TMP/home-$acct"
mkdir -p "$FAKEHOME"
HOME="$FAKEHOME" MOPAC_HARNESS_BIN="$TMP/harness-linux-amd64" \
sh "$INSTALL" "$acct" >"$TMP/out-$acct" 2>&1
rc=$?
[ "$rc" -eq 0 ] || { bad "T2 install $acct exits 0 (got $rc)"; sed 's/^/ /' "$TMP/out-$acct"; rc_all=1; continue; }
cfg="$FAKEHOME/.mopac/harness.toml"
for cond in \
"[ -f \"\$cfg\" ]" \
"[ -x \"\$FAKEHOME/.mopac/bin/harness\" ]" \
"[ -x \"\$FAKEHOME/.mopac/bin/mopac-start\" ]" \
"[ -x \"\$FAKEHOME/.mopac/bin/mopac-stop\" ]" \
"[ -d \"\$FAKEHOME/.mopac/state/loop\" ]" \
"[ -d \"\$FAKEHOME/.mopac/state/events\" ]" \
"[ -d \"\$FAKEHOME/.mopac/reports\" ]" \
"[ -d \"\$FAKEHOME/.mopac/work\" ]" \
"! grep -q '@[A-Z_][A-Z0-9_]*@' \"\$cfg\"" \
"grep -q \"vertical = \\\"\$vert\\\"\" \"\$cfg\"" \
"grep -q 'listen = \":'\$ev'\"' \"\$cfg\"" \
"grep -q 'listen = \":'\$sv'\"' \"\$cfg\"" \
"grep -q \"project=\$proj&\" \"\$cfg\"" \
"grep -q \"\$FAKEHOME/.mopac/state/loop\" \"\$cfg\"" \
"grep -q \":\$ev\" \"\$FAKEHOME/.mopac/bin/mopac-start\"" \
"[ \"\$(stat -c %a \"\$FAKEHOME/.mopac/env\")\" = 600 ]"
do
if ! eval "$cond"; then
bad "T2 $acct: $cond"
rc_all=1
fi
done
done
[ "$rc_all" -eq 0 ] && ok "T2 render all 9 accounts: files, modes, substitutions, ports"
# --- T3 idempotence (re-run one account: exit 0, config + env unchanged)
acct=TSGBOD
FAKEHOME="$TMP/home-$acct"
sum_cfg=$(cksum "$FAKEHOME/.mopac/harness.toml" | awk '{print $1, $2}')
sum_env=$(cksum "$FAKEHOME/.mopac/env" | awk '{print $1, $2}')
HOME="$FAKEHOME" MOPAC_HARNESS_BIN="$TMP/harness-linux-amd64" \
sh "$INSTALL" "$acct" >"$TMP/out-rerun" 2>&1
rc=$?
[ "$rc" -eq 0 ] && \
[ "$sum_cfg" = "$(cksum "$FAKEHOME/.mopac/harness.toml" | awk '{print $1, $2}')" ] && \
[ "$sum_env" = "$(cksum "$FAKEHOME/.mopac/env" | awk '{print $1, $2}')" ] \
&& ok "T3 idempotent re-run: exit 0, harness.toml + env byte-identical" \
|| bad "T3 idempotent re-run (rc=$rc)"
# --- T4 hand-edited config survives (refuse to overwrite)
echo "# hand-edited marker" >>"$FAKEHOME/.mopac/harness.toml"
HOME="$FAKEHOME" MOPAC_HARNESS_BIN="$TMP/harness-linux-amd64" \
sh "$INSTALL" "$acct" >"$TMP/out-keep" 2>&1
rc=$?
grep -q "# hand-edited marker" "$FAKEHOME/.mopac/harness.toml" && [ "$rc" -eq 0 ] \
&& grep -q "kept existing" "$TMP/out-keep" \
&& ok "T4 existing harness.toml kept on re-run (exit 0, marker intact)" \
|| bad "T4 refuse-to-overwrite (rc=$rc)"
# --- T5 unknown account
HOME="$TMP/home-nowhere" MOPAC_HARNESS_BIN="$TMP/harness-linux-amd64" \
sh "$INSTALL" no-such-account >"$TMP/out-unknown" 2>&1
[ "$?" -ne 0 ] && grep -q "not in" "$TMP/out-unknown" \
&& ok "T5 unknown account -> non-zero + message" \
|| bad "T5 unknown account"
# --- T6 default binary lookup (staged bundle layout: ../bin/harness-linux-amd64)
STAGE="$TMP/stage"; mkdir -p "$STAGE/deploy" "$STAGE/bin"
cp "$TSV" "$TEMPLATE" "$INSTALL" "$STAGE/deploy/"
cp "$TMP/harness-linux-amd64" "$STAGE/bin/harness-linux-amd64"
FAKEHOME="$TMP/home-stage"; mkdir -p "$FAKEHOME"
HOME="$FAKEHOME" sh "$STAGE/deploy/install-account.sh" TSGCCO >"$TMP/out-stage" 2>&1
rc=$?
[ "$rc" -eq 0 ] && [ -x "$FAKEHOME/.mopac/bin/harness" ] && \
grep -q "vertical = \"cco\"" "$FAKEHOME/.mopac/harness.toml" \
&& ok "T6 staged-bundle lookup ../bin/harness-linux-amd64 works (no env override)" \
|| bad "T6 staged-bundle lookup (rc=$rc)"
# --- T7 generated TOML loads (needs a built dev binary; skip otherwise)
if [ -x "$ROOT/bin/harness" ]; then
cfg="$TMP/home-TSGCOO/.mopac/harness.toml"
HOME="$TMP/home-TSGCOO" "$ROOT/bin/harness" once --dry-run --demo \
-config "$cfg" >"$TMP/out-load" 2>&1
rc=$?
grep -q "vertical: coo" "$TMP/out-load" && [ "$rc" -eq 0 ] \
&& ok "T7 rendered harness.toml loads (once --dry-run --demo, exit 0)" \
|| { bad "T7 rendered config loads (rc=$rc)"; sed 's/^/ /' "$TMP/out-load"; }
else
echo "skip T7 (bin/harness not built; run ./dev.sh check or make release first)"
fi
}
t1
t2_t6
echo
echo "pass=$PASS fail=$FAIL"
[ "$FAIL" -eq 0 ]