Files
esh-pfi-infrastructure/playbooks/deploy-asset-engine.yaml
T
vh 079c7b15e3 asset-engine + irv-ml1: scaffold orchestrator wiring
Three changes prepping infra for asset_engine's orchestration feature
(SSH-driven bring-up / bring-down of irv-ml1 inference services with
per-device VRAM gating, contract in vh/asset-engine commit 5a36f8c):

1. asset-engine compose + .env.example + playbook gain a read-only
   bind-mount for /app/runtime/ssh — the dedicated ed25519 keypair
   (generated on ana-docker, not in the repo) plus a pinned known_hosts
   for irv-ml1's host fingerprint. Env vars SSH_KEY_PATH and
   SSH_KNOWN_HOSTS are exposed for the app to consume.

2. docs/asset-engine/services.yaml gains a `lifecycle: { stack, vram_gb,
   gpu_device_id }` block on each of 12 orchestratable irv-ml1 services
   (kokoro, chatterbox, index-tts, qwen3-tts, cosyvoice, fish-s2,
   kyutai-tts, vibevoice, voxtral, parakeet, stable-audio-open, ace-step).
   VRAM numbers are estimates from model footprint at fp16 — tune from
   real nvidia-smi measurements once the gate is live. comfyui and
   kokoro-captioned are deliberately excluded (variable-VRAM and
   shared-container respectively).

3. servers/irv-ml1/README.md docker-stacks table now lists all 13
   inference stacks (was only dockge + agents + comfyui) with port +
   GPU pinning columns.

Pubkey deployed to ~lkraven/.ssh/authorized_keys on irv-ml1;
end-to-end SSH from ana-docker → irv-ml1 verified with strict
host-key checking.
2026-05-12 13:24:07 -07:00

136 lines
6.1 KiB
YAML

# Deploy asset-engine (https://gitea.phasefinal.com/vh/asset-engine) to a
# Docker host following the PFI /opt/docker/ convention (ana-docker by
# default, but the playbook works against any host with Docker +
# traefik-net in place).
#
# Idempotent: rerunning is safe. Creates-gates + conditional when:
# checks 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-asset-engine.yaml
# scripts/elway ana-docker --playbook playbooks/deploy-asset-engine.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/asset-engine.git
ref: main
build_dir: /opt/docker/build/asset-engine
image_tag: asset-engine:local
compose_dir: /opt/docker/compose/asset-engine
db_dir: /opt/docker/conf/asset-engine/db
outputs_dir: /opt/docker/conf/asset-engine/outputs
ssh_dir: /opt/docker/conf/asset-engine/ssh
host_port: "8200"
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 asset-engine 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 + 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 outputs dir exists
# Separate from db_dir so outputs/ can later move to a bigger
# volume without touching DB state.
shell: mkdir -p {{ outputs_dir }}
creates: "{{ outputs_dir }}"
- name: Ensure SSH key dir exists (parent only — keys themselves are seeded
out-of-band on the host so the private key never crosses the network)
shell: mkdir -p {{ ssh_dir }} && chmod 700 {{ ssh_dir }}
creates: "{{ ssh_dir }}"
# ── deploy compose files ────────────────────────────────────────────
- name: Upload compose.yaml
upload:
src: stacks/asset-engine/compose.yaml
dest: "{{ compose_dir }}/compose.yaml"
mode: "0644"
- name: Seed .env from template (only if absent)
upload:
src: stacks/asset-engine/.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 /health to respond
# Short retry loop — docker compose up returns before healthcheck
# stabilizes; we want verify: to run against a live server.
shell: |
for i in $(seq 1 30); do
curl -sf -o /dev/null http://localhost:{{ host_port }}/health && exit 0
sleep 1
done
exit 1
changed_when: "false"
verify:
- name: /health returns 200
shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/health
changed_when: "false"
- name: /health body reports status=ok
shell: curl -sf http://localhost:{{ host_port }}/health | grep -q '"status"[[:space:]]*:[[:space:]]*"ok"'
changed_when: "false"