# board-viewer-vds-deploy ## Goal Поднять статическую HTML-доску `board-viewer` (read-only kanban over Gitea API) на VDS под `board.kzntsv.site`. Двухсервисный docker-compose stack (cron-loop build + nginx serve) за traefik с basic-auth. Periodic regen каждые ~5 мин внутри build-контейнера. Артефакты deploy/ написаны и валидированы в dev-проекте `~/projects/board-viewer/deploy/`. Эта таска — чисто ops-handoff: DNS, image build/push, bootstrap stack, smoke. ## Source dev-project - Repo: `~/projects/board-viewer/` → `https://git.kzntsv.site/OpeItcLoc03/board-viewer.git` - Artefacts (HEAD-of-master, code-complete): - `deploy/docker-compose.yml` — стек на `proxy` network, traefik labels - `deploy/Dockerfile.build` — node:22-alpine + cron-loop entrypoint - `deploy/nginx.conf` — static serving + Cache-Control - `deploy/auth.toml.example` — шаблон Gitea token + repo whitelist - `deploy/.env.example` — шаблон `BOARD_VIEWER_USERS` (basic-auth bcrypt, с `$$`-escape) - `deploy/README.md` — полный One-time setup + Day-to-day + DR - Compose syntax валидирован (`docker compose config`) - Smoke против real Gitea (78 records, 4 repos) — `node --experimental-strip-types src/cli.ts` производит `dist/index.html + dist/static/` ## Pending ops actions - [ ] **DNS:** A-record `board.kzntsv.site` → VDS IP (где живёт DNS — Cloudflare / namecheap) - [ ] **Build & push image:** на dev-машине из корня репо: `docker build -f deploy/Dockerfile.build -t registry.kzntsv.site/board-viewer-build:latest .` `docker push registry.kzntsv.site/board-viewer-build:latest` - [ ] **Bootstrap `/opt/stacks/board-viewer/` на VDS:** - скопировать `docker-compose.yml` + `nginx.conf` из репо - `cp auth.toml.example auth.toml`, вписать реальный Gitea token + `board_viewer_repos` list - `cp .env.example .env`, сгенерировать `htpasswd -nbB viewer 'PASS'`, escape каждый `$`→`$$`, вписать в `BOARD_VIEWER_USERS` - `chmod 600 auth.toml .env` - [ ] `sudo docker compose pull && sudo docker compose up -d` - [ ] **Smoke:** - `curl -I https://board.kzntsv.site/` → 401 - `curl -I -u viewer:'PASS' https://board.kzntsv.site/` → 200 - `sudo docker compose logs --tail=200 build` → видна строка `wrote /output/index.html (N records from M repos)` ## Acceptance criteria - DNS `board.kzntsv.site` резолвится в VDS IP - Traefik route на `board.kzntsv.site` с basic-auth middleware (creds из `.env`) - Build-контейнер крутится без рестартов, каждые `BOARD_TICK_SEC` (default 300s) обновляет `/output/index.html` - Web-контейнер раздаёт `dist/` через nginx; 401 без креды, 200 с - В password manager сохранены: `viewer` password + Gitea token (на случай DR) ## Decisions log - 2026-05-22: ownership transferred from `~/projects/board-viewer/.tasks/[board-viewer-cron-deploy]` (dev-проект, code-complete) на `.admin/.tasks/` per новое правило workshop CLAUDE.md §5 (domain-промоушены с ops-acceptance → target=.admin). Slug изменён `cron-deploy`→`vds-deploy` для cross-project ясности. ## Notes - Mechanism — docker-compose stack (не systemd timer); гомогенно с existing VDS pattern (Gitea, registry, ntfy). Cron-loop = `while true; do node cli.ts; sleep ${BOARD_TICK_SEC}; done` внутри build-контейнера. - Rate-limit: 4 repos × ~2 Gitea API calls каждые 5 мин = ~24-100 calls/period, токенизированный Gitea не упрётся. - DR-сценарий — см. `deploy/README.md` секция «Disaster recovery». No persistent state — каждый render fresh из Gitea.