The catalog in server/containers had drifted from the VPS. Eleven compose
files and twelve units no longer matched what runs, and two running
services, Gbrown_Agent and BillTracker, were missing. server/vps is now a
copy of the live files, and panama-server reads from it.
server/home holds the home server's services in the same shape. They still
run under Docker, so the podman units there are drafts for a later move, and
nothing installs from that tree. PostHog, Sentry and Plausible are checkouts
of upstream repositories. Only their local files are kept, next to an
UPSTREAM file naming the repository and the exact commit.
Every secret in both trees is a ${VAR} reference named in .env.example, or
CHANGE_ME in files that are not compose files. The live servers still hold
some of those values inline, so a live file can differ from its copy on
those lines alone. Existing .env.example files keep their comments, with new
keys appended at the end.
The ignore rules and the secrets contract now cover both trees, plus the
volume/ and volumes/ directories the home server uses.
79 lines
3.0 KiB
Bash
Executable File
79 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Every service in the catalog has the same shape, because panama-server and
|
|
# the update timer both depend on it blindly:
|
|
#
|
|
# * a compose.yml, which is the only file enable symlinks into ~/Server
|
|
# * exactly one podman-*.service unit -- its filename is the unit's
|
|
# identity, so two would be an ambiguity and zero an unenableable service
|
|
# * a WorkingDirectory of %h/Server/<Name>, matching the directory enable
|
|
# creates -- a unit pointing anywhere else starts compose against a
|
|
# directory that has no .env and no data
|
|
# * every ${VAR} the compose interpolates without a default is named in
|
|
# .env.example, or the first enable renders a compose full of empty
|
|
# strings and the service runs misconfigured rather than failing
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
# The VPS catalog is what panama-server installs from. server/home/ shares the
|
|
# layout but is not installable yet, so it is held to the secrets contract only.
|
|
containers_dir="$repo_dir/server/vps"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
[[ -d "$containers_dir" ]] || { printf 'containers shape contract: PASS (no services yet)\n'; exit 0; }
|
|
|
|
shopt -s nullglob
|
|
for dir in "$containers_dir"/*/; do
|
|
name="$(basename "$dir")"
|
|
|
|
[[ -f "$dir/compose.yml" ]] || note "$name has no compose.yml"
|
|
|
|
units=("$dir"/*.service)
|
|
if (( ${#units[@]} == 0 )); then
|
|
note "$name has no unit file"
|
|
continue
|
|
elif (( ${#units[@]} > 1 )); then
|
|
note "$name has ${#units[@]} unit files; its identity is ambiguous"
|
|
continue
|
|
fi
|
|
unit="${units[0]}"
|
|
|
|
case "$(basename "$unit")" in
|
|
podman-*.service) ;;
|
|
*) note "$name's unit is not named podman-<name>.service: $(basename "$unit")" ;;
|
|
esac
|
|
|
|
grep -q "^WorkingDirectory=%h/Server/$name\$" "$unit" \
|
|
|| note "$name's unit does not work in %h/Server/$name"
|
|
grep -q 'podman compose' "$unit" \
|
|
|| note "$name's unit does not run podman compose"
|
|
|
|
# ${VAR} without a :- default has nowhere to come from but the .env, and
|
|
# the .env is seeded from .env.example -- so a variable the example does
|
|
# not name is one the first enable silently renders empty.
|
|
[[ -f "$dir/compose.yml" ]] || continue
|
|
while IFS= read -r var; do
|
|
[[ -n "$var" ]] || continue
|
|
if [[ ! -f "$dir/.env.example" ]]; then
|
|
note "$name interpolates \${$var} but has no .env.example"
|
|
continue
|
|
fi
|
|
grep -qE "^${var}=" "$dir/.env.example" \
|
|
|| note "$name interpolates \${$var}, which .env.example does not name"
|
|
done < <(grep -vE '^[[:space:]]*#' "$dir/compose.yml" 2>/dev/null \
|
|
| grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' \
|
|
| sed 's/^\${//; s/}$//' | sort -u)
|
|
done
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
|
printf 'containers shape contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'containers shape contract: PASS\n'
|