vor: stack + playbook + workflow template for ana-docker deploy
Same shape as task-board: build-on-host from vh/vor, bind-mounted
persistence for sessions/ and responses/ (the user-published markdown
files), exposed at port 7879 (adjacent to task-board's 7878 since both
are claude-tooling sidecars).
Workflow template assumes the same DEPLOY_SSH_KEY + MGMT_REPO_TOKEN
secrets at user scope; nothing new to provision. Playbook accepts SHA
or branch refs (same fix as deploy-task-board.yaml) so manual runs
and CI runs share the same code path.
Centralized vs upstream-local: README documents the trade. Claude
fetches response markdown via /api/sessions/{id} JSON instead of a
local file read — the only API-flow change from the upstream README.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Deploy vor (Inquisitor UI, https://gitea.phasefinal.com/vh/vor) to a
|
||||
# Docker host following the PFI /opt/docker/ convention. Default
|
||||
# target is ana-docker; the playbook is host-agnostic.
|
||||
#
|
||||
# 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-vor.yaml
|
||||
# scripts/elway ana-docker --playbook playbooks/deploy-vor.yaml --var ref=v0.2.0
|
||||
#
|
||||
# Prereqs on the target host:
|
||||
# - Docker + docker compose plugin
|
||||
# - `traefik-net` docker network (external)
|
||||
# - Target user (lkraven) has git SSH access to gitea.phasefinal.com
|
||||
# - Target user is in the `docker` group.
|
||||
|
||||
vars:
|
||||
repo_url: git@gitea.phasefinal.com:vh/vor.git
|
||||
ref: main
|
||||
build_dir: /opt/docker/build/vor
|
||||
image_tag: vor:local
|
||||
compose_dir: /opt/docker/compose/vor
|
||||
conf_dir: /opt/docker/conf/vor
|
||||
host_port: "7879"
|
||||
|
||||
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 vor repo if absent
|
||||
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.2.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"
|
||||
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 }} .
|
||||
|
||||
# ── compose + persistence dirs ──────────────────────────────────────
|
||||
- name: Ensure compose dir exists
|
||||
shell: mkdir -p {{ compose_dir }}
|
||||
creates: "{{ compose_dir }}"
|
||||
|
||||
- name: Ensure sessions dir exists
|
||||
shell: mkdir -p {{ conf_dir }}/sessions
|
||||
creates: "{{ conf_dir }}/sessions"
|
||||
|
||||
- name: Ensure responses dir exists
|
||||
shell: mkdir -p {{ conf_dir }}/responses
|
||||
creates: "{{ conf_dir }}/responses"
|
||||
|
||||
# ── deploy compose files ────────────────────────────────────────────
|
||||
- name: Upload compose.yaml
|
||||
upload:
|
||||
src: stacks/vor/compose.yaml
|
||||
dest: "{{ compose_dir }}/compose.yaml"
|
||||
mode: "0644"
|
||||
|
||||
- name: Seed .env from template (only if absent)
|
||||
upload:
|
||||
src: stacks/vor/.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 /api/sessions to respond
|
||||
shell: |
|
||||
for i in $(seq 1 30); do
|
||||
curl -sf -o /dev/null http://localhost:{{ host_port }}/api/sessions && exit 0
|
||||
sleep 1
|
||||
done
|
||||
exit 1
|
||||
changed_when: "false"
|
||||
|
||||
verify:
|
||||
- name: /api/sessions returns 200
|
||||
shell: curl -sf -o /dev/null http://localhost:{{ host_port }}/api/sessions
|
||||
changed_when: "false"
|
||||
|
||||
- name: / returns the UI shell
|
||||
shell: curl -sf http://localhost:{{ host_port }}/ | grep -q -i 'inquisitor\|vor'
|
||||
changed_when: "false"
|
||||
|
||||
- name: Container is in the traefik-net network
|
||||
shell: docker inspect vor --format '{{json .NetworkSettings.Networks}}' | grep -q traefik-net
|
||||
changed_when: "false"
|
||||
@@ -0,0 +1,13 @@
|
||||
# Built locally from vh/vor by the deploy playbook; not pulled from a registry.
|
||||
VOR_IMAGE=vor:local
|
||||
|
||||
# Host port. Adjacent to task-board's 7878 since both are claude-tooling
|
||||
# sidecars; keep them grouped for muscle memory.
|
||||
VOR_PORT=7879
|
||||
VOR_BIND=0.0.0.0
|
||||
|
||||
# Persistence — sessions/ holds Claude's posted markdown,
|
||||
# responses/ holds the user's published response markdown. Both are
|
||||
# bind-mounted so they survive `docker compose up --force-recreate`.
|
||||
VOR_SESSIONS_DIR=/opt/docker/conf/vor/sessions
|
||||
VOR_RESPONSES_DIR=/opt/docker/conf/vor/responses
|
||||
@@ -0,0 +1,61 @@
|
||||
# vor
|
||||
|
||||
[Inquisitor UI](https://gitea.phasefinal.com/vh/vor) deployed centrally.
|
||||
A FastAPI sidecar that renders Claude's `/inquisitor` markdown as a
|
||||
fillable form — Claude POSTs sessions, user fills + publishes via
|
||||
browser, Claude reads the response back.
|
||||
|
||||
**Server:** ana-docker
|
||||
**Port:** 7879 (configurable via `.env`)
|
||||
**Upstream repo:** [vh/vor](https://gitea.phasefinal.com/vh/vor)
|
||||
**Image:** `vor:local` — built on the host from the git repo by the
|
||||
deploy playbook. Not pulled from a registry.
|
||||
|
||||
## Centralized vs local
|
||||
|
||||
The upstream README describes a **local** deployment pattern (Claude
|
||||
on the workstation posts to `127.0.0.1:8765`, response file lives on
|
||||
the local filesystem). Running vor on ana-docker changes that:
|
||||
|
||||
- Claude POSTs to `http://10.250.50.70:7879/api/sessions`
|
||||
- The browser opens `http://10.250.50.70:7879/?session=<id>`
|
||||
- The response markdown is **not** written to a path Claude can read
|
||||
off its own disk; Claude reads it back via
|
||||
`GET /api/sessions/{id}` (`response_markdown` field) instead.
|
||||
|
||||
Trade: central + always-on + multi-device (phone, tablet, second
|
||||
workstation) at the cost of a network round-trip and an HTTP-fetch
|
||||
step in Claude's flow instead of a file read.
|
||||
|
||||
## Deploy
|
||||
|
||||
Two paths — automated (preferred) and manual.
|
||||
|
||||
### Automated (Gitea Actions, push-to-main)
|
||||
|
||||
Every push to `main` on `vh/vor` triggers a deploy via the central
|
||||
gitea-runner. Workflow template lives next to this README at
|
||||
[`gitea-workflow-deploy.yaml.example`](gitea-workflow-deploy.yaml.example);
|
||||
copy into the vor repo at `.gitea/workflows/deploy.yaml` (already
|
||||
done on first wiring). Reuses the `DEPLOY_SSH_KEY` and
|
||||
`MGMT_REPO_TOKEN` secrets at user scope; no per-repo setup needed
|
||||
once those exist.
|
||||
|
||||
### Manual (elway from a workstation)
|
||||
|
||||
```bash
|
||||
# First deploy / update to latest main
|
||||
scripts/elway ana-docker --playbook playbooks/deploy-vor.yaml
|
||||
|
||||
# Pin to a specific ref
|
||||
scripts/elway ana-docker --playbook playbooks/deploy-vor.yaml --var ref=v0.2.0
|
||||
```
|
||||
|
||||
## Path layout (on ana-docker)
|
||||
|
||||
| Host path | Container path | Purpose | Restic? |
|
||||
|---|---|---|---|
|
||||
| `/opt/docker/build/vor/` | — | git checkout used as docker build context | excluded |
|
||||
| `/opt/docker/compose/vor/` | — | compose.yaml + .env | included (via `/opt/docker`) |
|
||||
| `/opt/docker/conf/vor/sessions/` | `/app/sessions` | Claude-posted markdown | **included** |
|
||||
| `/opt/docker/conf/vor/responses/` | `/app/responses` | user-published response markdown | **included** |
|
||||
@@ -0,0 +1,45 @@
|
||||
# vor — Inquisitor UI sidecar.
|
||||
#
|
||||
# Image is built on the host from the vor git repo by the deploy
|
||||
# playbook (`playbooks/deploy-vor.yaml`), which clones into
|
||||
# /opt/docker/build/vor and runs `docker build -t vor:local .` before
|
||||
# installing this compose and bringing it up.
|
||||
#
|
||||
# Sessions + responses persist under bind mounts so container rebuilds
|
||||
# don't lose user-published response files (which Claude reads via the
|
||||
# /api/sessions/{id} JSON path).
|
||||
#
|
||||
# All tunables live in .env — edit that, not this file.
|
||||
|
||||
services:
|
||||
vor:
|
||||
image: ${VOR_IMAGE}
|
||||
container_name: vor
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${VOR_BIND:-0.0.0.0}:${VOR_PORT}:8765"
|
||||
volumes:
|
||||
- ${VOR_SESSIONS_DIR}:/app/sessions
|
||||
- ${VOR_RESPONSES_DIR}:/app/responses
|
||||
environment:
|
||||
# Container always listens on 8765 internally; host port is the only knob.
|
||||
- PYTHONUNBUFFERED=1
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "python -c 'import urllib.request,sys; r=urllib.request.urlopen(\"http://127.0.0.1:8765/api/sessions\",timeout=3); sys.exit(0 if r.status==200 else 1)' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- tnet
|
||||
labels:
|
||||
- homepage.group=Toolchain
|
||||
- homepage.name=vor
|
||||
- homepage.icon=mdi-clipboard-text-search
|
||||
- homepage.description=Inquisitor UI — Claude session ↔ user response sidecar
|
||||
- homepage.href=http://10.250.50.70:${VOR_PORT}
|
||||
|
||||
networks:
|
||||
tnet:
|
||||
name: traefik-net
|
||||
external: true
|
||||
@@ -0,0 +1,65 @@
|
||||
# Gitea Actions workflow for vor.
|
||||
#
|
||||
# THIS FILE LIVES IN THE VOR REPO, NOT HERE.
|
||||
# Copy to vh/vor:.gitea/workflows/deploy.yaml and commit.
|
||||
#
|
||||
# Identical pattern to stacks/task-board/gitea-workflow-deploy.yaml.example
|
||||
# — checks out vor + eshpfi-management, then runs the elway playbook
|
||||
# pinned to the triggering commit SHA. The playbook is the single
|
||||
# source of truth for "how vor is deployed", manual or automated.
|
||||
#
|
||||
# Required Actions secrets (already set at user scope from earlier
|
||||
# task-board wiring; nothing new needed):
|
||||
#
|
||||
# DEPLOY_SSH_KEY Private SSH key authorized for lkraven@ana-docker.
|
||||
# MGMT_REPO_TOKEN Gitea PAT (read:repository) on vh/esh-pfi-infrastructure.
|
||||
|
||||
name: Deploy vor
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: pfi-fleet
|
||||
|
||||
steps:
|
||||
- name: Install playbook prerequisites
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 python3-yaml openssh-client
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- name: Checkout vor (triggering repo)
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout management repo (eshpfi-management)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: vh/esh-pfi-infrastructure
|
||||
token: ${{ secrets.MGMT_REPO_TOKEN }}
|
||||
path: _mgmt
|
||||
|
||||
- name: Configure SSH to ana-docker
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
cat > ~/.ssh/config <<'EOF'
|
||||
Host ana-docker
|
||||
HostName 10.250.50.70
|
||||
User lkraven
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
StrictHostKeyChecking accept-new
|
||||
EOF
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
- name: Deploy vor (elway playbook, pinned to this commit)
|
||||
working-directory: _mgmt
|
||||
run: |
|
||||
scripts/elway ana-docker \
|
||||
--playbook playbooks/deploy-vor.yaml \
|
||||
--var ref=${{ github.sha }}
|
||||
Reference in New Issue
Block a user