# 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 FOUR compose services: # althing-chamber — FastAPI/HTMX web UI, port 7881 host → 8000 container # althing-forseti — moderator daemon, no port # althing-agent-runner — Phase 2 worldtree-driver dispatcher, no port # althing-valkey — Phase 3.1 valkey 8 alpine, pub/sub bridge (internal only) # # The three althing services use the same image (built from vh/althing); # valkey is a stock upstream image. SQLite-backed state shares between the # three althing services via the bind-mount under /opt/docker/conf/althing-chamber/data; # streaming events ride pub/sub on the docker default network via valkey. # # 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/) 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" - name: agent-runner container running shell: docker ps --filter name=^/althing-agent-runner$ --format '{{.Status}}' | grep -q '^Up' changed_when: "false" - name: valkey container running + healthy shell: docker ps --filter name=^/althing-valkey$ --format '{{.Status}}' | grep -q 'healthy' changed_when: "false"