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"
|
||||
@@ -0,0 +1,27 @@
|
||||
# althing-chamber stack tunables. Copy to `.env` on ana-docker before deploying.
|
||||
#
|
||||
# The deploy playbook seeds `.env` from this template on first run only —
|
||||
# it won't clobber an existing `.env`.
|
||||
|
||||
# Image tag. Built locally from the vh/althing git repo by the playbook.
|
||||
ALTHING_IMAGE=althing-chamber:local
|
||||
|
||||
# Host port exposing the chamber UI (container always listens on 8000
|
||||
# internally). Internal-only — no Traefik.
|
||||
# Chamber's compiled-in default port is 7878 but that collides with
|
||||
# task-board's 7878 on the same host. 7881 is the canonical fleet slot,
|
||||
# adjacent to task-board:7878 and vor:7879.
|
||||
ALTHING_PORT=7881
|
||||
|
||||
# Bind address for the host port. 0.0.0.0 = LAN-reachable (default for an
|
||||
# internal-only tool).
|
||||
ALTHING_BIND=0.0.0.0
|
||||
|
||||
# Host path for SQLite + state. Container runs as uid 1000 (matches
|
||||
# lkraven on these hosts) so the playbook's mkdir without sudo produces
|
||||
# a writable dir.
|
||||
#
|
||||
# This single dir is the only persistent state — althing's SQLite DB,
|
||||
# read_cursors, notification_state, hand_queue, floor_grants all live
|
||||
# here. Restic backs it up via the standard /opt/docker tree.
|
||||
ALTHING_DATA_DIR=/opt/docker/conf/althing-chamber/data
|
||||
@@ -0,0 +1,96 @@
|
||||
# althing-chamber
|
||||
|
||||
Web UI + moderator daemon for the althing inter-agent message bus.
|
||||
Two services share a single SQLite store via a bind-mount; the chamber
|
||||
serves FastAPI/HTMX, the forseti daemon runs the moderation + curation
|
||||
loops. Cross-process glue is `eventbus.bridge_from_db` polling the same
|
||||
DB both processes write.
|
||||
|
||||
**Server:** ana-docker
|
||||
**URL:** `http://10.250.50.70:7881` (configurable via `.env`)
|
||||
**Upstream repo:** [vh/althing](https://gitea.phasefinal.com/vh/althing)
|
||||
**Image:** `althing-chamber:local` — built on the host from the git repo
|
||||
by the deploy playbook. Not pulled from a registry.
|
||||
|
||||
## Services in this stack
|
||||
|
||||
| Container | Role | Port | Healthcheck |
|
||||
|---|---|---|---|
|
||||
| `althing-chamber` | FastAPI/HTMX web UI; SSE subscribers; `/health` endpoint | host 7881 → container 8000 | `python urllib /health` |
|
||||
| `althing-forseti` | Moderator + curator daemon; writes events that chamber's bridge picks up | — (no HTTP) | none (process-up signal only) |
|
||||
|
||||
Both use the same `${ALTHING_IMAGE}`; the `command:` line in compose picks
|
||||
which entrypoint (`althing-chamber` vs `althing-forseti`) runs in each
|
||||
container.
|
||||
|
||||
## Deploy
|
||||
|
||||
Two paths — automated (preferred) and manual (escape hatch / first-time).
|
||||
|
||||
### Automated (Gitea Actions, push-to-main)
|
||||
|
||||
The vh/althing repo ships `.gitea/workflows/deploy.yaml`. Every push to
|
||||
main + manual `workflow_dispatch` triggers the elway playbook below
|
||||
pinned to the triggering commit SHA. A reference copy of the workflow
|
||||
lives next to this README at
|
||||
[`gitea-workflow-deploy.yaml.example`](gitea-workflow-deploy.yaml.example);
|
||||
the canonical source is in the vh/althing repo. The example header
|
||||
lists the two repo secrets required (`DEPLOY_SSH_KEY`,
|
||||
`MGMT_REPO_TOKEN`).
|
||||
|
||||
### Manual (elway from a workstation)
|
||||
|
||||
```bash
|
||||
# First deploy (or update to latest main)
|
||||
scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml
|
||||
|
||||
# Pin to a specific ref (tag, branch, or commit SHA)
|
||||
scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml --var ref=v0.1.0
|
||||
```
|
||||
|
||||
## Path layout (on ana-docker)
|
||||
|
||||
| Host path | Container path | Purpose | Restic? |
|
||||
|---|---|---|---|
|
||||
| `/opt/docker/build/althing-chamber/` | — | git checkout used as docker build context | excluded |
|
||||
| `/opt/docker/compose/althing-chamber/` | — | compose.yaml + .env | included (via `/opt/docker`) |
|
||||
| `/opt/docker/conf/althing-chamber/data/` | `/app/data` | SQLite + state for BOTH services | **included** |
|
||||
|
||||
## Network model
|
||||
|
||||
Internal tooling, LAN-only. Chamber's container port 8000 is published
|
||||
on the host at `0.0.0.0:7881` (configurable via `ALTHING_BIND` /
|
||||
`ALTHING_PORT`); access is direct via `http://10.250.50.70:7881`. No
|
||||
Traefik, no TLS terminator, no public hostname.
|
||||
|
||||
Forseti has no port — it's a daemon. The two services communicate only
|
||||
via the shared SQLite file under the `${ALTHING_DATA_DIR}` bind-mount;
|
||||
no docker network coupling beyond compose's default bridge.
|
||||
|
||||
## Env-var contract
|
||||
|
||||
| Var | In env? | In `~/.althing/config.yaml`? | Notes |
|
||||
|---|---|---|---|
|
||||
| `ALTHING_ROOT` | ✓ (set in compose) | n/a | Overrides the default `~/.althing/` dir. Container sets `/app/data`. |
|
||||
| `ALTHING_DB` | ✓ (set in compose) | n/a | Explicit SQLite path. Defaults to `${ALTHING_ROOT}/althing.db`. |
|
||||
| `ALTHING_BIND` | ✓ (deploy-time) | also accepted | Bind address for the chamber HTTP server. Container always sets 0.0.0.0 internally. |
|
||||
| `ALTHING_PORT` | ✓ (deploy-time) | also accepted | Chamber listens here internally (always 8000 inside the container). |
|
||||
|
||||
Env-var support for `ALTHING_BIND` / `ALTHING_PORT` was added on the
|
||||
galdrabok side as part of the container-deploy cycle (env > config.yaml
|
||||
> defaults precedence).
|
||||
|
||||
## First-deploy sequence (when galdrabok's Dockerfile lands)
|
||||
|
||||
1. Generate a deploy keypair on ana-docker (private stays on host;
|
||||
pubkey lands in `~lkraven/.ssh/authorized_keys`).
|
||||
2. Wire two secrets in `vh/althing` Actions settings:
|
||||
- `DEPLOY_SSH_KEY` — the private key from step 1.
|
||||
- `MGMT_REPO_TOKEN` — Gitea PAT with `read:repository` on this repo,
|
||||
used by the workflow to clone the management repo for the playbook.
|
||||
3. Push to vh/althing's main branch (or trigger `workflow_dispatch`);
|
||||
the Actions runner clones both repos, configures SSH, runs
|
||||
`scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml --var ref=$SHA`.
|
||||
4. Playbook: clones into `/opt/docker/build/althing-chamber`, builds
|
||||
the image, uploads compose + .env (one-time seed), brings both
|
||||
services up, polls `/health` until 200.
|
||||
@@ -0,0 +1,76 @@
|
||||
# althing-chamber + forseti — web UI + moderator daemon for the althing
|
||||
# inter-agent message bus.
|
||||
#
|
||||
# Two services share the althing SQLite store via a single bind-mount:
|
||||
# althing-chamber — FastAPI/HTMX app (port 7881 host → 8000 container)
|
||||
# althing-forseti — moderator daemon (no port; cross-process glue via the DB)
|
||||
#
|
||||
# eventbus.bridge_from_db is load-bearing — forseti's DB commits reach
|
||||
# chamber's SSE subscribers via the bridge, not via shared memory or IPC.
|
||||
# Both processes read+write the same `~/.althing/althing.db` SQLite file
|
||||
# at /app/data/althing.db inside the container, backed by the host's
|
||||
# /opt/docker/conf/althing-chamber/data bind-mount.
|
||||
#
|
||||
# Image is built on the host from the vh/althing git repo by the deploy
|
||||
# playbook (`playbooks/deploy-althing-chamber.yaml`), which clones into
|
||||
# /opt/docker/build/althing-chamber and runs `docker build -t
|
||||
# althing-chamber:local .` before installing this compose and bringing
|
||||
# both services up. No registry.
|
||||
#
|
||||
# Internal tooling — accessed directly at http://10.250.50.70:7881 over
|
||||
# the LAN; does NOT traverse Traefik. State persists under
|
||||
# /opt/docker/conf/althing-chamber/data on the host.
|
||||
#
|
||||
# All tunables live in .env — edit that, not this file.
|
||||
|
||||
services:
|
||||
althing-chamber:
|
||||
image: ${ALTHING_IMAGE}
|
||||
container_name: althing-chamber
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${ALTHING_BIND:-0.0.0.0}:${ALTHING_PORT}:8000"
|
||||
environment:
|
||||
# ALTHING_ROOT moves the entire ~/.althing dir; ALTHING_DB additionally
|
||||
# pins the SQLite path explicitly. Both point inside the bind-mount.
|
||||
- ALTHING_ROOT=/app/data
|
||||
- ALTHING_DB=/app/data/althing.db
|
||||
# ALTHING_BIND / ALTHING_PORT — galdrabok-side env-var precedence
|
||||
# (env > config.yaml > defaults) is being added in the same cycle
|
||||
# as this scaffold lands. Container always listens on 8000 internally;
|
||||
# the host port mapping above is the only externally-visible knob.
|
||||
- ALTHING_BIND=0.0.0.0
|
||||
- ALTHING_PORT=8000
|
||||
volumes:
|
||||
- ${ALTHING_DATA_DIR}:/app/data
|
||||
command: ["althing-chamber"]
|
||||
healthcheck:
|
||||
# Liveness probe — chamber's /health endpoint returns 200 with no DB
|
||||
# read (true liveness, not readiness). galdrabok adds this endpoint
|
||||
# in the same cycle as this scaffold; container will crashloop on
|
||||
# healthcheck until that lands.
|
||||
test: ["CMD-SHELL", "python -c 'import urllib.request,sys; r=urllib.request.urlopen(\"http://127.0.0.1:8000/health\",timeout=3); sys.exit(0 if r.status==200 else 1)' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
labels:
|
||||
- homepage.group=Toolchain
|
||||
- homepage.name=althing chamber
|
||||
- homepage.icon=mdi-bullhorn
|
||||
- homepage.description=Web UI for the althing inter-agent message bus
|
||||
- homepage.href=http://10.250.50.70:${ALTHING_PORT}
|
||||
|
||||
althing-forseti:
|
||||
image: ${ALTHING_IMAGE}
|
||||
container_name: althing-forseti
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ALTHING_ROOT=/app/data
|
||||
- ALTHING_DB=/app/data/althing.db
|
||||
volumes:
|
||||
- ${ALTHING_DATA_DIR}:/app/data
|
||||
command: ["althing-forseti"]
|
||||
# No healthcheck — the forseti CLI doesn't expose one. Liveness signal
|
||||
# for ops is "container hasn't exited" + chamber-side observation that
|
||||
# bridge_from_db events are flowing.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Gitea Actions workflow for althing-chamber.
|
||||
#
|
||||
# THIS FILE LIVES IN THE VH/ALTHING REPO, NOT HERE.
|
||||
# Copy to vh/althing:.gitea/workflows/deploy.yaml and commit.
|
||||
# (The canonical copy lives in the althing repo; this file is a
|
||||
# reference for what shape the workflow takes.)
|
||||
#
|
||||
# What it does on every push to main (and on manual workflow_dispatch):
|
||||
# 1. Checks out althing itself (the triggering repo).
|
||||
# 2. Checks out vh/esh-pfi-infrastructure to pick up the elway
|
||||
# playbook and helper scripts.
|
||||
# 3. Configures SSH so elway can reach ana-docker.
|
||||
# 4. Runs `scripts/elway ana-docker --playbook playbooks/deploy-althing-chamber.yaml`
|
||||
# pinning to the commit SHA that triggered the workflow.
|
||||
#
|
||||
# Required Actions secrets (configure under
|
||||
# https://gitea.phasefinal.com/vh/althing/settings/actions/secrets,
|
||||
# or org-level for reuse across repos):
|
||||
#
|
||||
# DEPLOY_SSH_KEY Private SSH key whose pubkey is in
|
||||
# ~lkraven/.ssh/authorized_keys on ana-docker.
|
||||
# Used by the runner to invoke the elway playbook.
|
||||
# Generate fresh; don't reuse a personal key.
|
||||
#
|
||||
# MGMT_REPO_TOKEN Gitea PAT (read:repository scope) on
|
||||
# vh/esh-pfi-infrastructure, used to clone the
|
||||
# management repo. Generate at
|
||||
# https://gitea.phasefinal.com/-/user/settings/applications.
|
||||
|
||||
name: Deploy althing-chamber
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
# `pfi-fleet` matches the central runner on ana-docker. Pin to
|
||||
# `ana-docker` instead if you want to refuse running on a future
|
||||
# site-local runner. The runner's label embeds a default image
|
||||
# (node:20-bookworm-slim) — has node + git out of the box, so
|
||||
# actions/checkout@v4 (a JS action) works without a custom
|
||||
# container. We just apt-install python3 + pyyaml for elway.
|
||||
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 althing (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
|
||||
# The DEPLOY_SSH_KEY secret is the full private key contents,
|
||||
# newline-terminated. ssh refuses keys that aren't 0600.
|
||||
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
# ssh_config alias so elway resolves "ana-docker" the same
|
||||
# way it would on a workstation. accept-new is fine for a
|
||||
# fresh job container — host key gets cached for the lifetime
|
||||
# of this job only.
|
||||
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 althing-chamber (elway playbook, pinned to this commit)
|
||||
working-directory: _mgmt
|
||||
run: |
|
||||
scripts/elway ana-docker \
|
||||
--playbook playbooks/deploy-althing-chamber.yaml \
|
||||
--var ref=${{ github.sha }}
|
||||
Reference in New Issue
Block a user