# 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.
