Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,55 @@ jobs:
build-args: |
VERSION=${{ env.VERSION }}

# The deploy job (self-hosted runner, on-host docker-compose) comes with the
# deploy/ stack (Caddyfile + docker-compose.yml), the second Milestone-F PR.
# Ship the freshly pushed image to the on-host compose stack. The runner lives ON the
# deploy host (outbound-only long-poll to GitHub; the host is VPN-internal, so no inbound
# is possible). It syncs only the NON-secret infra (compose file + Caddyfile) from the
# repo; secrets (.env, .glabs-web.yaml) and the caddy-data volume (TLS certs) are never
# touched. See deploy/README.md "Automatischer Deploy".
deploy:
needs: build-and-push-image
# Gate: only runs once the self-hosted runner on the deploy host exists — set the repo
# variable AUTO_DEPLOY=true then. Until then the image is still built & pushed above;
# this job is simply skipped (no stuck queue waiting for a missing runner).
if: ${{ vars.AUTO_DEPLOY == 'true' }}
runs-on: [self-hosted, glabs-deploy]
env:
# The v-prefixed release tag (from whichever trigger fired). docker/metadata-action
# tags the image with this raw ref (v3.38.0), so GLABS_WEB_TAG must carry the v too.
VERSION: ${{ github.event.release.tag_name || inputs.tag }}
# On-host deploy dir; override via repo variable DEPLOY_DIR if different.
DEPLOY_DIR: ${{ vars.DEPLOY_DIR || '/home/glabs/glabs/deploy' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# Sync the deploy/ files as they were at the released tag.
ref: ${{ github.event.release.tag_name || inputs.tag }}

- name: Sync infra + deploy pinned image
run: |
set -euo pipefail
# Sync non-secret infra into the on-host deploy dir. Secrets (.env,
# .glabs-web.yaml) and the caddy-data volume (certs) stay put.
cp deploy/docker-compose.yml "$DEPLOY_DIR/"
CADDY_CHANGED=0
if ! diff deploy/Caddyfile "$DEPLOY_DIR/Caddyfile" >/dev/null 2>&1; then
cp deploy/Caddyfile "$DEPLOY_DIR/"
CADDY_CHANGED=1
fi

cd "$DEPLOY_DIR"
# Pin the backend tag so the host reflects the running version
# (manual rollback = edit .env + `docker compose up -d glabs-web`).
sed -i "s/^GLABS_WEB_TAG=.*/GLABS_WEB_TAG=$VERSION/" .env

docker compose pull glabs-web
# Reconcile the stack: only glabs-web (new tag) + any changed service is recreated.
# The gh-runner services are under `profiles: [runner]`, so this plain `up -d`
# ignores them — the runner never restarts itself mid-deploy.
docker compose up -d
# Caddy re-reads its Caddyfile only on container (re)start.
if [ "$CADDY_CHANGED" = "1" ]; then
docker compose up -d --force-recreate caddy
fi
docker image prune -f
10 changes: 10 additions & 0 deletions deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,13 @@ OAUTH2_PROXY_CLIENT_ID=your-client-id
OAUTH2_PROXY_CLIENT_SECRET=your-client-secret
# Cookie encryption secret (must be 16/24/32 bytes — `openssl rand -base64 24` gives 32 chars).
OAUTH2_PROXY_COOKIE_SECRET=change-me-random-32-bytes-base64

# --- Auto-deploy: self-hosted GitHub Actions runners (profile "runner") ---
# Only needed if you run the gh-runner services (docker compose --profile runner up -d).
# On-host deploy dir — MUST match the workflow's DEPLOY_DIR repo variable and is where the
# runner containers bind-mount .env / docker-compose.yml / Caddyfile.
DEPLOY_DIR=/home/glabs/glabs/deploy
# GitHub PAT the runner containers use to self-renew their registration tokens.
# Classic PAT with scopes `repo` + `workflow` (covers obcode/glabs AND obcode/glabs.gui),
# or fine-grained with Repository permission "Administration: Read and write".
GH_RUNNER_PAT=ghp_change-me
35 changes: 35 additions & 0 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,41 @@ ssh -L 27017:127.0.0.1:27017 <user>@<host>
# then: mongodb://<MONGO_USER>:<MONGO_PASSWORD>@localhost:27017/?authSource=admin
```

## Automatischer Deploy (self-hosted runners)

CI builds and pushes the images on GitHub-hosted runners (`docker.yml`). The deploy host is
VPN-internal, so GitHub can't push into it — instead a **self-hosted runner on the host** polls
GitHub outbound and, on a release, runs `docker compose` locally. Each repo's `docker.yml` has
a gated `deploy` job (`runs-on: [self-hosted, glabs-deploy]`):

- **glabs** (backend) — syncs `docker-compose.yml` + `Caddyfile` into `$DEPLOY_DIR` (secrets and
the `caddy-data` volume untouched), pins `GLABS_WEB_TAG` in `.env`, `docker compose pull
glabs-web` + `up -d`, and force-recreates Caddy if the Caddyfile changed.
- **glabs.gui** — pins `GUI_TAG` and rolls only the `gui` service.

Because `obcode` is a personal account (no org), runners are **repo-scoped**: one per repo,
sharing the label `glabs-deploy`. Both run as containers in this compose under the `runner`
profile.

### One-time host setup

1. Set `DEPLOY_DIR` and `GH_RUNNER_PAT` in `.env` (see `.env.example`). `GH_RUNNER_PAT` is a
classic PAT with `repo` + `workflow` (covers both repos).
2. Start the runners (the `runner` profile keeps them out of the default stack, so a deploy's
`docker compose up -d` never restarts them mid-job):
```sh
docker compose --profile runner up -d gh-runner gh-runner-gui
```
3. Confirm both appear under **Settings → Actions → Runners** in each repo (idle, label
`glabs-deploy`).
4. Flip the gate: set the repo variable **`AUTO_DEPLOY=true`** in **both** `obcode/glabs` and
`obcode/glabs.gui` (Settings → Secrets and variables → Actions → Variables). Optionally set
`DEPLOY_DIR` there too if it differs from the default. Until `AUTO_DEPLOY=true`, images are
still built and pushed — only the deploy step is skipped.

From then on, every release rolls itself out. Manual rollback stays available: set an older
`GLABS_WEB_TAG`/`GUI_TAG` in `.env` and `docker compose up -d glabs-web` (or `gui`).

## Notes

- **Rollback:** set `GLABS_WEB_TAG` / `GUI_TAG` in `.env` to an older release tag and
Expand Down
58 changes: 58 additions & 0 deletions deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,64 @@ services:
- caddy-data:/data # certificates + ACME account/EAB registration — MUST persist
- caddy-config:/config

# --- Self-hosted GitHub Actions runners for auto-deploy (profile "runner") ---
# The deploy host is VPN-internal — GitHub cannot push in. These runners poll GitHub
# OUTBOUND and, on a release, run `docker compose` locally (see README "Automatischer
# Deploy"). obcode is a personal account (no org), so runners are REPO-scoped: one per
# repo, sharing the label `glabs-deploy`.
#
# `profiles: [runner]` keeps them OUT of the default stack: a bare `docker compose up -d`
# (what the deploy job runs) ignores profiled services entirely, so a runner never
# restarts itself mid-deploy and aborts its own job. Start them once, separately:
# docker compose --profile runner up -d gh-runner gh-runner-gui
gh-runner:
image: myoung34/github-runner:latest
container_name: glabs-gh-runner
restart: unless-stopped
profiles: ["runner"]
environment:
REPO_URL: https://github.com/obcode/glabs
RUNNER_NAME: glabs-deploy
RUNNER_SCOPE: repo
LABELS: glabs-deploy
# PAT (classic: repo + workflow, or fine-grained: Administration RW) so the container
# renews registration tokens itself and survives restarts without a token swap.
ACCESS_TOKEN: ${GH_RUNNER_PAT:-}
RUNNER_WORKDIR: /tmp/runner/work
security_opt:
- label:disable
volumes:
# Host Docker socket → `docker compose` in the deploy job talks to the HOST daemon.
- /var/run/docker.sock:/var/run/docker.sock
# The on-host deploy dir at the SAME path inside the container, so `cp`/`sed` in the
# job and the compose bind-mounts resolve to identical host paths (the socket resolves
# mounts against the host). Keep DEPLOY_DIR in sync with the workflow's DEPLOY_DIR var.
- ${DEPLOY_DIR:-/home/glabs/glabs/deploy}:${DEPLOY_DIR:-/home/glabs/glabs/deploy}
- /tmp/runner:/tmp/runner

# Second runner for the glabs.gui repo (personal account → no org-wide sharing, each repo
# needs its own repo-scoped runner). Same stack, same `runner` profile, same PAT (classic
# repo+workflow covers both repos), same label — only REPO_URL + RUNNER_NAME differ. The
# GUI deploy job only touches the `gui` service.
gh-runner-gui:
image: myoung34/github-runner:latest
container_name: glabs-gh-runner-gui
restart: unless-stopped
profiles: ["runner"]
environment:
REPO_URL: https://github.com/obcode/glabs.gui
RUNNER_NAME: glabs-deploy-gui
RUNNER_SCOPE: repo
LABELS: glabs-deploy
ACCESS_TOKEN: ${GH_RUNNER_PAT:-}
RUNNER_WORKDIR: /tmp/runner-gui/work
security_opt:
- label:disable
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${DEPLOY_DIR:-/home/glabs/glabs/deploy}:${DEPLOY_DIR:-/home/glabs/glabs/deploy}
- /tmp/runner-gui:/tmp/runner-gui

volumes:
mongo-data:
caddy-data:
Expand Down
Loading