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
+6
View File
@@ -0,0 +1,6 @@
NEXTAUTH_SECRET=CHANGE_ME
NEXT_PRIVATE_ENCRYPTION_KEY=CHANGE_ME
NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=CHANGE_ME
NEXT_PRIVATE_DATABASE_URL=CHANGE_ME
NEXT_PRIVATE_DIRECT_DATABASE_URL=CHANGE_ME
NEXT_PRIVATE_SMTP_PASSWORD=CHANGE_ME
+72
View File
@@ -0,0 +1,72 @@
# Documenso — VPS (ROOTLESS PODMAN). Ported from the home server 2026-08-12.
#
# ⚠️ NOT STARTED. No systemd unit is enabled for this. It has never run on either
# machine, so there is no data to migrate. Bring it up with:
# systemctl --user enable --now podman-documenso.service
#
# BEFORE FIRST START, two things must happen:
#
# 1. Create its role and database on the SHARED postgres (this stack no longer
# ships its own):
# podman exec -i postgresql psql -U npm <<'SQL'
# CREATE ROLE documenso LOGIN PASSWORD '<see this service .env on the machine>';
# CREATE DATABASE documenso OWNER documenso;
# SQL
# Then add the same lines to PostgreSQL/initdb/00-roles-and-databases.sql so a
# rebuild recreates them.
#
# 2. Provide a signing certificate at ./volumes/cert/cert.p12, or Documenso will
# not start. Generate one with:
# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3650 -nodes
# openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem -passout pass:
#
# Also unset: the four NEXT_PRIVATE_SMTP_* values are still CHANGE_ME. Documenso will
# run without working email, but signature invitations will fail to send.
#
# Rootless adaptations from the home version:
# - Dropped its private postgres:16-alpine; uses the shared instance (AGENTS.md §2).
# - :Z on bind mounts (SELinux Enforcing).
# - /etc/localtime mounts removed in favour of TZ (AGENTS.md §8).
#
# NPM: proxy docs.gbrown.org -> http://documenso:3000
networks:
nginx-bridge:
external: true
services:
documenso:
image: docker.io/documenso/documenso:latest
container_name: documenso
hostname: documenso
domainname: docs.gbrown.org
networks: ['nginx-bridge']
environment:
- TZ=America/New_York
- PORT=3000
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXT_PRIVATE_ENCRYPTION_KEY=${NEXT_PRIVATE_ENCRYPTION_KEY}
- NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY}
- NEXT_PUBLIC_WEBAPP_URL=https://docs.gbrown.org
- NEXTAUTH_URL=https://docs.gbrown.org
# Shared postgres, not a private container
- NEXT_PRIVATE_DATABASE_URL=${NEXT_PRIVATE_DATABASE_URL}
- NEXT_PRIVATE_DIRECT_DATABASE_URL=${NEXT_PRIVATE_DIRECT_DATABASE_URL}
- NEXT_PRIVATE_SIGNING_TRANSPORT=local
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
- NEXT_PRIVATE_SIGNING_PASSPHRASE=
- NEXT_PUBLIC_UPLOAD_TRANSPORT=database
- NEXT_PRIVATE_SMTP_TRANSPORT=smtp-auth
- NEXT_PRIVATE_SMTP_HOST=CHANGE_ME
- NEXT_PRIVATE_SMTP_PORT=587
- NEXT_PRIVATE_SMTP_USERNAME=CHANGE_ME
- NEXT_PRIVATE_SMTP_PASSWORD=${NEXT_PRIVATE_SMTP_PASSWORD}
- NEXT_PRIVATE_SMTP_FROM_NAME=Documenso
- [email protected]
labels:
com.centurylinklabs.watchtower.enable: "true"
volumes:
- ./volumes/cert/cert.p12:/opt/documenso/cert.p12:Z
tty: true
stdin_open: true
restart: unless-stopped
@@ -0,0 +1,21 @@
[Unit]
RequiresMountsFor=/home/gib/Media
Description=Podman Compose: Documenso
After=network-online.target podman.socket podman-postgresql.service
Wants=network-online.target podman-postgresql.service
StartLimitIntervalSec=300
StartLimitBurst=3
[Service]
Type=oneshot
WorkingDirectory=%h/Server/Documenso
ExecStart=/usr/bin/podman compose up -d
ExecStop=/usr/bin/podman compose down
RemainAfterExit=yes
TimeoutStartSec=900
TimeoutStopSec=90
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.target