diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5015209..2adedce 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,30 +1,14 @@ name: Deploy to valhalla -# Post-split deploy. The application stacks (foundry, media, notify, monitoring, -# remote, owncloud, resume, famapp, authentik, dev) live under stacks// -# and are deployed by Portainer git polling — the runner does NOT touch them. -# Caddy lives in its own Portainer git stack (stacks/proxy/) and is likewise -# deployed by Portainer; this workflow only pushes the Caddyfile and triggers -# a hot reload on the running Caddy container. +# Deployment moved to Gitea Actions (.gitea/workflows/deploy-caddy.yml). +# This workflow is kept as a manual fallback only. on: - push: - branches: [main] - paths: - - Caddyfile - - .github/workflows/deploy.yml - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + workflow_dispatch: jobs: deploy: runs-on: self-hosted steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Push Caddyfile and reload Caddy - run: | - cp Caddyfile /config/caddy/Caddyfile - docker exec caddy caddy reload --config /etc/caddy/Caddyfile + - name: Disabled + run: echo "Deployment moved to Gitea Actions." diff --git a/AGENTS.md b/AGENTS.md index f0ec5f3..1acc6ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,9 +15,11 @@ from your tool's user-level instructions (`~/.codex/AGENTS.md` / `~/.gemini/GEMI - **The repo is canonical.** Portainer polls `main` every 5 min and redeploys any app stack whose `stacks//*` files changed. Editing here changes nothing until you `git push`. - **Deployment channels:** - - `stacks//*` → git push → Portainer redeploys that stack (≤ 5 min). - - `Caddyfile` → git push → runner workflow `scp`s it + reloads Caddy (or `apply-compose.ps1 -Caddy`). + - `stacks//*` → git push to Gitea → Portainer polls every 5 min and redeploys that stack. GitHub is a temporary mirror only. + - `Caddyfile` → git push → Gitea Actions (`.gitea/workflows/deploy-caddy.yml`) copies it + reloads Caddy. Fallback: `apply-compose.ps1 -Caddy`. - `portainer-compose.yml` / `vault.hcl` → `apply-compose.ps1 -Portainer` (Portainer can't manage itself). +- **Portainer app stacks must have empty UI environment variables.** All config comes from `stack.env` via `env_file: stack.env`. Run `scripts/check-portainer-stack-env.ps1` before pushing. +- **famapp images** build on Gitea Actions (`v*` tags) and push to `registry.ginnoir.com/ginnoir/famapp`. - **Secrets are committed intentionally.** `.env` and `stacks/*/stack.env` are versioned in this PRIVATE repo. Do **not** scrub or gitignore them. - **Line endings:** `.gitattributes` forces LF. Never push CRLF (especially `stack.env`) — stray diff --git a/CLAUDE.md b/CLAUDE.md index ebc01b1..adb88b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,9 +6,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Deployment configuration for a **live, single-host Docker homelab** running on a headless Ubuntu server reachable at `ssh ginnoir@valhalla`. This repo is **not application code** — it is the canonical source for ~50 containers organized into per-domain Portainer-managed stacks. -**The repo is canonical.** Portainer polls this repo's `main` branch every 5 min and redeploys any application stack whose source files have changed. Editing here changes nothing until you push to GitHub (or, for the management plane, run `apply-compose.ps1 -Portainer`). +**The repo is canonical.** Portainer polls the **Gitea** repo `https://gitea.ginnoir.com/ginnoir/homelabstack` on `main` every 5 min and redeploys any application stack whose source files have changed. Editing here changes nothing until you push (or, for the management plane, run `apply-compose.ps1 -Portainer`). GitHub is a temporary push mirror only. -`.env` and `stacks/*/stack.env` are committed **intentionally** — the GitHub repo `ginnoir/homelabstack` is private and the stack's secrets are versioned with it. Do not scrub or gitignore them. +`.env` and `stacks/*/stack.env` are committed **intentionally** — secrets are versioned in the private repos. Do not scrub or gitignore them. ## Layout @@ -25,25 +25,26 @@ Deployment configuration for a **live, single-host Docker homelab** running on a │ ├── notify/ # ntfy, freshrss, vigilant │ ├── monitoring/ # uptime-kuma, homarr │ ├── remote/ # rustdesk hbbr + hbbs -│ └── dev/ # gitea, code-server, registry, bookstack, dbx, plane +│ └── dev/ # code-server, bookstack, dbx, plane ├── portainer-compose.yml # management plane (raw compose, not a Portainer stack) ├── vault.hcl # vault config (deployed alongside portainer-compose.yml) -├── Caddyfile # caddy config (deployed via runner workflow or -Caddy) +├── Caddyfile # caddy config (deployed via Gitea Actions or -Caddy) ├── .env # mgmt-plane env + vault unseal keys + global vars ├── apply-compose.ps1 # manual ops helper (mgmt plane + Caddyfile + vault unseal) -└── .github/workflows/deploy.yml # runner: pushes Caddyfile + reloads caddy on push +├── .gitea/workflows/deploy-caddy.yml # Gitea Actions: pushes Caddyfile + reloads caddy on push +└── .github/workflows/deploy.yml # disabled — Gitea Actions is primary ``` ## Deployment channels — what triggers what | Edit | Deploys via | Latency | |---|---|---| -| `stacks//*` | **git push** → Portainer polls every 5 min and redeploys that one stack | ≤ 5 min | -| `Caddyfile` | git push → runner workflow `scp`s to `/config/caddy/Caddyfile` + `docker exec caddy caddy reload` | seconds (when workflow is enabled) | +| `stacks//*` | **git push to Gitea** → Portainer polls every 5 min and redeploys that one stack | ≤ 5 min | +| `Caddyfile` | git push → Gitea Actions copies to `/config/caddy/Caddyfile` + `docker exec caddy caddy reload` | seconds | | `portainer-compose.yml` / `vault.hcl` | `apply-compose.ps1 -Portainer` (Portainer can't manage itself) | seconds | | `~/valhalla-lab/.env` (mgmt-plane env, vault unseal keys) | `apply-compose.ps1 -EnvFile` | seconds | -The runner workflow gate-filters on `paths: [Caddyfile, .github/workflows/deploy.yml]` so non-Caddyfile pushes don't trigger it. +Gitea is primary; GitHub is a temporary push mirror. Portainer app stacks must have **no Portainer UI env vars** — all config comes from `stack.env` via `env_file: stack.env`. ## On-host topology @@ -91,7 +92,7 @@ ssh ginnoir@valhalla "docker compose -f /config/portainer/compose/14/stacks/ownc 1. Pick the stack it belongs to under `stacks//` (or create a new stack and a new Portainer git stack for it). 2. Add the service to `stacks//docker-compose.yml`. Configs → `/config/` bind. Blobs → `/storage1/labdata/` bind. Add `edge` to its `networks:` if Caddy must reach it; otherwise just the per-stack private net. -3. Add secrets to `stacks//stack.env` (Portainer reads this file directly from the git repo on each redeploy). **Env convention:** every service gets `env_file: stack.env`, and var names in `stack.env` are exactly what the container reads — no `${VAR}` interpolation, no Portainer UI env vars. Composite values (e.g. `DATABASE_URL`) are written out in full in `stack.env`; keep embedded components in sync on rotation. Exception: `stacks/dev` still uses `${VAR}` interpolation backed by Portainer UI env vars — when editing its `stack.env`, mirror the change in the stack's UI env vars too. +3. Add secrets to `stacks//stack.env` (Portainer reads this file directly from the git repo on each redeploy). **Env convention:** every service gets `env_file: stack.env`, and var names in `stack.env` are exactly what the container reads — no `${VAR}` interpolation, no Portainer UI env vars. Composite values (e.g. `DATABASE_URL`) are written out in full in `stack.env`; keep embedded components in sync on rotation. 4. Add a site block to `Caddyfile` (`reverse_proxy :`); `import internal_only` for LAN-only. 5. Run `./scripts/gen-bookmarks.ps1` to regenerate `bookmarks-domains.html` + `bookmarks-ports.html` from the new Caddy block / published ports. 6. `git push`. Portainer redeploys the stack within 5 min; the runner reloads Caddy on the same push if `Caddyfile` changed. @@ -113,7 +114,9 @@ ssh ginnoir@valhalla "docker compose -f /config/portainer/compose/14/stacks/ownc - `Caddyfile` proxies `matrix.ginnoir.com → localhost:8008`, but there is **no Matrix/Synapse service in compose** — it's external/legacy. Likewise `dev.ginnoir.com → 192.168.1.74:3000` points at a different LAN host. - `watchtower` auto-updates `:latest` images, so a running image can drift ahead of what the last `apply` pulled. **Infra images** (Postgres, MariaDB, Redis, MinIO, CouchDB, Caddy, Vault) are **version-pinned** in compose with `com.centurylabs.watchtower.enable=false`; bump tags manually every few months. - Line endings: `.gitattributes` forces **LF** so files stay Unix-clean. Pushing CRLF (especially `stack.env`) to the Linux host appends stray `\r` to values and breaks things. -- The runner workflow (`.github/workflows/deploy.yml`) may be **disabled** at the GitHub repo level — check `gh workflow list` if Caddyfile pushes don't trigger a reload. Manual fallback: `apply-compose.ps1 -Caddy`. +- **Caddyfile reloads** are handled by `.gitea/workflows/deploy-caddy.yml` (Gitea Actions, `valhalla-management-runner`). If Gitea Actions doesn't trigger, fall back to `apply-compose.ps1 -Caddy`. The GitHub workflow (`.github/workflows/deploy.yml`) is disabled. +- **famapp images** are built by Gitea Actions on `v*` tags and pushed to `registry.ginnoir.com/ginnoir/famapp`. Portainer pulls from the self-hosted registry (registered in Portainer as "Valhalla Registry"). +- **Portainer app stacks must have empty UI environment variables.** All stack config comes from `stack.env` via `env_file`. Run `scripts/check-portainer-stack-env.ps1` to verify before pushing. ## Skills diff --git a/apply-compose.ps1 b/apply-compose.ps1 index 036f53f..56bff0c 100644 --- a/apply-compose.ps1 +++ b/apply-compose.ps1 @@ -53,7 +53,7 @@ if ($Portainer) { Write-Host "Pushing vault.hcl (Vault lives in the management plane) ..." ssh $server "mkdir -p /config/vault/config /config/vault/data /config/vault/logs" scp $vaultConfigLocal "${server}:/config/vault/config/vault.hcl" - Write-Host "Applying management plane (portainer, vault, github-runner, watchtower) ..." + Write-Host "Applying management plane (portainer, vault, gitea, registry, watchtower) ..." ssh $server "docker compose -f ~/valhalla-lab/portainer-compose.yml --env-file ~/valhalla-lab/.env up -d" } diff --git a/portainer-compose.yml b/portainer-compose.yml index 09a7af5..010f029 100644 --- a/portainer-compose.yml +++ b/portainer-compose.yml @@ -187,24 +187,6 @@ services: labels: - "com.centurylabs.watchtower.enable=false" - github-runner: - container_name: github_runner - image: myoung34/github-runner:latest - restart: unless-stopped - environment: - - ACCESS_TOKEN=${GITHUB_RUNNER_ACCESS_TOKEN} - - REPO_URL=https://github.com/ginnoir/homelabstack - - RUNNER_NAME=valhalla - - RUNNER_LABELS=self-hosted,Linux,x64 - - RUNNER_SCOPE=repo - - DOCKER_GROUP_GID=1001 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /home/ginnoir/valhalla-lab:/valhalla-lab - - /config/caddy:/config/caddy - labels: - - "com.centurylabs.watchtower.enable=false" - watchtower: container_name: watchtower image: containrrr/watchtower:latest diff --git a/stacks/proxy/docker-compose.yml b/stacks/proxy/docker-compose.yml index e048b4c..09ebd78 100644 --- a/stacks/proxy/docker-compose.yml +++ b/stacks/proxy/docker-compose.yml @@ -7,7 +7,7 @@ # # The Caddyfile itself is unchanged — upstreams are service names that resolve # over edge. It is bind-mounted from /config/caddy/Caddyfile and hot-reloaded -# by the GitHub Actions runner on push (deploy.yml). +# by the Gitea Actions runner on push (.gitea/workflows/deploy-caddy.yml). # # serfriz/caddy-cloudflare-ddns bundles caddy-dns/cloudflare (DNS-01 ACME), # caddy-cloudflare-ip (real client IPs behind CF), caddy-dynamicdns.