# Deploy — `board-viewer` Read-only HTML kanban viewer over Gitea API. Runs as a two-service Docker stack on the VDS, behind Traefik with basic-auth. ``` VDS ├── traefik (websecure → letsEncrypt) │ └── Host(`board.kzntsv.site`) → board-viewer-web (basic-auth middleware) └── /opt/stacks/board-viewer/ ├── docker-compose.yml ← stack ├── auth.toml ← Gitea token + repo whitelist (NOT committed) ├── .env ← BOARD_VIEWER_USERS (basic-auth bcrypt creds, NOT committed) └── nginx.conf ``` Two services: - **`board-viewer-build`** (`registry.kzntsv.site/board-viewer-build`) Cron-loop: every `BOARD_TICK_SEC` (default 300s) runs `node --experimental-strip-types src/cli.ts` against `auth.toml`, writes `/output/{index.html, static/*}` into the shared volume. - **`board-viewer-web`** (`nginx:alpine`) Serves the shared volume on port 80; Traefik attaches host + basic-auth. Volume `dist` is internal to the compose project; nginx mounts it read-only. --- ## One-time setup ### 1. DNS Create an A-record: ``` board.kzntsv.site → (TTL 3600) ``` Wait for propagation (a few minutes typically). ### 2. Build & push the build image From a dev machine **at the repo root** (this Dockerfile expects to be built from the project root): ```bash docker build -f deploy/Dockerfile.build -t registry.kzntsv.site/board-viewer-build:latest . docker push registry.kzntsv.site/board-viewer-build:latest ``` (See `~/projects/.wiki` for the VDS registry login command if your daemon isn't already authenticated.) ### 3. Bootstrap on VDS SSH to the VDS, then: ```bash sudo mkdir -p /opt/stacks/board-viewer cd /opt/stacks/board-viewer # Copy compose + nginx.conf from this repo's deploy/ dir # (via scp / git pull / whatever you use for other stacks). sudo cp .../docker-compose.yml . sudo cp .../nginx.conf . # auth.toml — Gitea token + repo whitelist sudo cp .../auth.toml.example auth.toml sudo nano auth.toml # paste real gitea_token; edit board_viewer_repos list # .env — basic-auth credentials for the Traefik middleware sudo cp .../.env.example .env # Generate a bcrypt hash: htpasswd -nbB viewer 'STRONG_PASSWORD_HERE' # Output is `viewer:$2y$05$...`. Replace every `$` with `$$` (compose # interpolates env_file values), then paste into BOARD_VIEWER_USERS in .env. # Multiple users: comma-separated, each with `$$` escaping. sudo nano .env sudo chmod 600 auth.toml .env sudo docker compose pull sudo docker compose up -d ``` ### 4. Verify ```bash # Service is up sudo docker compose ps # First render happens immediately on container start; check logs: sudo docker compose logs -f build # expect: "wrote /output/index.html (N records from M repos)" # HTTP smoke (gives 401 without creds, 200 with) curl -I https://board.kzntsv.site/ # → 401 curl -I -u viewer:'PASS' https://board.kzntsv.site/ # → 200 ``` --- ## Day-to-day - **Force regen now:** `sudo docker compose restart build` (next render runs immediately on container start). - **View render logs:** `sudo docker compose logs --tail=200 build` - **View access logs:** `sudo docker compose logs --tail=200 web` - **Edit repo list:** edit `/opt/stacks/board-viewer/auth.toml`, then `sudo docker compose restart build`. - **Add user:** generate `htpasswd -nbB 'PASS'`, escape every `$` as `$$`, append (preceded by a comma) to `BOARD_VIEWER_USERS` in `.env`, then `sudo docker compose up -d` (re-applies labels on the web service). - **New version of `board-viewer-build` image:** on dev → `docker build … && docker push …`, on VDS → `sudo docker compose pull && sudo docker compose up -d`. --- ## Disaster recovery The repo is the source of truth. To rebuild the stack from scratch: 1. Restore VDS hostname + DNS A-record. 2. Restore Traefik stack (separate concern, in `/opt/stacks/traefik/`). 3. `git clone https://git.kzntsv.site/OpeItcLoc03/board-viewer.git`. 4. Follow "One-time setup" above. No persistent state lives in this stack — every render is freshly produced from Gitea. The only artefacts to recreate are `auth.toml` (token from password manager) and `.env` with `BOARD_VIEWER_USERS` (regenerate bcrypt hash from password manager).