closes board-viewer-polish
- M1 src/parser.ts: comment explaining why **Status:** text field is intentionally
ignored (emoji is canonical, prevents future-reader head-scratch)
- M2 src/render.ts + tests: clamp future ISO to '0m' (server clock drift safety)
+ inline comment + test
- M3 .tasks/board-viewer-gitea-reader.md: decisions-log clarifying orphan
per-task files are out of scope by design (STATUS.md = index of record)
- M4 static/board.{js,css} + src/render.ts: filter rebuilt to query records
(not DOM badge text); 5 status-filter pill buttons in header with active
toggle state; smoke-verified blocked filter
- M5 deploy/Dockerfile.build: HEALTHCHECK (file fresher than 2×tick) and
stderr redirect for failures with timestamp
- M6 src/reader.ts: parallelize getLatestCommitIso per-repo via Promise.all
(preserves block order)
49 tests pass (was 43 pre-polish), typecheck clean, smoke against real Gitea
returns 81 records from 4 repos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: everyBOARD_TICK_SEC(default 300s) runsnode --experimental-strip-types src/cli.tsagainstauth.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 → <VDS IP> (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):
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:
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
# 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, thensudo docker compose restart build. - Add user: generate
htpasswd -nbB <user> 'PASS', escape every$as$$, append (preceded by a comma) toBOARD_VIEWER_USERSin.env, thensudo docker compose up -d(re-applies labels on the web service). - New version of
board-viewer-buildimage: 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:
- Restore VDS hostname + DNS A-record.
- Restore Traefik stack (separate concern, in
/opt/stacks/traefik/). git clone https://git.kzntsv.site/OpeItcLoc03/board-viewer.git.- 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).