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
+54 -9
View File
@@ -34,16 +34,61 @@ hyprland_at="$(line_of '^HYPR_FILE=')"
# ── Nothing fragile above the desktop ───────────────────────────────────────
#
# Named individually rather than by pattern: each is a command whose failure is
# survivable, and each one above the Hyprland block is a machine that boots to
# nothing.
# survivable, and each one that EXECUTES above the Hyprland block is a machine
# that boots to nothing.
#
# Executes, not appears: the fragile steps live in functions defined near the
# top (the server role calls them without ever reaching a desktop section),
# and a definition runs nothing. So function bodies are excluded from the
# position scan, and the desktop path's calls to those functions are required
# to sit below the desktop instead.
for fragile in 'dnf swap' 'groupupdate' 'group upgrade' 'gstreamer1-plugins' \
'flatpak install' 'nvm install' 'curl -fsSL'; do
while read -r at; do
(( at < hyprland_at )) \
&& note "'$fragile' runs at line $at, above the desktop at line $hyprland_at"
done < <(grep -n -- "$fragile" "$installer" | grep -v '^\s*#' | cut -d: -f1)
done
while IFS= read -r finding; do
[[ -n "$finding" ]] && note "$finding"
done < <(python3 - "$installer" "$hyprland_at" <<'PY'
import re, sys
path, hypr = sys.argv[1], int(sys.argv[2])
lines = open(path, encoding="utf-8").read().splitlines()
in_body = False
body = set()
for i, line in enumerate(lines, 1):
if not in_body and re.match(r'^[a-z_]+\(\)\s*\{', line):
in_body = True
body.add(i)
continue
if in_body:
body.add(i)
if line == '}':
in_body = False
fragile = ['dnf swap', 'groupupdate', 'group upgrade', 'gstreamer1-plugins',
'flatpak install', 'nvm install', 'curl -fsSL']
for i, line in enumerate(lines, 1):
if i >= hypr or i in body or line.strip().startswith('#'):
continue
for needle in fragile:
if needle in line:
print(f"'{needle}' runs at line {i}, above the desktop at line {hypr}")
# The desktop path still has to run the fragile helpers -- below the desktop.
# (The server path calls them above, inside a branch that exits before the
# desktop section; the exit is asserted back in bash.)
for call in ('setup_node', 'install_bun', 'install_claude_code', 'install_codex'):
calls = [i for i, line in enumerate(lines, 1)
if re.match(r'^\s*' + call + r'\s*$', line) and i not in body]
if not calls:
print(f"{call} is never called, so the desktop path skips it")
elif not any(i > hypr for i in calls):
print(f"{call} is only called above the desktop")
PY
)
# The server branch is what excuses fragile calls above the desktop, and only
# because it never falls through into the desktop section.
sed -n '/^if \[\[ "\$ROLE" == server \]\]; then/,/^fi/p' "$installer" | grep -q '^\s*exit 0' \
|| note 'the server branch does not exit before the desktop section'
# ── Everything fragile is actually tolerated ────────────────────────────────
#