# Deploy skaldsong (https://gitea.phasefinal.com/vh/skaldsong) 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). # # Registry-pull pattern, NOT build-on-host: vh/skaldsong's CI builds and # pushes `gitea.phasefinal.com/vh/skaldsong:` (+ :latest) to the # gitea registry; this playbook just pulls + recreates the container. # Differs from althing-chamber / asset-engine playbooks which build the # image on-host from a git clone — those are local-build stacks. # # Idempotent: rerunning is safe. Creates-gates skip work that's already # done; `docker compose up -d` is itself idempotent (no restart unless # compose content, env, or image SHA changed). # # Usage: # # CI passes the triggering commit SHA via --var ref=${{ github.sha }}: # scripts/elway ana-docker --playbook playbooks/deploy-skaldsong.yaml \ # --var ref= # # # Manual runs can pass a branch or tag (will resolve to the registry # # tag of that name — CI publishes :main and :v0.1.0 tags alongside # # the SHA-pin): # scripts/elway ana-docker --playbook playbooks/deploy-skaldsong.yaml \ # --var ref=main # # Prereqs on the target host: # - Docker + docker compose plugin # - Target user (lkraven) is in the `docker` group # - `docker login gitea.phasefinal.com` has been done at least once # (credentials persist in /home/lkraven/.docker/config.json). CI's # workflow runs its own `docker login` step from REGISTRY_USER / # REGISTRY_TOKEN secrets; manual runs assume the host's auth is # already established. vars: registry_image: gitea.phasefinal.com/vh/skaldsong ref: latest compose_dir: /opt/docker/compose/skaldsong db_dir: /opt/docker/conf/skaldsong/db runs_dir: /opt/docker/conf/skaldsong/runs host_port: "8300" steps: # ── compose + state dirs ──────────────────────────────────────────── - name: Ensure compose dir exists shell: mkdir -p {{ compose_dir }} creates: "{{ compose_dir }}" - name: Ensure DB dir exists # Created as lkraven (uid 1000 on these hosts), matching the # container's app user — no chown dance needed. shell: mkdir -p {{ db_dir }} creates: "{{ db_dir }}" - name: Ensure runs dir exists # Separate from db_dir so runs/ can later move to a bigger volume # without touching DB state. shell: mkdir -p {{ runs_dir }} creates: "{{ runs_dir }}" # ── deploy compose files ──────────────────────────────────────────── - name: Upload compose.yaml upload: src: stacks/skaldsong/compose.yaml dest: "{{ compose_dir }}/compose.yaml" mode: "0644" - name: Seed .env from template (only if absent) upload: src: stacks/skaldsong/.env.example dest: "{{ compose_dir }}/.env" mode: "0644" when: "[ ! -f {{ compose_dir }}/.env ]" # ── pin SKALDSONG_IMAGE to the requested ref ──────────────────────── - name: Set SKALDSONG_IMAGE pin to {{ ref }} # Idempotent: strip any existing SKALDSONG_IMAGE= line and append # the new one. CI passes --var ref=; manual runs pass branch / # tag names (CI publishes those as registry tags too). shell: | sed -i '/^SKALDSONG_IMAGE=/d' {{ compose_dir }}/.env echo "SKALDSONG_IMAGE={{ registry_image }}:{{ ref }}" >> {{ compose_dir }}/.env # ── pull + bring up ───────────────────────────────────────────────── - name: Pull image {{ registry_image }}:{{ ref }} # Explicit pull step (rather than relying on compose up's pull) so # any auth failure surfaces here, not buried in compose output. shell: docker pull {{ registry_image }}:{{ ref }} - name: docker compose up -d --force-recreate # --force-recreate picks up env changes even if the image SHA is # identical to what's running (rare but happens on env-only deploys). # --pull never: we just pulled explicitly above, so no need for # compose to re-check. shell: cd {{ compose_dir }} && docker compose up -d --force-recreate --pull never - name: Wait for skaldsong /health to respond # Container's healthcheck is internal; this host-side poll confirms # the published port is reachable + the FastAPI app finished booting. # Generous retry budget — first-deploy bootstrapping (DB migrations, # SPA asset indexing) can take 30s+. 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: skaldsong /health returns 200 shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/health changed_when: "false" - name: skaldsong container running # Grep for '^Up' (not 'healthy') — verify runs immediately after the # deploy step finishes, which is well before the container's # start_period (30s) elapses. The 'healthy' state from docker ps is # a delayed echo of the same /health probe verify 1/2 already # confirms, so racing the start_period here is bogus. Keep this # check to catch the "container exited entirely" case. shell: docker ps --filter name=^/skaldsong$ --format '{{.Status}}' | grep -q '^Up' changed_when: "false"