# 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).