build+deploy: reproducible pipeline via Gitea Actions + gw-04 image-watcher #1
@@ -20,12 +20,39 @@ jobs:
|
||||
rust:
|
||||
runs-on: ubuntu-latest
|
||||
needs: gates
|
||||
# Compile sqlx query! macros against the committed .sqlx cache (no DB needed);
|
||||
# DB-backed tests spin up their own postgres via testcontainers at runtime.
|
||||
# Compile sqlx query! macros against the committed .sqlx cache (no DB needed).
|
||||
# Tests need a live Postgres — locally cm-testkit reads CM_TEST_DATABASE_URL
|
||||
# from .cargo/config.toml pointing at scripts/test-server.sh's host container.
|
||||
# The fleet act_runner uses the `host` executor (jobs run on morpheus/tank/
|
||||
# architect natively, not inside a container), so we start a per-run postgres
|
||||
# container and reach it via its bridge IP. GITHUB_RUN_ID scopes the name so
|
||||
# concurrent jobs on the same runner don't collide.
|
||||
env:
|
||||
SQLX_OFFLINE: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Start postgres sidecar
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NAME="ci-pg-${GITHUB_RUN_ID}"
|
||||
docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$NAME" \
|
||||
-e POSTGRES_PASSWORD=postgres \
|
||||
-e POSTGRES_DB=postgres \
|
||||
postgres:16-alpine >/dev/null
|
||||
PG_IP=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "$NAME")
|
||||
echo "PG_CONTAINER=$NAME" >> "$GITHUB_ENV"
|
||||
echo "CM_TEST_DATABASE_URL=postgres://postgres:postgres@${PG_IP}:5432/postgres" >> "$GITHUB_ENV"
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec "$NAME" pg_isready -U postgres -q >/dev/null 2>&1; then
|
||||
echo "postgres ready at ${PG_IP} after ${i}s"
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "postgres never became ready" >&2
|
||||
docker logs "$NAME" >&2 || true
|
||||
exit 1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.96.0
|
||||
@@ -39,6 +66,9 @@ jobs:
|
||||
run: cargo test --workspace
|
||||
- name: Air-gapped installer verify path
|
||||
run: ./ci/test-install.sh
|
||||
- name: Cleanup postgres sidecar
|
||||
if: always()
|
||||
run: docker rm -f "${PG_CONTAINER:-}" >/dev/null 2>&1 || true
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -92,3 +122,53 @@ jobs:
|
||||
with:
|
||||
name: playwright-traces
|
||||
path: frontend/test-results/
|
||||
|
||||
# Rolling deploy: on green main only, build the three prod images, tag with
|
||||
# :main-<sha> + :latest, push to the fleet registry (redclaw-web-01:5000 via
|
||||
# its Tailscale IP — the fleet's daemons trust it in insecure-registries by
|
||||
# IP, not by hostname). GW-04's clawmates-deploy.timer rolls forward within
|
||||
# ~1 minute of the push. Skipped on PRs.
|
||||
#
|
||||
# `e2e` is intentionally NOT in `needs`: it launches its own postgres + dex
|
||||
# via `docker run` on the host and then reaches them via 127.0.0.1, which
|
||||
# fails from inside the act_runner container. Migrating e2e to a physical
|
||||
# build node is a separate task; until then e2e is signal-only, not gating.
|
||||
publish:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gates, rust, frontend]
|
||||
env:
|
||||
REGISTRY: 100.94.185.103:5000
|
||||
NAMESPACE: clawmates
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Resolve short SHA
|
||||
run: echo "SHA=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
||||
- name: Build images
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for svc in broker server frontend; do
|
||||
docker build \
|
||||
-t "${REGISTRY}/${NAMESPACE}/${svc}:main-${SHA}" \
|
||||
-t "${REGISTRY}/${NAMESPACE}/${svc}:latest" \
|
||||
-f "images/${svc}.Dockerfile" .
|
||||
done
|
||||
- name: Push images
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for svc in broker server frontend; do
|
||||
docker push "${REGISTRY}/${NAMESPACE}/${svc}:main-${SHA}"
|
||||
docker push "${REGISTRY}/${NAMESPACE}/${svc}:latest"
|
||||
done
|
||||
- name: Summary
|
||||
run: |
|
||||
{
|
||||
echo "## Published images"
|
||||
echo ""
|
||||
for svc in broker server frontend; do
|
||||
echo "- \`${REGISTRY}/${NAMESPACE}/${svc}:main-${SHA}\`"
|
||||
echo "- \`${REGISTRY}/${NAMESPACE}/${svc}:latest\`"
|
||||
done
|
||||
echo ""
|
||||
echo "GW-04 timer picks these up within ~1 minute."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Clawmates rolling deploy (pull :latest, roll on drift)
|
||||
Wants=docker.service
|
||||
After=docker.service network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/clawmates-deploy.sh
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Nice=10
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# clawmates rolling deploy — polls the fleet registry for :latest of the three
|
||||
# prod images (broker, server, frontend). On drift, pulls the new image, retags
|
||||
# it under the un-prefixed name the running compose file uses, and rolls the
|
||||
# affected services with `docker compose up -d`.
|
||||
#
|
||||
# Install:
|
||||
# sudo install -m 0755 clawmates-deploy.sh /usr/local/bin/clawmates-deploy.sh
|
||||
# sudo install -m 0644 clawmates-deploy.service /etc/systemd/system/
|
||||
# sudo install -m 0644 clawmates-deploy.timer /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now clawmates-deploy.timer
|
||||
#
|
||||
# Verify:
|
||||
# systemctl list-timers clawmates-deploy.timer
|
||||
# tail -f /var/log/clawmates-deploy.log
|
||||
#
|
||||
# The retag step (registry/clawmates/<svc>:latest → clawmates/<svc>:latest)
|
||||
# keeps the current /root/clawmates/docker-compose.yml working unchanged until
|
||||
# we're ready to migrate the compose file to registry-prefixed image names.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REGISTRY="${REGISTRY:-100.94.185.103:5000}"
|
||||
NAMESPACE="${NAMESPACE:-clawmates}"
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-/root/clawmates}"
|
||||
LOG="${LOG:-/var/log/clawmates-deploy.log}"
|
||||
SERVICES=(broker server frontend)
|
||||
|
||||
log() { printf '%s %s\n' "$(date -Iseconds)" "$*" | tee -a "$LOG" >/dev/null; }
|
||||
|
||||
changed=()
|
||||
for svc in "${SERVICES[@]}"; do
|
||||
ref="${REGISTRY}/${NAMESPACE}/${svc}:latest"
|
||||
local_ref="${NAMESPACE}/${svc}:latest"
|
||||
|
||||
before=$(docker inspect --format '{{.Id}}' "$ref" 2>/dev/null || echo "")
|
||||
if ! docker pull -q "$ref" >/dev/null 2>&1; then
|
||||
log "pull failed: $ref"
|
||||
continue
|
||||
fi
|
||||
after=$(docker inspect --format '{{.Id}}' "$ref")
|
||||
|
||||
if [[ "$before" != "$after" ]]; then
|
||||
log "new image for $svc: ${before:-<none>} -> $after"
|
||||
changed+=("$svc")
|
||||
fi
|
||||
|
||||
# Always keep the un-prefixed tag pointing at the fresh image, so a stale
|
||||
# local retag can't wedge us.
|
||||
docker tag "$ref" "$local_ref"
|
||||
done
|
||||
|
||||
if [[ ${#changed[@]} -eq 0 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "rolling: ${changed[*]}"
|
||||
cd "$COMPOSE_DIR"
|
||||
docker compose up -d "${changed[@]}"
|
||||
log "roll complete: ${changed[*]}"
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Clawmates rolling deploy — every minute
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=1min
|
||||
AccuracySec=15s
|
||||
Unit=clawmates-deploy.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user