diff --git a/plans/2026-06-24-trmnl-plugins-lxc.md b/plans/2026-06-24-trmnl-plugins-lxc.md new file mode 100644 index 0000000..c38f9f3 --- /dev/null +++ b/plans/2026-06-24-trmnl-plugins-lxc.md @@ -0,0 +1,107 @@ +# 2026-06-24 — TRMNL plugins LXC (128) + middleware deploy pipeline + +## Goal + +Stand up a dedicated LXC to host self-hosted **middleware for TRMNL e-ink plugins**. +TRMNL cloud polls `https://trmnl.hubris.network//dashboard` every 15 min; the +middleware fetches/shapes live data and returns JSON that TRMNL merges into the plugin's +Liquid template. First consumer: the Munich Home Dashboard (`/munich-home/dashboard`). +One LXC + one FastAPI service hosts all current and future plugins (router per plugin). + +Source repo: gitea `dtoro/terminalito` (app code). This repo only documents the fabric +wiring, same split as Artifacto/Plato. + +## Current state + +- No TRMNL middleware in the lab. Highest LXC id is 127 (see `containers/index.md`). +- Public hostnames terminate at the [VPS netbird traefik](../hosts/netbird-vps.md) → netbird + mesh → [caddy (121)](../containers/121-caddy.md) → backend LXC. Cert obtained by Caddy + (IONOS DNS-01) and mirrored to the VPS by the daily cert-sync timer on the host. +- Auto-deploy pipelines are gitea-webhook driven, two shapes (see [auto-deploy](../infrastructure/auto-deploy.md)). + +## Target state + +``` +TRMNL cloud --GET 15m, Bearer token--> https://trmnl.hubris.network/munich-home/dashboard + VPS traefik (public TLS) --netbird--> caddy (121) --> trmnl (128) :9851 trmnl-plugins.service + ├ Open-Meteo (weather) + ├ MVG departures (transit) + └ Google Calendar (OAuth, SOPS) +``` + +- **LXC 128 `trmnl`**: Debian, unprivileged, ~1 core / 512 MiB–1 GiB / 8 GiB rootfs. No mounts. +- **Service** `trmnl-plugins.service` → `uvicorn server.app:app --host 0.0.0.0 --port 9851`, + `EnvironmentFile=/etc/trmnl-plugins/env`. Auth: every path except `/health` requires + `Authorization: Bearer $TRMNL_POLL_TOKEN`. +- **Auto-deploy** (Shape B): `/opt/terminalito` working tree, sibling `/opt/terminalito-deploy/`. +- Public hostname `trmnl.hubris.network`. + +## Pre-flight checklist + +- [ ] Confirm next free LXC id is 128 (`homelab list`, `containers/index.md`). +- [ ] Decide IP on `192.168.8.0/16` LAN (e.g. `192.168.8.211`) — pick a free one. +- [ ] Have Google OAuth client + refresh token, MVG stop globalIds, and a generated + `trmnl_poll_token` ready for the secret (see `dtoro/terminalito` README). + +## Step-by-step procedure + +1. **Provision + enroll** + ```bash + # create LXC 128 trmnl on hubris (Debian), then enroll it: + homelab client add trmnl # joins netbird, provisions /etc/age/key.txt, edits inventory.yaml + homelab client add --finalize-pubkey # commits the age pubkey + ssh trmnl 'apt-get install -y python3-venv git' + ``` + +2. **Secret** (`trmnl-oauth`): create `secrets/trmnl-oauth.yaml` with + `google_client_id/secret/refresh_token`, MVG stop ids, and `trmnl_poll_token`; add a + `path_regex` rule in `.sops.yaml` granting **trmnl**'s age pubkey; `sops updatekeys`. + The service reads it at deploy time via `homelab secret trmnl-oauth` → `/etc/trmnl-plugins/env`. + +3. **App + service** on LXC 128 (clone uses the **internal** gitea URL — `git.hubris.network` + resets from inside LXCs): + ```bash + git clone http://192.168.8.121:3000/dtoro/terminalito.git /opt/terminalito + python3 -m venv /opt/terminalito/server/.venv + /opt/terminalito/server/.venv/bin/pip install -r /opt/terminalito/server/requirements.txt + # install /etc/systemd/system/trmnl-plugins.service, enable --now + ``` + +4. **Deploy pipeline** (Shape B, mirrors homelab-mcp): create `/opt/terminalito-deploy/` + `{webhook.py,deploy.sh}` (HMAC vs `/etc/terminalito-deploy/secret`, filter `refs/heads/main`, + `git pull` → `pip install -r server/requirements.txt` → rebuild env from `homelab secret` → + `systemctl restart trmnl-plugins`). Receiver `:9797`. Git creds at + `/etc/terminalito-deploy/git-credentials` (mode 600). Register a gitea webhook on + `dtoro/terminalito`; add `192.168.8.<128-ip>` to gitea `app.ini` `ALLOWED_HOST_LIST`. + +5. **DNS**: add `trmnl.hubris.network` A → `192.168.8.175` (caddy) on [Technitium (107)](../containers/107-dns.md). + +6. **Caddy** (`dtoro/caddy-conf`, commit+push auto-deploys): + ``` + trmnl.hubris.network { reverse_proxy 192.168.8.<128-ip>:9851 } + ``` + +7. **Public exposure** on the [VPS](../hosts/netbird-vps.md): add traefik router+service for + `Host(\`trmnl.hubris.network\`)` → `http://192.168.8.<128-ip>:9851`; add the host to the + cert-sync map so the LE cert mirrors over. + +8. **TRMNL cloud**: create a Polling private plugin, URL `…/munich-home/dashboard`, header + `Authorization: Bearer `, refresh 15 min; paste `full.liquid`; add to a playlist. + +## Verification + +- `ssh trmnl systemctl is-active trmnl-plugins` → `active`; `curl -s localhost:9851/health` → `ok`. +- LAN: `curl -s -H "Authorization: Bearer " https://trmnl.hubris.network/munich-home/dashboard` + → 200 JSON; without the header → 401; `/health` → 200. +- Public: same curl from off-mesh resolves via VPS, 200. +- Push a no-op commit → `/opt/terminalito-deploy` logs show pull+restart; webhook 202. +- `homelab mcp get_host trmnl` and `search_docs trmnl` agree with `containers/128-trmnl.md`. + +## Post-migration + +When executed, write changelog entries (same date) on: +`containers/128-trmnl.md` (new page), `containers/index.md` (row), `inventory.yaml` +(`services.trmnl`), `infrastructure/auto-deploy.md` (pipeline row + `ALLOWED_HOST_LIST` + +changelog), `containers/104-gitea.md` (webhook + allowed host), `containers/121-caddy.md` +(new proxied host), `containers/107-dns.md` (A record), `hosts/netbird-vps.md` (public route + +cert-sync). Then set this plan's status to `Done` in `plans/index.md`. diff --git a/plans/index.md b/plans/index.md index 9aab22c..70cf1e0 100644 --- a/plans/index.md +++ b/plans/index.md @@ -6,6 +6,7 @@ Pre-flight runbooks for planned changes that haven't happened yet. Once executed | Date | Title | Status | | ---- | ----- | ------ | +| 2026-06-24 | [TRMNL plugins LXC (128) + middleware deploy pipeline](2026-06-24-trmnl-plugins-lxc.md) | Planned | | 2026-06-01 | [Slate AX → SODOLA managed switch migration](2026-06-01-slate-ax-to-sodola-migration.md) | Done | ## Conventions