feat: add auto-deploy workflow for changed stacks and document setup process
Some checks failed
Auto-Deploy Changed Stacks / deploy (push) Failing after 15s
Some checks failed
Auto-Deploy Changed Stacks / deploy (push) Failing after 15s
This commit is contained in:
parent
820121fd5a
commit
618641fadd
173
.gitea/workflows/auto-deploy.yml
Normal file
173
.gitea/workflows/auto-deploy.yml
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
name: Auto-Deploy Changed Stacks
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Detect changed stacks and trigger Komodo webhooks
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Webhook map: stack-name → Komodo webhook URL
|
||||||
|
# Komodo webhook URLs are self-authenticating (token is embedded
|
||||||
|
# in the URL path). Fill in URLs from:
|
||||||
|
# Komodo UI → Stack → Webhooks tab → copy the full URL
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
declare -A WEBHOOK_MAP
|
||||||
|
|
||||||
|
# heimdall stacks
|
||||||
|
WEBHOOK_MAP["5etools"]="TODO"
|
||||||
|
WEBHOOK_MAP["authentik"]="TODO"
|
||||||
|
WEBHOOK_MAP["bentopdf"]="TODO"
|
||||||
|
WEBHOOK_MAP["byparr"]="TODO"
|
||||||
|
WEBHOOK_MAP["convertx"]="TODO"
|
||||||
|
WEBHOOK_MAP["core"]="TODO"
|
||||||
|
WEBHOOK_MAP["docker_registry"]="TODO"
|
||||||
|
WEBHOOK_MAP["gitea"]="TODO"
|
||||||
|
WEBHOOK_MAP["guardian"]="TODO"
|
||||||
|
WEBHOOK_MAP["homelab-registry-mcp"]="TODO"
|
||||||
|
WEBHOOK_MAP["karakeep"]="TODO"
|
||||||
|
WEBHOOK_MAP["kitchenowl"]="TODO"
|
||||||
|
WEBHOOK_MAP["ntfy"]="TODO"
|
||||||
|
WEBHOOK_MAP["overseerr"]="TODO"
|
||||||
|
WEBHOOK_MAP["profilarr"]="TODO"
|
||||||
|
WEBHOOK_MAP["prowlarr"]="TODO"
|
||||||
|
WEBHOOK_MAP["radarr"]="TODO"
|
||||||
|
WEBHOOK_MAP["sabnzbd"]="TODO"
|
||||||
|
WEBHOOK_MAP["snapotter"]="TODO"
|
||||||
|
WEBHOOK_MAP["sonarr"]="TODO"
|
||||||
|
WEBHOOK_MAP["sparkyfitness"]="TODO"
|
||||||
|
WEBHOOK_MAP["tautulli"]="TODO"
|
||||||
|
WEBHOOK_MAP["tracearr"]="TODO"
|
||||||
|
WEBHOOK_MAP["trek"]="TODO"
|
||||||
|
WEBHOOK_MAP["vaultwarden"]="TODO"
|
||||||
|
WEBHOOK_MAP["vscode"]="TODO"
|
||||||
|
WEBHOOK_MAP["weatherchannel"]="TODO"
|
||||||
|
WEBHOOK_MAP["wizarr"]="TODO"
|
||||||
|
WEBHOOK_MAP["zipline"]="TODO"
|
||||||
|
|
||||||
|
# waldorf stacks
|
||||||
|
WEBHOOK_MAP["immich"]="TODO"
|
||||||
|
WEBHOOK_MAP["openwebui"]="TODO"
|
||||||
|
WEBHOOK_MAP["pinchflat"]="TODO"
|
||||||
|
WEBHOOK_MAP["plex"]="TODO"
|
||||||
|
WEBHOOK_MAP["tunarr"]="TODO"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Detect changed paths vs the previous commit
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD)
|
||||||
|
|
||||||
|
echo "=== Changed files ==="
|
||||||
|
if [[ -z "$CHANGED_FILES" ]]; then
|
||||||
|
echo " (none)"
|
||||||
|
else
|
||||||
|
echo "$CHANGED_FILES"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Parse changed paths → extract {node} and {stack}
|
||||||
|
# Expected pattern: nodes/{node}/{stack}/...
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
declare -A SEEN_STACKS
|
||||||
|
declare -a STACKS_TO_DEPLOY
|
||||||
|
declare -a UNMATCHED_PATHS
|
||||||
|
|
||||||
|
while IFS= read -r filepath; do
|
||||||
|
[[ -z "$filepath" ]] && continue
|
||||||
|
|
||||||
|
IFS='/' read -ra PARTS <<< "$filepath"
|
||||||
|
|
||||||
|
# Must start with "nodes" and have at least 4 components
|
||||||
|
# (nodes / {node} / {stack} / file)
|
||||||
|
[[ "${PARTS[0]}" != "nodes" ]] && continue
|
||||||
|
[[ "${#PARTS[@]}" -lt 4 ]] && continue
|
||||||
|
|
||||||
|
node="${PARTS[1]}"
|
||||||
|
stack="${PARTS[2]}"
|
||||||
|
|
||||||
|
[[ -z "$stack" ]] && continue
|
||||||
|
|
||||||
|
# Deduplicate: skip if this stack was already queued
|
||||||
|
[[ -n "${SEEN_STACKS[$stack]+_}" ]] && continue
|
||||||
|
SEEN_STACKS["$stack"]=1
|
||||||
|
|
||||||
|
if [[ -n "${WEBHOOK_MAP[$stack]+_}" ]]; then
|
||||||
|
STACKS_TO_DEPLOY+=("$stack")
|
||||||
|
echo " Queued for deploy: $stack (node: $node)"
|
||||||
|
else
|
||||||
|
UNMATCHED_PATHS+=("$filepath (stack: $stack)")
|
||||||
|
fi
|
||||||
|
done <<< "$CHANGED_FILES"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Warn about paths that have no webhook entry — not silent
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
if [[ "${#UNMATCHED_PATHS[@]}" -gt 0 ]]; then
|
||||||
|
echo "=== WARNING: paths with no matching webhook entry ==="
|
||||||
|
for p in "${UNMATCHED_PATHS[@]}"; do
|
||||||
|
echo " - $p"
|
||||||
|
done
|
||||||
|
echo " Add these stack names to WEBHOOK_MAP in this workflow."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Nothing under nodes/ changed
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
if [[ "${#STACKS_TO_DEPLOY[@]}" -eq 0 ]]; then
|
||||||
|
echo "No stack changes detected — nothing to deploy."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# POST to each Komodo webhook
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
echo "=== Triggering Komodo webhooks ==="
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
for stack in "${STACKS_TO_DEPLOY[@]}"; do
|
||||||
|
url="${WEBHOOK_MAP[$stack]}"
|
||||||
|
|
||||||
|
if [[ "$url" == "TODO" ]]; then
|
||||||
|
echo " SKIP: $stack — webhook URL not configured yet (fill in WEBHOOK_MAP)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n " → $stack ... "
|
||||||
|
HTTP_STATUS=$(curl --silent --show-error --output /dev/null \
|
||||||
|
--write-out "%{http_code}" \
|
||||||
|
--max-time 30 \
|
||||||
|
--request POST "$url" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--data '{"trigger":"gitea-push"}')
|
||||||
|
|
||||||
|
if [[ "$HTTP_STATUS" =~ ^2 ]]; then
|
||||||
|
echo "HTTP $HTTP_STATUS OK"
|
||||||
|
else
|
||||||
|
echo "HTTP $HTTP_STATUS FAILED"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ "$FAILED" -ne 0 ]]; then
|
||||||
|
echo "ERROR: One or more Komodo webhook POSTs returned a non-2xx status."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All webhooks triggered successfully."
|
||||||
16
README.md
16
README.md
@ -36,6 +36,22 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🚢 Auto-Deploy
|
||||||
|
|
||||||
|
Merging a PR to `main` automatically deploys every stack whose folder changed.
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. The Gitea Actions workflow (`.gitea/workflows/auto-deploy.yml`) detects which `nodes/{node}/{stack}/` paths changed
|
||||||
|
2. It POSTs to the corresponding Komodo webhook for each affected stack — no manual redeploy needed
|
||||||
|
3. Stacks with no matching webhook entry are logged as warnings (not silent failures); any non-2xx response fails the job loudly
|
||||||
|
|
||||||
|
**Before it goes live you need:**
|
||||||
|
- A Gitea Actions runner installed on Heimdall (see [SOP-003](documentation/SOPs/SOP-003-Gitea-Actions-Runner-Setup.md))
|
||||||
|
- All `TODO` webhook URLs in `WEBHOOK_MAP` replaced with the actual Komodo webhook URLs
|
||||||
|
(Komodo UI → Stack → Webhooks tab → copy the full self-authenticating URL)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
This is a personal homelab, but documentation improvements and issue reports are welcome!
|
This is a personal homelab, but documentation improvements and issue reports are welcome!
|
||||||
|
|||||||
194
documentation/SOPs/SOP-003-Gitea-Actions-Runner-Setup.md
Normal file
194
documentation/SOPs/SOP-003-Gitea-Actions-Runner-Setup.md
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# SOP-003: Gitea Actions Runner Setup (Heimdall)
|
||||||
|
|
||||||
|
**Status:** Active
|
||||||
|
**Created:** June 2, 2026
|
||||||
|
**Last Updated:** June 2, 2026
|
||||||
|
**Owner:** Nathan Castaldi
|
||||||
|
**Applies To:** Gitea Actions auto-deploy workflow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Install and register a Gitea Actions runner on Heimdall so that the
|
||||||
|
`.gitea/workflows/auto-deploy.yml` workflow can execute when PRs are merged
|
||||||
|
to `main`. The runner triggers Komodo webhooks for any stack whose folder
|
||||||
|
changed in the push.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [ ] SSH access to Heimdall (`10.0.0.151`)
|
||||||
|
- [ ] Docker and Docker Compose running on Heimdall
|
||||||
|
- [ ] Gitea instance accessible at `https://git.castaldifamily.com`
|
||||||
|
- [ ] Admin or repo-owner access to the `nathan/homelab` Gitea repository
|
||||||
|
- [ ] Komodo webhook URLs retrieved from Komodo UI (see [Step 4](#step-4--add-required-secrets-in-gitea))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Retrieve a Runner Registration Token
|
||||||
|
|
||||||
|
1. In Gitea, go to **Repository** → `nathan/homelab` → **Settings** → **Actions** → **Runners**
|
||||||
|
2. Click **Create new Runner**
|
||||||
|
3. Copy the **registration token** — you will need it in Step 3
|
||||||
|
|
||||||
|
> **Note:** Tokens are single-use. If you lose it, generate a new one.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Create the Runner Compose File on Heimdall
|
||||||
|
|
||||||
|
SSH into Heimdall and create the runner stack directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /opt/gitea-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `/opt/gitea-runner/compose.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: gitea-runner
|
||||||
|
|
||||||
|
services:
|
||||||
|
runner:
|
||||||
|
image: gitea/act_runner:latest
|
||||||
|
container_name: gitea-runner
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
GITEA_INSTANCE_URL: "https://git.castaldifamily.com"
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN}"
|
||||||
|
GITEA_RUNNER_NAME: "heimdall-runner"
|
||||||
|
GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye-slim"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- runner-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
runner-data:
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `/opt/gitea-runner/.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
GITEA_RUNNER_REGISTRATION_TOKEN=<paste token from Step 1>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Security:** The `.env` file contains a credential. Do not commit it to Git.
|
||||||
|
> The token is consumed on first registration; after that the runner stores its
|
||||||
|
> own credentials in the `runner-data` volume.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Start the Runner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/gitea-runner
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch the startup logs to confirm registration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs -f runner
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output (within ~10 seconds):
|
||||||
|
|
||||||
|
```
|
||||||
|
INFO Registering runner ... runner=heimdall-runner
|
||||||
|
INFO Runner registered successfully.
|
||||||
|
INFO Starting runner ... name=heimdall-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the runner appears in Gitea:
|
||||||
|
|
||||||
|
- Go to **Repository** → **Settings** → **Actions** → **Runners**
|
||||||
|
- `heimdall-runner` should show **Online**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — Add Required Secrets in Gitea
|
||||||
|
|
||||||
|
The auto-deploy workflow does not use a shared secret (Komodo webhook URLs
|
||||||
|
are self-authenticating). No repository secrets are required for the
|
||||||
|
workflow itself.
|
||||||
|
|
||||||
|
If you later need to store additional credentials (e.g., a Komodo API key
|
||||||
|
for a future workflow), add them at:
|
||||||
|
|
||||||
|
> **Repository** → **Settings** → **Secrets and Variables** → **Actions** → **New secret**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Fill In Webhook URLs
|
||||||
|
|
||||||
|
Open `.gitea/workflows/auto-deploy.yml` in the repo and replace each `TODO`
|
||||||
|
value in `WEBHOOK_MAP` with the actual URL from Komodo:
|
||||||
|
|
||||||
|
1. Log into Komodo UI (`https://komodo.castaldifamily.com`)
|
||||||
|
2. Navigate to **Stacks** → select the stack
|
||||||
|
3. Open the **Webhooks** tab
|
||||||
|
4. Copy the full webhook URL (token is embedded in the path)
|
||||||
|
5. Paste it as the map value for that stack name
|
||||||
|
|
||||||
|
Commit the updated workflow file to `main` (or merge a PR that includes it).
|
||||||
|
|
||||||
|
> **Never paste webhook URLs into public forks or issue comments.** They are
|
||||||
|
> equivalent to credentials.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6 — Verify End-to-End
|
||||||
|
|
||||||
|
1. Make a trivial change to any stack folder (e.g., add a blank line to
|
||||||
|
`nodes/heimdall/sonarr/compose.yaml`)
|
||||||
|
2. Commit and push directly to `main` (or merge a PR)
|
||||||
|
3. In Gitea, go to **Repository** → **Actions** — the `Auto-Deploy Changed Stacks` run should appear
|
||||||
|
4. Click the run → expand the **Detect changed stacks and trigger Komodo webhooks** step
|
||||||
|
5. Confirm output resembles:
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Changed files ===
|
||||||
|
nodes/heimdall/sonarr/compose.yaml
|
||||||
|
|
||||||
|
Queued for deploy: sonarr (node: heimdall)
|
||||||
|
|
||||||
|
=== Triggering Komodo webhooks ===
|
||||||
|
→ sonarr ... HTTP 200 OK
|
||||||
|
|
||||||
|
All webhooks triggered successfully.
|
||||||
|
```
|
||||||
|
|
||||||
|
6. In Komodo UI, verify the `sonarr` stack shows a recent deployment event
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Check |
|
||||||
|
|---|---|
|
||||||
|
| Runner shows **Offline** in Gitea | `docker compose logs runner` on Heimdall; check `GITEA_INSTANCE_URL` reachability |
|
||||||
|
| Job never picks up | Runner label must match `runs-on` in the workflow (`ubuntu-latest`) |
|
||||||
|
| `HTTP 404` on webhook POST | URL in `WEBHOOK_MAP` is wrong; re-copy from Komodo UI |
|
||||||
|
| `HTTP 401` / `403` on webhook POST | URL token is stale; regenerate webhook in Komodo UI |
|
||||||
|
| Job shows `HEAD~1` diff error | Ensure `fetch-depth: 2` is set in the checkout step |
|
||||||
|
| Stack not in `WEBHOOK_MAP` | Logged as a warning in job output; add the stack name to the map |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
To stop the runner without removing its registration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/gitea-runner && docker compose stop runner
|
||||||
|
```
|
||||||
|
|
||||||
|
To fully deregister and remove:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/gitea-runner && docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Then delete the runner from **Gitea → Repository → Settings → Actions → Runners**.
|
||||||
Loading…
x
Reference in New Issue
Block a user