cca4bda1fc
- deploy/deploy.sh: idempotent deploy script with dynamic port
allocation (3011..30200), flock-based concurrency, atomic
.postgrest-port/.nextjs-port writes, port cleanup of the previous
deploy + dev stack, nginx config rendering+reload, healthchecks
with rollback, optional image pruning
- deploy/docker-compose.yml + Dockerfile.nextjs: example stack
consuming ${POSTGREST_HOST_PORT} / ${NEXTJS_HOST_PORT} (kept as
reference; the repo's root docker-compose.yml is the source of
truth for the actual production stack)
- deploy/nginx.conf.template: /api/* -> PostgREST, /* -> Next.js
- deploy/.env.production.example: managed port block + preserved secrets
- deploy/healthcheck.sh: standalone health probe (cron-friendly)
- deploy/Makefile: deploy/status/health/logs/down/rollback targets
- deploy/GITEA_SETUP.md: webhook vs Actions runner instructions
- deploy/README.md + deploy/.gitignore
Note: .gitea/workflows/deploy.yml was deliberately not added — the
existing workflow at that path on Gitea main is the source of truth
and is left untouched.
120 lines
4.4 KiB
Markdown
120 lines
4.4 KiB
Markdown
# Wiring deploy.sh into Gitea
|
|
|
|
Two practical patterns, both supported:
|
|
|
|
## Option A — Gitea webhook (push event → cURL on the server)
|
|
|
|
Simplest. A push to `main` causes Gitea to POST to a small endpoint on the
|
|
homelab server, which then runs `deploy.sh`.
|
|
|
|
### A.1. Add a webhook secret in Gitea
|
|
- Repository → Settings → Webhooks → Add Webhook → Gitea
|
|
- Target URL: `https://deploy.example.com/hooks/gitea` (your reverse-proxied
|
|
endpoint that runs the script)
|
|
- HTTP method: `POST`
|
|
- POST content type: `application/json`
|
|
- Secret: a long random string
|
|
- Trigger on: "Push events"
|
|
- Branch filter: `main` (or `gitea-sync`)
|
|
- Save and note the secret.
|
|
|
|
### A.2. Drop a tiny receiver on the homelab server
|
|
A 5-line systemd-timer-friendly shell receiver is enough. For example:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# /usr/local/bin/gitea-deploy-webhook
|
|
set -euo pipefail
|
|
LOG=/var/log/gitea-deploy.log
|
|
echo "[$(date -Iseconds)] trigger" >> "$LOG"
|
|
sudo -u deploy /srv/app/deploy/deploy.sh >> "$LOG" 2>&1
|
|
```
|
|
|
|
Expose it via a separate `server { ... }` block in nginx that listens on
|
|
something obscure and `allow`/`deny`s only Gitea's source IPs. Or put it
|
|
behind a Cloudflare Tunnel / Tailscale Funnel. The exact exposure model is
|
|
up to you.
|
|
|
|
### A.3. Hardening the secret
|
|
If you want the receiver to verify Gitea's HMAC, add this check (works with
|
|
`GITEA_WEBHOOK_SECRET` matching what you set in the UI):
|
|
|
|
```bash
|
|
SECRET='paste-your-secret-here'
|
|
sig=$(printf '%s' "$HTTP_RAW_BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | xxd -p -c 256)
|
|
expected=$(printf 'sha256=%s' "$sig")
|
|
[[ "$HTTP_X_GITEA_SIGNATURE" == "$expected" ]] || { echo "bad sig"; exit 1; }
|
|
```
|
|
|
|
(Your receiver framework — `webhook`, `socat`, `caddy` plugin — handles
|
|
header propagation differently; adapt accordingly.)
|
|
|
|
---
|
|
|
|
## Option B — Gitea Actions runner (self-hosted)
|
|
|
|
The Gitea-native CI path. Most flexible: only deploy when the runner is on
|
|
the homelab.
|
|
|
|
### B.1. Register a self-hosted runner
|
|
On the homelab, follow https://docs.gitea.com/usage/actions/act_runner
|
|
and register a runner labeled `self-hosted,homelab`.
|
|
|
|
### B.2. Workflow file (already in the repo)
|
|
The workflow lives at [.gitea/workflows/deploy.yml](../.gitea/workflows/deploy.yml)
|
|
in the repo root. It triggers on push to `main` / `gitea-sync` and calls
|
|
`./deploy/deploy.sh`. Highlights:
|
|
|
|
- **`on.push.paths`** filter — only deploys when source, deploy config, or
|
|
the workflow itself changes. Drop the block to deploy on every commit.
|
|
- **`workflow_dispatch`** — manual trigger from the Gitea UI with optional
|
|
inputs for `api_url`, `project_name`, `skip_prune`. Useful for blue/green
|
|
deploys (`project_name: prod-app-green`).
|
|
- **`concurrency.group`** — at most one deploy runs at a time (the script
|
|
also has its own `flock`).
|
|
- **`runs-on: [self-hosted, homelab]`** — must match the labels you gave
|
|
the runner.
|
|
- **Preflight step** — verifies `docker`, `docker compose`, `flock`, `ss`,
|
|
`curl` exist and the required files are present, BEFORE the script takes
|
|
its lock.
|
|
- **Post-deploy `healthcheck.sh --nginx`** — independent smoke test in
|
|
addition to the script's own healthcheck.
|
|
- **Failure annotation** — tail of `deploy.log` printed on failure so the
|
|
Actions log shows what went wrong without a separate SSH session.
|
|
|
|
### B.3. Optional: path filter tweaks
|
|
To deploy on docs-only commits, drop the `paths:` block under `on.push`.
|
|
To deploy on PR merges only, replace the trigger with:
|
|
|
|
```yaml
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
```
|
|
|
|
### B.4. Required secrets
|
|
- `GITEA_TOKEN` (a personal access token with `write:repository` scope) —
|
|
only needed for the "annotate the commit" step (which posts the new
|
|
ports as a commit comment). The rest of the workflow works without any
|
|
secrets.
|
|
|
|
The deploy itself reads `.env.production` from the workspace, so no app
|
|
secrets need to be plumbed through Actions (the runner has filesystem
|
|
access to `/srv/app/`).
|
|
|
|
---
|
|
|
|
## Comparison
|
|
|
|
| | Webhook (A) | Actions (B) |
|
|
|---|---|---|
|
|
| Setup cost | Trivial | Moderate (runner install) |
|
|
| Logging | `deploy.log` only | Gitea Actions UI + `deploy.log` |
|
|
| Conditional triggers | Receiver must parse the body | YAML in the workflow |
|
|
| Multi-repo | One webhook per repo | One workflow per repo |
|
|
| Source of truth | Webhook delivery | Runner job history |
|
|
|
|
For a single homelab repo, **Option B is recommended** — you get the Actions
|
|
UI for free and the runner already lives on the box, so there's no extra
|
|
network surface area to secure.
|