mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Phase 7: Cosmos deployment
- Dockerfile with a check target (typecheck and tests) and one image per service on a pinned Node 22.20 Alpine base, each with only its own production dependencies and a health check. - deploy/compose.yaml: gk-content, gk-admin and gk-site on homelab_network with no host ports, read-only root filesystems, no capabilities, the vault's Site folder mounted into the content service, and backup.dump=sqlite on it. - deploy/deploy.sh builds and checks the commit, restarts the stack, and goes back to the last good tag if it doesn't come up healthy; rollback.sh swaps tags. .gitea/workflows/deploy.yml runs it on a host runner on Cosmos. - docs/deploy.md runbook (settings, runner, mirror, NPM hosts, Turnstile, Healthchecks, backups, cutover) and the stack's inventory note in vault-kit. - Site: trusts X-Forwarded-Host/Proto for gregkilmer.com hosts so form posts pass Astro's origin check behind the proxy; rate limits read CF-Connecting-IP; site URL set for absolute links. - Content service: the held-change webhook carries a Discord-ready message; .env.example lists the core settings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
+114
@@ -0,0 +1,114 @@
|
||||
# Deploying on Cosmos
|
||||
|
||||
The stack is three containers on `homelab_network`, none with a host port:
|
||||
|
||||
| Container | Image target | Port | Reached as | Who can reach it |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `gk-content` | `content` | 4310 | `content.gregkilmer.com` (API and `/mcp`), `http://gk-content:4310` on homelab_network | Tailnet only |
|
||||
| `gk-admin` | `admin` | 4320 | `admin.gregkilmer.com` | Tailnet only |
|
||||
| `gk-site` | `site` | 4321 | `beta.gregkilmer.com` until cutover, then `gregkilmer.com` | Tailnet only until cutover, then public through Cloudflare |
|
||||
|
||||
The old site keeps serving gregkilmer.com until phase 8 (cutover).
|
||||
|
||||
Files in the repo:
|
||||
|
||||
- `Dockerfile`: one target per service, plus `check` (typecheck and every test). Node is pinned to `node:22.20.0-alpine`.
|
||||
- `deploy/compose.yaml`: the stack. Containers run as the vault's owner, with a read-only root filesystem, no capabilities and `no-new-privileges`.
|
||||
- `deploy/deploy.sh`: builds `check`, then the three images tagged with the commit, and restarts the stack. If the new containers aren't healthy within two minutes, it puts the last good tag back. Run it again on the same commit and it does nothing (`FORCE=1` rebuilds).
|
||||
- `deploy/rollback.sh`: switches to the previous tag. Run it again to roll forward.
|
||||
- `.gitea/workflows/deploy.yml`: runs `deploy.sh` on Cosmos for each new commit to master.
|
||||
|
||||
## On Cosmos
|
||||
|
||||
Everything lives in `/home/greg/homelab/gregkilmer`:
|
||||
|
||||
```
|
||||
/home/greg/homelab/gregkilmer/
|
||||
compose.yaml copied from deploy/compose.yaml by every deploy
|
||||
.env GK_TAG, GK_PREVIOUS_TAG, VAULT_SITE, PUID, PGID
|
||||
content.env content service secrets and sync settings
|
||||
admin.env ADMIN_TOKEN
|
||||
site.env site secrets: tokens, Turnstile, SMTP
|
||||
data/content/ content.db and submissions/ (backed up)
|
||||
```
|
||||
|
||||
Kopia already covers `/home/greg`, so the `.env` files and `data/` are in the encrypted backups.
|
||||
|
||||
## First-time setup
|
||||
|
||||
1. **Folder and settings.** Run these from a checkout of the repo:
|
||||
|
||||
```sh
|
||||
mkdir -p /home/greg/homelab/gregkilmer/data/content
|
||||
cd /home/greg/homelab/gregkilmer
|
||||
cp <repo>/deploy/stack.env.example .env
|
||||
cp <repo>/services/content/.env.example content.env
|
||||
cp <repo>/deploy/admin.env.example admin.env
|
||||
cp <repo>/services/site/.env.example site.env
|
||||
chmod 600 .env *.env
|
||||
```
|
||||
|
||||
In `.env`, check that `PUID` and `PGID` match `id -u greg` and `id -g greg`, so the content service can write notes back into the vault.
|
||||
|
||||
2. **Tokens.** Make one per role with `openssl rand -hex 24`:
|
||||
- In `content.env`, set `CONTENT_TOKENS=reader:<r>,admin:<a>,intake:<i>,agent:<g>`.
|
||||
- In `admin.env`, set `ADMIN_TOKEN=<a>`.
|
||||
- In `site.env`, set `CONTENT_TOKEN=<r>` and `CONTENT_INTAKE_TOKEN=<i>`.
|
||||
- Give agents `<g>`.
|
||||
|
||||
Leave `CONTENT_URL` out of `site.env`; compose sets it.
|
||||
|
||||
3. **The rest of the settings.** Fill in Healthchecks, the Discord webhook, Turnstile, SMTP and the sync logins. The list is in `/mnt/project-files/website/phase6/setup-on-cosmos.md` and in each `.env.example`.
|
||||
|
||||
4. **Gitea runner.** The workflow needs an `act_runner` on Cosmos with the label `cosmos` running in host mode (`cosmos:host`). It runs as a user in the `docker` group with write access to `/home/greg/homelab/gregkilmer`.
|
||||
- A runner with the Docker socket is root-equivalent. Register it for this repository only, not for the whole instance.
|
||||
|
||||
5. **Gitea mirror.** Mirror `github.com/Patiun/com.gregkilmer.www` into Gitea as a pull mirror and turn Actions on for it.
|
||||
- A pull mirror's sync may not raise a push event. The workflow therefore also runs every 15 minutes and deploys only when master has moved.
|
||||
- Check once that a new commit reaches Cosmos. If the schedule doesn't fire on a mirror either, run the workflow by hand from the Actions tab.
|
||||
|
||||
6. **First deploy.** Run it from the Actions tab, or by hand from a checkout: `./deploy/deploy.sh`. Then check:
|
||||
- `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml ps` shows three healthy containers.
|
||||
- `docker logs gk-content` says `Content service on 0.0.0.0:4310: N notes`.
|
||||
|
||||
7. **Nginx Proxy Manager.** Add a proxy host for each hostname. Force SSL on each. For `content`, add `proxy_buffering off;` under Advanced, because MCP streams its responses.
|
||||
|
||||
| Host | Forward to | Access list |
|
||||
| --- | --- | --- |
|
||||
| `admin.gregkilmer.com` | `http://gk-admin:4320` | Tailnet only |
|
||||
| `content.gregkilmer.com` | `http://gk-content:4310` | Tailnet only |
|
||||
| `beta.gregkilmer.com` | `http://gk-site:4321` | Tailnet only |
|
||||
|
||||
- DNS: an A record for each pointing at 100.99.111.16 (TTL 300), the same as other tailnet hosts.
|
||||
- Certificates: Let's Encrypt can't reach a tailnet-only host over HTTP, so use NPM's DNS challenge with your DNS provider. This is the same open item as the `*.patiun.com` certificate.
|
||||
- Agents already on homelab_network can skip NPM and use `http://gk-content:4310/mcp`.
|
||||
|
||||
8. **Turnstile.** Add `beta.gregkilmer.com` and `gregkilmer.com` to the widget's hostnames.
|
||||
|
||||
9. **Healthchecks.** Create these checks:
|
||||
- one for the content service's full scan (`HEALTHCHECK_URL`), every 5 minutes;
|
||||
- one per sync job you turn on (Archidekt, Suwayomi, Bambuddy and Homebox hourly; BGG and Kavita daily; covers every 15 minutes).
|
||||
|
||||
10. **Inventory note.** Copy `vault-kit/24.01_PersonalWebsite/GregKilmer.com stack.md` into the vault's `24.01_PersonalWebsite` folder, so `cosmos-inventory` stops listing the containers as undocumented.
|
||||
|
||||
## Backups
|
||||
|
||||
- `gk-content` carries `backup.dump=sqlite`. `cosmos-dump` finds `content.db` in its `/data` mount and copies it with `VACUUM INTO` before Kopia runs.
|
||||
- `content.db` holds note history, the review queue, saved inquiries and orders, and the sync cache.
|
||||
- The vault mount has no SQLite files, so nothing else is dumped.
|
||||
- Form attachments are plain files in `data/content/submissions/`, which Kopia picks up with the rest of `/home/greg`.
|
||||
- The plan called for a cache folder in `exclusions.conf`. None is needed: the sync cache and search index live inside `content.db` and are small.
|
||||
- `gk-admin` and `gk-site` keep no data.
|
||||
|
||||
## Cutover (phase 8)
|
||||
|
||||
1. In NPM, point `gregkilmer.com` and `www.gregkilmer.com` at `http://gk-site:4321` with no access list, and retire the old `greg-profile-site` container.
|
||||
2. Turn on Cloudflare's proxy for both records with SSL mode Full (strict). Cloudflare doesn't cache HTML by default, which is what the site needs: pages change within a minute of a note changing.
|
||||
3. The site already trusts `X-Forwarded-Host` and `X-Forwarded-Proto` for gregkilmer.com and its subdomains (`security.allowedDomains` in `services/site/astro.config.mjs`), so form posts pass Astro's origin check behind the proxy. It reads the visitor's address from `CF-Connecting-IP` for the form rate limits.
|
||||
|
||||
## Day to day
|
||||
|
||||
- **Deploy:** merge to master on GitHub; Gitea mirrors it and the workflow deploys it.
|
||||
- **Roll back:** `GK_STACK_DIR=/home/greg/homelab/gregkilmer ./deploy/rollback.sh` from a checkout, or set `GK_TAG` in `.env` to any image tag still on the host and run `docker compose up -d`.
|
||||
- **Logs:** `docker logs gk-content`. Sync runs also appear on the admin page's Overview.
|
||||
- **Restart one service:** `docker compose -f /home/greg/homelab/gregkilmer/compose.yaml restart gk-site`.
|
||||
Reference in New Issue
Block a user