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 -13
View File
@@ -34,19 +34,25 @@ PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
UPGRADE=0
FORCE_PACKAGES=0
ROLE_PRESET=""
for arg in "$@"; do
case "$arg" in
--upgrade) UPGRADE=1 ;;
--packages) FORCE_PACKAGES=1 ;;
--server) ROLE_PRESET=server ;;
-h|--help)
cat <<'USAGE'
usage: install [--upgrade] [--packages]
usage: install [--upgrade] [--packages] [--server]
(no arguments) Build this machine. Asks the interview, runs every stage.
--upgrade Update a machine that already exists. Asks nothing, and
skips setup-identity and install-hardware.
--packages Run install-packages even when the lists are unchanged.
Only meaningful with --upgrade; a full install always runs it.
--server Answer the interview's role question with 'server' without
being asked -- the curl-onto-a-fresh-VPS path. The rest of
the interview still runs. Meaningless with --upgrade, which
reads the role this machine already recorded.
USAGE
exit 0 ;;
*)
@@ -119,9 +125,16 @@ record_packages_hash() {
if (( ! UPGRADE )); then
bootstrap=()
command -v gum >/dev/null 2>&1 || bootstrap+=(gum)
command -v lspci >/dev/null 2>&1 || bootstrap+=(pciutils)
command -v mokutil >/dev/null 2>&1 || bootstrap+=(mokutil)
command -v fwupdmgr >/dev/null 2>&1 || bootstrap+=(fwupd)
# The probe tools serve only the hardware questions, which a server is never
# asked -- installing lspci on a VPS to not use it would be the interview
# costing packages the machine has no reason to carry. Only gated when the
# role is already known; a plain ./install on a server still bootstraps them,
# harmlessly, because the role is not known until the interview answers.
if [[ "$ROLE_PRESET" != server ]]; then
command -v lspci >/dev/null 2>&1 || bootstrap+=(pciutils)
command -v mokutil >/dev/null 2>&1 || bootstrap+=(mokutil)
command -v fwupdmgr >/dev/null 2>&1 || bootstrap+=(fwupd)
fi
if (( ${#bootstrap[@]} > 0 )); then
echo "Installing what the setup questions are built on: ${bootstrap[*]}"
sudo dnf install -y "${bootstrap[@]}" >/dev/null || {
@@ -190,16 +203,35 @@ if (( ! UPGRADE )); then
PANAMA_ANSWERS="$(mktemp -t panama-answers.XXXXXX)"
export PANAMA_ANSWERS
if ! "$PANAMA_PATH/setup/scripts/interview"; then
if ! PANAMA_ROLE_PRESET="$ROLE_PRESET" "$PANAMA_PATH/setup/scripts/interview"; then
exit 1
fi
# shellcheck source=/dev/null
source "$PANAMA_ANSWERS"
export PANAMA_HOSTNAME PANAMA_GIT_NAME PANAMA_GIT_EMAIL PANAMA_GIT_EDITOR \
PANAMA_GH_LOGIN PANAMA_SSH_KEY PANAMA_NVIDIA PANAMA_MOK_HASH \
PANAMA_DEBLOAT PANAMA_FIRMWARE PANAMA_EXTRAS PANAMA_USER_CONTENT
export PANAMA_ROLE PANAMA_HOSTNAME PANAMA_GIT_NAME PANAMA_GIT_EMAIL \
PANAMA_GIT_EDITOR PANAMA_GH_LOGIN PANAMA_SSH_KEY PANAMA_NVIDIA \
PANAMA_MOK_HASH PANAMA_DEBLOAT PANAMA_FIRMWARE PANAMA_EXTRAS \
PANAMA_USER_CONTENT
fi
# ── The role ─────────────────────────────────────────────────────────────────
#
# The one interview answer that outlives the run, because every later
# `panama update` runs with no interview and still has to know which machine
# this is. A fresh install records what was just answered; an upgrade reads
# what an earlier install recorded, defaulting to desktop -- which is what
# every machine that predates roles is. Exported so each stage sees the same
# answer through setup/lib/machine-role without re-deriving it.
# shellcheck source=setup/lib/machine-role
source "$PANAMA_PATH/setup/lib/machine-role"
if (( ! UPGRADE )); then
PANAMA_ROLE="${PANAMA_ROLE:-desktop}"
panama_role_record "$PANAMA_ROLE"
else
PANAMA_ROLE="$(panama_role)"
fi
export PANAMA_ROLE
# One password, before anything long runs, and then never again. The stages
# call sudo dozens of times across twenty-plus minutes, and the timestamp
# expires five minutes after whichever call came last -- so a single dnf step
@@ -223,7 +255,19 @@ if [[ -n "${PANAMA_HOSTNAME:-}" ]]; then
echo "Hostname set to: $(hostname)"
fi
STAGES=(install-packages link-dotfiles link-skills link-user change-settings link-vicinae-scripts setup-identity install-hardware)
# One list per role, chosen whole rather than filtered from a superset, so
# what a server runs is readable here rather than derived. A server gets the
# shared stages plus its own two; it never links skills (all three shipped
# skills operate the desktop), never touches gsettings or Vicinae, and has no
# hardware stage -- NVIDIA, Secure Boot and firmware are first-boot desktop
# concerns. setup-server runs after packages (it needs podman and firewalld
# installed) and link-server after that, so the units it links land on a
# machine already able to run them.
if [[ "$PANAMA_ROLE" == server ]]; then
STAGES=(install-packages link-dotfiles link-user setup-server link-server setup-identity)
else
STAGES=(install-packages link-dotfiles link-skills link-user change-settings link-vicinae-scripts setup-identity install-hardware)
fi
# The two an upgrade drops. Both exist only to act on interview answers, and
# both are first-run decisions: who you are and what hardware this is. Filtered
@@ -297,10 +341,21 @@ fi
# It never changes the exit code. On a fresh machine it legitimately reports
# things as unconfigured -- no Home Assistant token yet, Nextcloud not signed in
# -- and failing an install over those would be crying wolf.
doctor="$PANAMA_PATH/config/dot/quickshell/scripts/panama-doctor"
if [[ -x "$doctor" ]]; then
printf '\n=== health ===\n'
"$doctor" --summary || true
# On a server the quickshell doctor would report a desktop that was never
# installed; what is actually running there is the container services, and
# panama-server status is the check that answers for them.
if [[ "$PANAMA_ROLE" == server ]]; then
server_status="$PANAMA_PATH/bin/panama-server"
if [[ -x "$server_status" ]]; then
printf '\n=== health ===\n'
"$server_status" status || true
fi
else
doctor="$PANAMA_PATH/config/dot/quickshell/scripts/panama-doctor"
if [[ -x "$doctor" ]]; then
printf '\n=== health ===\n'
"$doctor" --summary || true
fi
fi
# Whatever this particular machine wants doing that Panama should not carry for
@@ -318,6 +373,8 @@ fi
if (( ${#failed[@]} == 0 )); then
if (( UPGRADE )); then
echo "Panama is up to date."
elif [[ "$PANAMA_ROLE" == server ]]; then
echo "Panama installed. Enable a service with: panama server enable <Name>"
else
echo "Panama installed. Log out and choose the Hyprland session to start it."
fi