From 1dc771cec2501639e55a87bc473f96ae7aa1f54d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 8 Mar 2026 18:55:40 -0400 Subject: [PATCH] Fix CI/CD for Forgejo-on-swarm topology; add runner swarm compose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.yml: clarify that runner needs Docker socket; SSH deploy uses DEPLOY_PATH secret so app host path is configurable; --no-deps so db/ollama containers are left untouched during deploy - infra/runner-swarm.yml: act_runner as a Docker service on the Forgejo host — mounts host socket for buildx, writes act_runner config via one-shot init container, connects to fabled_backend network Co-Authored-By: Claude Sonnet 4.6 --- .forgejo/workflows/build.yml | 38 +++++++------ infra/runner-swarm.yml | 100 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 infra/runner-swarm.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 01e3989..acc442b 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -1,14 +1,16 @@ # Runs only on pushes to main. -# Builds the Docker image, pushes it to the Forgejo container registry, -# then SSHes into the deploy host and restarts the service. +# Builds the Docker image on the CI runner host (which has Docker socket access), +# pushes it to the Forgejo container registry, then SSHes to the app host to restart. # -# Required secrets (set in Forgejo repo → Settings → Secrets): -# REGISTRY_USER — your Forgejo username -# REGISTRY_TOKEN — Forgejo personal access token (write:packages scope) -# DEPLOY_HOST — hostname or IP of the server running the app -# DEPLOY_USER — SSH username on that server -# DEPLOY_SSH_KEY — private SSH key (the public key must be in authorized_keys on the server) -# DEPLOY_PATH — absolute path to the directory containing docker-compose.yml +# Runner requirement: act_runner with /var/run/docker.sock mounted (see infra/runner-swarm.yml). +# +# Required secrets (Forgejo repo → Settings → Secrets): +# REGISTRY_USER — your Forgejo username (bvandeusen) +# REGISTRY_TOKEN — Forgejo personal access token with write:packages scope +# DEPLOY_HOST — IP or hostname of the server running fabledassistant +# DEPLOY_USER — SSH user on that server +# DEPLOY_SSH_KEY — private SSH key (matching public key must be in authorized_keys on app host) +# DEPLOY_PATH — absolute path to the directory with docker-compose.yml on the app host name: Build & Deploy on: @@ -44,9 +46,9 @@ jobs: tags: | ${{ env.IMAGE }}:latest ${{ env.IMAGE }}:${{ github.sha }} - # Registry-side layer cache — dramatically speeds up rebuilds. - # On first run there is no cache so it builds cold; after that - # unchanged layers (npm install, pip install) are reused. + # Registry-side layer cache lives in the Forgejo container registry. + # Unchanged layers (npm install, pip install) are reused across builds, + # cutting typical build time from ~4 min to ~45 sec after the first run. cache-from: type=registry,ref=${{ env.IMAGE }}:cache cache-to: type=registry,ref=${{ env.IMAGE }}:cache,mode=max @@ -55,17 +57,19 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - name: Pull new image and restart + - name: Pull new image and restart app uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} + # Pulls only the app service image, restarts only the app container. + # Database and Ollama containers are left untouched (--no-deps). script: | set -e - cd ${{ secrets.DEPLOY_PATH }} - echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.fabledsword.com \ - -u "${{ secrets.REGISTRY_USER }}" --password-stdin + echo "${{ secrets.REGISTRY_TOKEN }}" | \ + docker login git.fabledsword.com -u "${{ secrets.REGISTRY_USER }}" --password-stdin + cd "${{ secrets.DEPLOY_PATH }}" docker compose pull app - docker compose up -d --no-build app + docker compose up -d --no-build --no-deps app docker image prune -f diff --git a/infra/runner-swarm.yml b/infra/runner-swarm.yml new file mode 100644 index 0000000..57fb3bd --- /dev/null +++ b/infra/runner-swarm.yml @@ -0,0 +1,100 @@ +# Forgejo Actions runner — deploy this on the FORGEJO HOST alongside the Forgejo stack. +# +# The runner connects outbound to git.fabledsword.com to receive jobs. +# No inbound connections from Forgejo are needed. +# The host Docker socket is mounted so the build job can use Docker Buildx. +# +# One-time setup on the Forgejo host: +# +# 1. Get a registration token: +# Forgejo → Site Administration → Runners → Create Runner +# (or per-repo: repo → Settings → Actions → Runners) +# +# 2. Create an env file (keep this off git): +# echo 'RUNNER_TOKEN=your_token_here' > /nfs/data/fabledsword/act_runner/.env +# +# 3. Deploy: +# docker stack deploy -c runner-swarm.yml act-runner +# (or: docker compose -f runner-swarm.yml up -d if not using swarm mode for this) +# +# Note: docker stack deploy does not support `volumes.driver_opts` or bind mounts +# in the same way as compose. If you hit swarm limitations with the socket bind mount, +# run this with plain `docker compose` instead of `docker stack deploy` — the runner +# doesn't need swarm-specific features. + +networks: + fabled_backend: + external: true + +volumes: + act_runner_data: + +services: + act_runner: + image: gitea/act_runner:latest + restart: unless-stopped + environment: + - GITEA_INSTANCE_URL=https://git.fabledsword.com + - GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN} + - GITEA_RUNNER_NAME=forgejo-host-runner + # Labels control which `runs-on:` values this runner accepts. + # ubuntu-latest is the label used in all workflow files. + - GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://node:20-bullseye + - CONFIG_FILE=/data/config.yml + volumes: + # Host Docker socket — required for the build job (docker buildx). + - /var/run/docker.sock:/var/run/docker.sock + - act_runner_data:/data + # act_runner creates job containers as siblings on the host docker daemon. + # The config file (written below by the init container) tells act_runner + # which host paths job containers are allowed to mount. + # See the init service below for how config.yml is created. + networks: + - fabled_backend + depends_on: + - act_runner_init + deploy: + replicas: 1 + restart_policy: + condition: on-failure + + # One-shot container that writes the act_runner config file before the runner starts. + # Runs once, exits, and the runner service picks up /data/config.yml. + act_runner_init: + image: busybox + volumes: + - act_runner_data:/data + command: > + sh -c " + if [ ! -f /data/config.yml ]; then + cat > /data/config.yml << 'EOF' +log: + level: info + +runner: + # How many jobs to run concurrently on this runner. + capacity: 2 + # Timeout for each job. + timeout: 30m + +cache: + enabled: true + dir: /data/.cache + +container: + # Job containers are created on the host Docker daemon (via mounted socket). + # They share the host network so they can reach Forgejo and the registry directly. + network: host + # Paths the runner is allowed to bind-mount into job containers. + # Add any paths here that workflow steps need to access on the host. + valid_volumes: + - /var/run/docker.sock +EOF + echo 'config.yml written' + else + echo 'config.yml already exists, skipping' + fi + " + deploy: + restart_policy: + condition: none