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
+70 -42
View File
@@ -20,6 +20,13 @@ CONFIG="$HOME/.config"
# every symlink below fail silently while the stage still reported success.
mkdir -p "$PANAMA_OLD" "$CONFIG"
# Which machine this is. A server links the shell environment -- bash, vim,
# the universal dirs below, the tmux and btop theming, the hook samples --
# and then stops: everything after the early exit assumes a screen.
# shellcheck source=../lib/machine-role
source "$PANAMA_PATH/setup/lib/machine-role"
ROLE="$(panama_role)"
# --- Bashrc ---
echo -e "\n--- Replacing .bashrc ---"
# Backup existing .bashrc if it's a regular file
@@ -45,8 +52,21 @@ ln -s "$PANAMA_BASH/.bashrc" "$HOME/.bashrc"
#
# gnome-control-center is a separate matter and stays declared: Panama's own
# Settings hands off to it for the panels it deliberately does not own.
dirs=("espanso" "ghostty" "gtk-3.0" "gtk-4.0" "hypr" "kitty" "nvim" \
"quickshell" "tmux" "uwsm" "vicinae" "wofi" "xdg-desktop-portal")
#
# Split by role, and every directory under config/dot must be claimed by
# exactly one of these three lists -- universal, desktop, or handled (linked
# or consumed some other way below: btop exposes only themes, ohmyposh is
# read in place by config/bash/shell, panama's hook samples are copied, vim
# links a single file). tests/setup/dotfile-classification-contract fails
# when a new directory appears in none of them, because unclassified means
# silently absent from every server.
universal_dirs=("nvim" "tmux")
desktop_dirs=("espanso" "ghostty" "gtk-3.0" "gtk-4.0" "hypr" "kitty" \
"quickshell" "uwsm" "vicinae" "wofi" "xdg-desktop-portal")
handled_dirs=("btop" "ohmyposh" "panama" "vim")
dirs=("${universal_dirs[@]}")
[[ "$ROLE" == server ]] || dirs+=("${desktop_dirs[@]}")
# --- Vim vimrc ---
echo -e "\n--- Setting up vim ---"
@@ -107,6 +127,54 @@ elif [ -d "$PANAMA_DOT/tmux/themes" ]; then
log "Seeded tmux $tmux_scheme theme ($tmux_name) → $TMUX_THEME"
fi
# btop reads themes from its own config directory, but OWNS btop.conf -- it
# rewrites that file on exit -- so only the theme files are exposed, per file,
# and the config itself is left to btop. panama-theme-apps edits the single
# color_theme line in place.
BTOP_THEME_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/btop/themes"
mkdir -p "$BTOP_THEME_DIR"
for btop_theme_src in "$PANAMA_DOT"/btop/themes/*.theme; do
[ -e "$btop_theme_src" ] || continue
btop_theme_dst="$BTOP_THEME_DIR/$(basename "$btop_theme_src")"
if [ -L "$btop_theme_dst" ]; then
rm "$btop_theme_dst"
fi
if [ -e "$btop_theme_dst" ]; then
log "Keeping existing btop theme at $btop_theme_dst"
else
ln -s "$btop_theme_src" "$btop_theme_dst"
log "Linked btop theme → $btop_theme_dst"
fi
done
# Hook samples. Copied rather than symlinked, and only when absent: hooks are
# the user's own scripts, and ~/.config/panama is theirs too -- settings.json
# lives there. A symlinked directory would put their scripts in the repository
# working tree, which is the mistake the gtk bookmarks made.
PANAMA_HOOK_SAMPLES="$PANAMA_DOT/panama/hooks"
USER_HOOK_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/panama/hooks"
if [ -d "$PANAMA_HOOK_SAMPLES" ]; then
mkdir -p "$USER_HOOK_DIR"
for sample in "$PANAMA_HOOK_SAMPLES"/*.sample; do
[ -e "$sample" ] || continue
sample_dst="$USER_HOOK_DIR/$(basename "$sample")"
if [ -e "$sample_dst" ]; then
log "Keeping existing hook sample at $sample_dst"
else
cp "$sample" "$sample_dst"
log "Copied hook sample → $sample_dst"
fi
done
fi
# A server's dotfiles end here. Everything below assumes a session: lock
# screens, GTK, launcher themes, icons, wallpapers, Firefox chrome, desktop
# entries, quadlets for the desktop's own containers, file associations.
if [ "$ROLE" = server ]; then
log "Server role: desktop dotfiles skipped"
exit 0
fi
# hyprlock.conf is generated from a template on every color scheme change and
# is not committed. Seed it so the FIRST lock of a fresh install is themed --
# without it hyprlock falls back to its own defaults, which is a bare gray
@@ -139,26 +207,6 @@ elif [ -r "$HYPRLOCK_TEMPLATE" ]; then
fi
fi
# btop reads themes from its own config directory, but OWNS btop.conf -- it
# rewrites that file on exit -- so only the theme files are exposed, per file,
# and the config itself is left to btop. panama-theme-apps edits the single
# color_theme line in place.
BTOP_THEME_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/btop/themes"
mkdir -p "$BTOP_THEME_DIR"
for btop_theme_src in "$PANAMA_DOT"/btop/themes/*.theme; do
[ -e "$btop_theme_src" ] || continue
btop_theme_dst="$BTOP_THEME_DIR/$(basename "$btop_theme_src")"
if [ -L "$btop_theme_dst" ]; then
rm "$btop_theme_dst"
fi
if [ -e "$btop_theme_dst" ]; then
log "Keeping existing btop theme at $btop_theme_dst"
else
ln -s "$btop_theme_src" "$btop_theme_dst"
log "Linked btop theme → $btop_theme_dst"
fi
done
# GTK3 has no include mechanism, so its settings.ini is generated whole from a
# template rather than layered. Without this, a fresh checkout has a template
# and no settings.ini, and GTK3 applications fall back to their built-in theme.
@@ -454,26 +502,6 @@ fi
# than by symlinking the directory itself, the same way the quadlets and
# desktop entries are: Nautilus writes nothing here today, but a directory
# symlink into the repository is how machine state ends up in a tracked path.
# Hook samples. Copied rather than symlinked, and only when absent: hooks are
# the user's own scripts, and ~/.config/panama is theirs too -- settings.json
# lives there. A symlinked directory would put their scripts in the repository
# working tree, which is the mistake the gtk bookmarks made.
PANAMA_HOOK_SAMPLES="$PANAMA_DOT/panama/hooks"
USER_HOOK_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/panama/hooks"
if [ -d "$PANAMA_HOOK_SAMPLES" ]; then
mkdir -p "$USER_HOOK_DIR"
for sample in "$PANAMA_HOOK_SAMPLES"/*.sample; do
[ -e "$sample" ] || continue
sample_dst="$USER_HOOK_DIR/$(basename "$sample")"
if [ -e "$sample_dst" ]; then
log "Keeping existing hook sample at $sample_dst"
else
cp "$sample" "$sample_dst"
log "Copied hook sample → $sample_dst"
fi
done
fi
PANAMA_NAUTILUS_DIR="$PANAMA_PATH/config/local/share/nautilus-python/extensions"
USER_NAUTILUS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nautilus-python/extensions"
if [ -d "$PANAMA_NAUTILUS_DIR" ]; then