althing-chamber: scaffold deploy stack on ana-docker
Two-service compose (chamber + forseti sidecar daemon) sharing a single
SQLite store via bind-mount under /opt/docker/conf/althing-chamber/data.
eventbus.bridge_from_db is the cross-process glue — forseti's commits
reach chamber's SSE subscribers via the bridge.
Pattern matches task-board's build-on-host deploy:
- elway playbook clones vh/althing into /opt/docker/build/
- docker build -t althing-chamber:local . (no registry)
- playbook uploads compose + seeds .env one-time, brings both
services up, polls /health
- Gitea Actions workflow lives in vh/althing; reference copy here.
Internal tooling — host port 7881 (chamber's default of 7878 collides
with task-board). LAN-direct, no Traefik. Container always listens on
8000 internally.
Scaffold will fail to bring the chamber container up healthy until
galdrabok-side commits land:
- Dockerfile at vh/althing repo root (two-stage: uv-bookworm-slim
build → python:3.12-slim runtime, locked per open_questions §2
of the v1 contract).
- GET /health endpoint on the chamber app (200, no DB read).
- ALTHING_BIND / ALTHING_PORT env-var support in
core.cli.chamber_serve / core.chamber.cli (env > config.yaml >
defaults precedence).
Coordinated via althing thread 01KRMAK7RD7TP6C8DF4KXV31RT.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# Deploy althing-chamber (https://gitea.phasefinal.com/vh/althing) to a
|
||||
# Docker host following the PFI /opt/docker/ convention (ana-docker by
|
||||
# default, but the playbook works against any host with Docker in place).
|
||||
#
|
||||
# Brings up TWO compose services from a single image:
|
||||
# althing-chamber — FastAPI/HTMX web UI, port 7881 host → 8000 container
|
||||
# althing-forseti — moderator daemon, no port
|
||||
#
|
||||
# Both processes share the SQLite store via the same bind-mount under
|
||||
# /opt/docker/conf/althing-chamber/data. eventbus.bridge_from_db is the
|
||||
# cross-process glue.
|
||||
#
|
||||
# Idempotent: rerunning is safe. Creates-gates skip work that's already
|
||||
# done; `docker compose up -d` is itself idempotent (no restart unless
|
||||
# compose content or env changed).
|
||||
#
|
||||
# Usage:
|
||||
# scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml
|
||||
# scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml --var ref=v0.1.0
|
||||
#
|
||||
# Prereqs on the target host:
|
||||
# - Docker + docker compose plugin
|
||||
# - Target user (lkraven) has git SSH access to gitea.phasefinal.com
|
||||
# — either SSH key authorized in gitea, or the repo is HTTPS-reachable
|
||||
# if you swap `repo_url` below.
|
||||
# - Target user is in the `docker` group.
|
||||
|
||||
vars:
|
||||
repo_url: git@gitea.phasefinal.com:vh/althing.git
|
||||
ref: main
|
||||
build_dir: /opt/docker/build/althing-chamber
|
||||
image_tag: althing-chamber:local
|
||||
compose_dir: /opt/docker/compose/althing-chamber
|
||||
data_dir: /opt/docker/conf/althing-chamber/data
|
||||
host_port: "7881"
|
||||
|
||||
steps:
|
||||
# ── host-side directory prep ─────────────────────────────────────────
|
||||
- name: Ensure /opt/docker/build parent exists
|
||||
shell: mkdir -p /opt/docker/build
|
||||
sudo: true
|
||||
creates: /opt/docker/build
|
||||
|
||||
- name: Chown /opt/docker/build to lkraven (only if mkdir'd by root above)
|
||||
shell: chown lkraven:lkraven /opt/docker/build
|
||||
sudo: true
|
||||
when: '[ "$(stat -c %U /opt/docker/build)" != lkraven ]'
|
||||
|
||||
# ── fetch / sync source ─────────────────────────────────────────────
|
||||
- name: Clone althing repo if absent
|
||||
# Auto-accept the first-run host key so the playbook doesn't hang
|
||||
# prompting for yes/no.
|
||||
shell: GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new" git clone {{ repo_url }} {{ build_dir }}
|
||||
creates: "{{ build_dir }}/.git"
|
||||
|
||||
- name: Fetch from origin
|
||||
shell: cd {{ build_dir }} && git fetch --quiet origin
|
||||
|
||||
- name: Reset working tree to {{ ref }}
|
||||
# Accept either a branch name (resolves via origin/<ref>) or a
|
||||
# full/short SHA (resolves directly). CI passes the triggering
|
||||
# commit SHA via --var ref=${{ github.sha }}; manual runs pass
|
||||
# branch names like main / v0.1.0.
|
||||
shell: |
|
||||
cd {{ build_dir }}
|
||||
if sha=$(git rev-parse --verify --quiet "origin/{{ ref }}^{commit}"); then :;
|
||||
elif sha=$(git rev-parse --verify --quiet "{{ ref }}^{commit}"); then :;
|
||||
else echo "elway: ref not found: {{ ref }}" >&2; exit 1; fi
|
||||
git reset --hard "$sha"
|
||||
# Report ok (no-change) when the tree was already at the requested
|
||||
# ref — saves a noisy CHANGED status line on no-op reruns.
|
||||
changed_when: '[ "$(cd {{ build_dir }} && git rev-parse HEAD)" != "$(cd {{ build_dir }} && (git rev-parse --verify --quiet "origin/{{ ref }}^{commit}" || git rev-parse --verify --quiet "{{ ref }}^{commit}"))" ]'
|
||||
|
||||
# ── image build ─────────────────────────────────────────────────────
|
||||
- name: Build image {{ image_tag }}
|
||||
shell: cd {{ build_dir }} && docker build -t {{ image_tag }} .
|
||||
# Docker build reuses layer cache and is fast on reruns, but it
|
||||
# always runs — we can't cheaply know up-front whether anything
|
||||
# downstream has changed. Leave it in the always-run lane; Docker
|
||||
# itself handles the no-op efficiently.
|
||||
|
||||
# ── compose + data dirs ─────────────────────────────────────────────
|
||||
- name: Ensure compose dir exists
|
||||
shell: mkdir -p {{ compose_dir }}
|
||||
creates: "{{ compose_dir }}"
|
||||
|
||||
- name: Ensure data dir exists
|
||||
# Single bind-mount shared between chamber + forseti. Created as
|
||||
# lkraven (uid 1000 on these hosts), matching the container's `app`
|
||||
# user — no chown dance needed.
|
||||
shell: mkdir -p {{ data_dir }}
|
||||
creates: "{{ data_dir }}"
|
||||
|
||||
# ── deploy compose files ────────────────────────────────────────────
|
||||
- name: Upload compose.yaml
|
||||
upload:
|
||||
src: stacks/althing-chamber/compose.yaml
|
||||
dest: "{{ compose_dir }}/compose.yaml"
|
||||
mode: "0644"
|
||||
|
||||
- name: Seed .env from template (only if absent)
|
||||
upload:
|
||||
src: stacks/althing-chamber/.env.example
|
||||
dest: "{{ compose_dir }}/.env"
|
||||
mode: "0644"
|
||||
when: "[ ! -f {{ compose_dir }}/.env ]"
|
||||
|
||||
# ── bring up + wait for ready ───────────────────────────────────────
|
||||
- name: docker compose up -d
|
||||
shell: cd {{ compose_dir }} && docker compose up -d
|
||||
|
||||
- name: Wait for chamber /health to respond
|
||||
# Chamber's healthcheck is internal (inside the container's network);
|
||||
# this host-side poll confirms the published port is reachable too.
|
||||
# Short retry loop — docker compose up returns before the FastAPI
|
||||
# app finishes booting.
|
||||
shell: |
|
||||
for i in $(seq 1 30); do
|
||||
curl -sf -o /dev/null http://localhost:{{ host_port }}/health && exit 0
|
||||
sleep 2
|
||||
done
|
||||
exit 1
|
||||
changed_when: "false"
|
||||
|
||||
verify:
|
||||
- name: chamber /health returns 200
|
||||
shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/health
|
||||
changed_when: "false"
|
||||
|
||||
- name: chamber container running
|
||||
shell: docker ps --filter name=^/althing-chamber$ --format '{{.Status}}' | grep -q '^Up'
|
||||
changed_when: "false"
|
||||
|
||||
- name: forseti container running
|
||||
shell: docker ps --filter name=^/althing-forseti$ --format '{{.Status}}' | grep -q '^Up'
|
||||
changed_when: "false"
|
||||
Reference in New Issue
Block a user