v0.1.0.0033: update AGENT.md/README.md/CHANGELOG with final architecture docs

This commit is contained in:
Claus Lohmar 2026-07-11 09:20:29 +01:00
parent 7303c94a3c
commit 9f6c85ce60
4 changed files with 100 additions and 97 deletions

141
AGENT.md
View file

@ -1,101 +1,82 @@
# NextWorkspace — Agent Workflow Instructions
# NextWorkspace — Architecture & Workflow
## Versioning
## Deployment Model
- Format: `MILESTONE.FEATURE.PATCH.BUILD` (e.g., `0.1.0.0032`)
- Bump VERSION file on every change before commit.
- Every commit must be tagged with the version: `git tag v$(cat VERSION)`
Single unified script: **`tools/nextwks.sh`** (replaces old `deploy.sh`/`install.sh`).
## Deployment Model (Unified Script)
| 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`) |
The project uses a single unified script at `tools/nextwks.sh` for all operations.
The old `deploy.sh` and `install.sh` are deprecated.
**Run WITHOUT sudo.** The script invokes `sudo` only where needed (apt, writing to `/opt/`, iptables).
**Key principles:**
- Source of truth is **git remote only**. No local `/opt/NextWks` repo.
- Building happens in `/tmp/nextwks-build/` via `git clone --depth 1` (fresh every time).
- Runtime goes to `/opt/nextworkspace/` (configs, compose, binary, lng).
- Secrets persist in `/opt/backup/.env` and survive `--destroy`.
- Stack runs on a shared `nextwks-net` podman bridge network.
## Key Architecture Decisions
### Script Flags
### 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`
| Flag | What it does |
|------|-------------|
| `--install` | First-time setup on a bare VM: installs deps, prompts for config, builds binary, generates configs, deploys stack |
| `--update` | Smart update: clones fresh, rebuilds binary, copies to target, bounces containers |
| `--destroy` | Full greenfield redeploy: tears down containers, wipes `/opt/nextworkspace/`, rebuilds from scratch using saved secrets from `/opt/backup/.env` |
### 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/`
### Workflow for Making Changes
### 3. File Ownership
All files in `/opt/nextworkspace/` and `/opt/backup/` are `chown -R` to the non-root user after every deploy.
1. Edit code in the development clone.
2. Test locally (e.g., `go build && go run .`).
3. Bump `VERSION` (increment BUILD).
4. Update `CHANGELOG.md`.
5. Commit: `git add -A && git commit -m "description"`
6. Tag: `git tag v$(cat VERSION)`
7. Push: `git push origin main --tags`
8. Deploy: `./nextwks.sh --update` (no sudo — script handles it per-command)
> **Note:** The deploy script is downloaded by users via `curl` from the repo. For production deployment, users run:
> ```bash
> curl -sL https://git.lohmar.co.uk/lexton-it/NextWks/raw/branch/main/tools/nextwks.sh | sudo bash -s -- --install
> ```
## Build Process
- Static Go binary: `CGO_ENABLED=0 go build -o nextworkspace .`
- Must run on Alpine in the container (no glibc dependency).
- Binary runs as PID 1 in the `launcher` container.
## Architecture
```
Caddy (:80/:443, host ports)
├── auth.{DOMAIN} → Authelia :9091 (internal)
├── app.{DOMAIN} → Launcher :9000 (internal) with forward auth
└── www.{DOMAIN} → static files
All three containers on nextwks-net (podman bridge).
Only Caddy exposes ports to host.
```
## Config Template System
Config files use `{PLACEHOLDER}` syntax. The script substitutes values at deploy time:
### 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}` (same as `SESSION_SECRET`) |
| `compose/stack.yaml` | `{AUTHELIA_SECRET}` (= `SESSION_SECRET`) |
## Env Vault (`/opt/backup/.env`)
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
Persisted secrets across destroys:
```
DOMAIN=nextwks.eu
TLS_EMAIL=admin@nextwks.eu
ADMIN_USERNAME=master
ADMIN_PASSWORD=<generated>
SMTP_HOST=smtp.openxchange.eu
SMTP_PORT=587
SMTP_USER=post@nextwks.eu
SMTP_PASS=<prompted>
JWT_SECRET=<auto-generated>
SESSION_SECRET=<auto-generated>
STORAGE_ENCRYPTION_KEY=<auto-generated>
ADMIN_PASSWORD_HASH=<bcrypt hash>
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
After deploy, the script polls `podman exec launcher wget -qO- http://127.0.0.1:9000/health`
up to 10 times (2s interval). Expected response: `OK`.
## Commit Message Style
- Imperative mood ("Add", "Fix", "Update", "Bump")
- Reference the component if relevant ("launcher: add health endpoint")
- Keep under 72 chars for the first line
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`.

View file

@ -1,5 +1,29 @@
# Changelog
## 0.1.0.0033 — 2026-07-11
### Added
- `tools/firewall-routing.sh` — iptables redirects + VM firewall
- `storage.encryption_key` to Authelia config (required by v4.38+)
- Auto-detection of existing install in `--install` mode
### Changed
- **Rootless Podman**: all container commands run without sudo
- **Ports**: Caddy binds to 8080/8443, iptables redirects 80/443
- `.gitignore`: `/nextworkspace` (root-scoped) to track `config/nextworkspace/`
- Configs regenerated on every mode (install/update/destroy)
- `.env` values single-quoted, written via `tee -a` to preserve `$` in bcrypt hashes
- Admin password: now 24 mixed-case alphanumeric chars (base64)
- Containers stopped before binary copy to avoid "Text file busy"
- Firewall rules persisted via `netfilter-persistent save`
- Docs: AGENT.md, README.md fully updated
### Fixed
- `SSL_ERROR_INTERNAL_ERROR_ALERT` — Authelia now starts with proper config
- Password hash corruption — `$2a$...` no longer mangled by `bash -c`
- "Text file busy" during `--update` — containers stopped before copy
- `--update` skipped config regeneration (now always regenerates)
## 0.1.0.0032 — 2026-07-11
### Added

View file

@ -5,24 +5,22 @@ A self-hosted productivity suite for startups. One binary + Caddy + Authelia.
## Architecture
```
app.nextwks.eu :443 auth.nextwks.eu :443
│ │
Caddy (TLS + forward auth) Caddy → Authelia :9091
│ │
├── /home/ → launcher page └── authelia-api :8080
├── /drive/* → OpenCloud :9100
├── /office/* → Euro Office :9200
├── /erp/* → ERPNext :9300
├── /chat/* → Matrix :9400
├── /meet/* → Jitsi :9500
├── /mail/* → Alps :9600
├── /ai/* → Open WebUI :9700
└── /admin/* → Portainer :9800
Internet :443 ──iptables──> :8443 ──> Caddy container :443
Internet :80 ──iptables──> :8080 ──> Caddy container :80
Caddy (rootless podman, nextwks-net)
├── auth.{DOMAIN} ──> Authelia :9091 (internal)
├── app.{DOMAIN} ──> Launcher :9000 (forward auth via Authelia)
└── www.{DOMAIN} ──> static files
Authelia :9091 ──> api :8080 (internal)
Launcher :9000 ──> /config, /people, /settings, /health
```
- **Caddy**: Reverse proxy, TLS (auto LE), subdomain routing, forward auth to Authelia
- **Authelia**: OIDC provider, 2FA, identity store
- **Binary**: Go launcher + path-based reverse proxy to upstream apps
- **Caddy**: TLS termination (ZeroSSL/LE), subdomain routing, forward auth to Authelia
- **Authelia**: OIDC provider, 2FA, identity store, user management API
- **Launcher**: Go binary — app dashboard, people directory, admin panel, settings
- **iptables**: Redirects 80→8080 and 443→8443 so Caddy can run rootless
## Quick Start (Bare VM)

View file

@ -1 +1 @@
0.1.0.0032
0.1.0.0033