Panama learns what a server is: from a root login to running containers

A machine's role is now the interview's first question and the one answer
Panama records. Servers get the same shell minus the screen: core packages,
nvm, Bun, Claude Code and Codex (desktops get Codex too), linger, rootless
ports from 80, firewalld, the nginx-bridge network, and a nightly image
updater that replaced watchtower for cause.

server/containers/ carries junior's 23 compose services -- secrets moved to
per-machine .env files that never enter this public repo, every transformed
compose proven to render byte-identical to what is live. 'panama server'
enables, disables and relinks them; nothing here restarts a running service.
'boot --server' walks a fresh VPS from its root login to a normal install.

Five new contracts pin the secrets rule, the catalog's shape, panama-server's
behavior, the role plumbing, and the dotfile classification.

Claude-Session: https://claude.ai/code/session_01NU5JGiN3JfzqrLQB6wmJ1E
This commit is contained in:
Gabriel Brown
2026-08-25 23:11:49 -04:00
parent 9b338608ef
commit f33da41cc6
93 changed files with 4735 additions and 247 deletions
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Every directory under config/dot is claimed by exactly one of
# link-dotfiles' three lists -- universal (both roles), desktop, or handled
# (linked or consumed some other way in that script). The failure this
# prevents is quiet: a new dot directory added to the repo but classified
# nowhere links on no server, or on every server, depending on which list the
# author forgot -- and nothing says so until a machine is missing its config.
#
# The lists are lifted from the script itself rather than restated here, so
# renaming them fails loudly instead of freezing a stale copy.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
link_dotfiles="$repo_dir/setup/scripts/link-dotfiles"
dot_dir="$repo_dir/config/dot"
findings=()
note() { findings+=("$1"); }
lists="$(sed -n '/^universal_dirs=(/,/^handled_dirs=(.*)$/p' "$link_dotfiles")"
if [[ -z "$lists" ]]; then
printf 'dotfile classification contract: link-dotfiles no longer defines the three lists\n' >&2
exit 1
fi
eval "$lists"
claimed=("${universal_dirs[@]}" "${desktop_dirs[@]}" "${handled_dirs[@]}")
for dir in "$dot_dir"/*/; do
name="$(basename "$dir")"
hits=0
for entry in "${claimed[@]}"; do
[[ "$entry" == "$name" ]] && hits=$((hits + 1))
done
if (( hits == 0 )); then
note "config/dot/$name is claimed by no list, so servers silently skip or acquire it"
elif (( hits > 1 )); then
note "config/dot/$name is claimed by $hits lists"
fi
done
for entry in "${claimed[@]}"; do
[[ -d "$dot_dir/$entry" ]] \
|| note "link-dotfiles classifies '$entry', which config/dot does not contain"
done
if (( ${#findings[@]} > 0 )); then
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
printf 'dotfile classification contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'dotfile classification contract: PASS (%d directories classified)\n' "${#claimed[@]}"