task-board: add stack + elway deploy playbook

stacks/task-board/compose.yaml + .env.example describe the runtime —
image tag is task-board:local (built on the host), 7878 on host maps
to 7878 in container, SQLite lives at /opt/docker/conf/task-board/data/
(bind mount, uid 1000 friendly), homepage auto-card labels under
Toolchain group, on traefik-net like the rest of the fleet.

playbooks/deploy-task-board.yaml is the first real elway playbook —
exercises everything we built tier-1 + tier-2 idempotency for:

  - `creates:` on mkdir + first-time clone + compose dir + data dir
  - `when:` to chown /opt/docker/build only if it came up root-owned
  - `when:` to seed .env only if one doesn't already exist (never
    clobbers user edits on rerun)
  - `changed_when:` on the `git reset --hard` step so repeat runs
    against the same ref report `ok` instead of `changed`
  - `changed_when: "false"` on every verify step (they attest, not
    change)
  - `upload:` with mode for compose.yaml + .env

Post-up the playbook polls /api/health for 30s before handing off to
the verify phase, so verification doesn't race the healthcheck's
start_period. Verify covers: /api/health 200, /api/tasks shape, /mcp
reachable, container on traefik-net.

Prereqs documented in the playbook header: Docker + compose plugin,
traefik-net network, git SSH access to gitea from the target host.
This commit is contained in:
vh
2026-04-24 14:26:42 -07:00
parent f115c982bc
commit 9c20e42215
4 changed files with 246 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# task-board stack tunables. Copy to `.env` on ana-docker before deploying.
#
# The deploy playbook seeds `.env` from this template on first run only —
# it won't clobber an existing `.env`.
# Image tag. Built locally from the task-board git repo by the playbook.
TASK_BOARD_IMAGE=task-board:local
# Host port exposing MCP + web UI (container listens on 7878 internally).
TASK_BOARD_PORT=7878
# Bind address. 0.0.0.0 exposes to LAN; 127.0.0.1 restricts to localhost.
# Claude Code running on another machine needs LAN (or SSH-forward + 127.0.0.1).
TASK_BOARD_BIND=0.0.0.0
# Host path for the SQLite store. Container runs as uid 1000 — path must be
# writable by that uid (mkdir'd by the playbook without sudo, so lkraven-owned
# when lkraven is uid 1000 on the host).
TASK_BOARD_DATA_DIR=/opt/docker/conf/task-board/data
# Seconds of inactivity before a card turns grey (dormant).
TASK_BOARD_DORMANT_AFTER=3600
# Seconds archived (completed) cards are kept before hard-delete.
TASK_BOARD_ARCHIVE_TTL=86400
+51
View File
@@ -0,0 +1,51 @@
# task-board
MCP + web dashboard that shows what the assistant is working on. Green
(active) / Red (waiting) / Grey (dormant) cards with comment streams.
**Server:** ana-docker
**Port:** 7878 (configurable via `.env`)
**Upstream repo:** [vh/task-board](https://gitea.phasefinal.com/vh/task-board)
**Image:** `task-board:local` — built on the host from the git repo by
the deploy playbook. Not pulled from a registry.
## Deploy
Via elway — see `playbooks/deploy-task-board.yaml` in the eshpfi-management
root. The playbook owns the full flow: clone/update the source repo,
`docker build`, install compose + seed .env, bring up, verify health.
```bash
# First deploy (or update to latest main)
scripts/elway ana-docker --playbook playbooks/deploy-task-board.yaml
# Pin to a specific ref (tag, branch, or commit SHA)
scripts/elway ana-docker --playbook playbooks/deploy-task-board.yaml --var ref=v0.1.0
```
## Wiring into Claude Code
After deploy, add to `~/.claude/settings.json` (user-level) or `.mcp.json`
(project-level):
```json
{
"mcpServers": {
"task-board": {
"type": "http",
"url": "http://10.250.50.70:7878/mcp"
}
}
}
```
Assistant gains four tools (`task_start`, `task_update`, `task_wait`,
`task_complete`). Web UI at `http://10.250.50.70:7878/`.
## Path layout (on ana-docker)
| Host path | Container path | Purpose | Restic? |
|---|---|---|---|
| `/opt/docker/build/task-board/` | — | git checkout used as docker build context | excluded |
| `/opt/docker/compose/task-board/` | — | compose.yaml + .env | included (via `/opt/docker`) |
| `/opt/docker/conf/task-board/data/` | `/app/data` | SQLite (`tasks.db`) | **included** |
+46
View File
@@ -0,0 +1,46 @@
# task-board — MCP + web dashboard for assistant task state.
#
# Image is built on the host from the task-board git repo by the deploy
# playbook (`playbooks/deploy-task-board.yaml`), which clones into
# /opt/docker/build/task-board and runs `docker build -t task-board:local .`
# before installing this compose and bringing it up.
#
# SQLite state persists under ${TASK_BOARD_DATA_DIR} (bind-mounted) so
# container rebuilds don't lose task history.
#
# All tunables live in .env — edit that, not this file.
services:
task-board:
image: ${TASK_BOARD_IMAGE}
container_name: task-board
restart: unless-stopped
ports:
- "${TASK_BOARD_BIND:-0.0.0.0}:${TASK_BOARD_PORT}:7878"
volumes:
- ${TASK_BOARD_DATA_DIR}:/app/data
environment:
# Container always listens on 7878 internally; host port is the only knob.
- TASK_BOARD_PORT=7878
- TASK_BOARD_BIND=0.0.0.0
- TASK_BOARD_DORMANT_AFTER=${TASK_BOARD_DORMANT_AFTER:-3600}
- TASK_BOARD_ARCHIVE_TTL=${TASK_BOARD_ARCHIVE_TTL:-86400}
healthcheck:
test: ["CMD-SHELL", "python -c 'import urllib.request,sys; r=urllib.request.urlopen(\"http://127.0.0.1:7878/api/health\",timeout=3); sys.exit(0 if r.status==200 else 1)' || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
networks:
- tnet
labels:
- homepage.group=Toolchain
- homepage.name=task-board
- homepage.icon=mdi-view-dashboard-variant
- homepage.description=Assistant task state dashboard (MCP + web UI)
- homepage.href=http://10.250.50.70:${TASK_BOARD_PORT}
networks:
tnet:
name: traefik-net
external: true