NextWks/AGENT.md

3.7 KiB
Raw Blame History

NextWorkspace — Architecture & Workflow

Deployment Model

Single unified script: tools/nextwks.sh (replaces old deploy.sh/install.sh).

Flag When to use
--install First-time setup on a bare VM (prompts for config)
--update Rebuild & restart with latest code (uses saved secrets)
--destroy Full greenfield redeploy (tears down everything, rebuilds from /opt/backup/.env)

Run WITHOUT sudo. The script invokes sudo only where needed (apt, writing to /opt/, iptables).

Key Architecture Decisions

1. Rootless Podman (no sudo for containers)

All podman / podman-compose commands run as the normal user. Containers are rootless.

  • Caddy binds to host ports 8080 and 8443 (not 80/443 — unprivileged)
  • iptables PREROUTING + OUTPUT -o lo redirect 80→8080, 443→8443
  • Firewall rules applied by tools/firewall-routing.sh, persisted via netfilter-persistent

2. Ephemeral Build Directory

  • Fresh git clone --depth 1 into /tmp/nextwks-build/ every time
  • No persistent /opt/NextWks repo (eliminates git permission issues)
  • Binary + configs built as user, then sudo cp to /opt/nextworkspace/

3. File Ownership

All files in /opt/nextworkspace/ and /opt/backup/ are chown -R to the non-root user after every deploy.

4. Config Templates with Placeholder Substitution

Config files live in git with {PLACEHOLDER} syntax. The script substitutes values at deploy time:

File Placeholders
config/caddy/Caddyfile {DOMAIN}, {TLS_EMAIL}
config/authelia/configuration.yml {DOMAIN}, {JWT_SECRET}, {SESSION_SECRET}, {STORAGE_ENCRYPTION_KEY}, {SMTP_HOST}, {SMTP_PORT}, {SMTP_USER}, {SMTP_PASS}
config/authelia/users_database.yml {ADMIN_PASSWORD_HASH}, {TLS_EMAIL}
compose/stack.yaml {AUTHELIA_SECRET} (= SESSION_SECRET)

Configs are regenerated on every run (install, update, destroy) from templates + saved secrets.

5. Secrets Vault (/opt/backup/.env)

All values are single-quoted to protect $ signs (bcrypt hashes). Survives --destroy. Sourced with set +u to avoid errors from $ in values.

6. Container Lifecycle

Containers are stopped before copying the new binary (avoids "Text file busy"). Restarted after deploy.


Network Architecture

Internet :443 → [iptables REDIRECT] → host :8443 → [Caddy container :443]
Internet :80  → [iptables REDIRECT] → host :8080 → [Caddy container :80]

Caddy (rootless, nextwks-net)
  ├── auth.{DOMAIN}  → Authelia :9091 (internal, no host port)
  ├── app.{DOMAIN}   → Launcher :9000 (internal, forward auth via Authelia)
  └── www.{DOMAIN}   → static files

All three containers on nextwks-net (rootless podman bridge). Only Caddy has host ports (8080, 8443). Authelia and Launcher are internal-only.

Firewall (tools/firewall-routing.sh)

  • NAT redirects: 80→8080, 443→8443
  • INPUT rules: allow lo, established, SSH (22), Caddy ports (8080, 8443), app ports (8000)
  • Global DROP at end for all other unsolicited inbound
  • Persisted: netfilter-persistent save (runs on every deploy mode)

Versioning

  • Format: MILESTONE.FEATURE.PATCH.BUILD (e.g., 0.1.0.0032)
  • Bump VERSION, update CHANGELOG.md, git tag v$(cat VERSION) on every change

Workflow

  1. Edit code in dev clone
  2. git commit -m "msg" && git tag v$(cat VERSION) && git push origin main --tags
  3. On server: ./nextwks.sh --update

Health Check

The script polls podman exec launcher curl -sf http://127.0.0.1:9000/health up to 15×3s. Launcher container uses alpine:latest with curl installed at startup via apk add --no-cache curl.