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
181 lines
8.1 KiB
Bash
Executable File
181 lines
8.1 KiB
Bash
Executable File
#!/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 ]
|