Files
oikos/plans/done/2026-06-24-trmnl-plugins-lxc.md

108 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<plugin>/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.yaml) → netbird
mesh → [caddy (121)](../knowledge/wiki/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](../knowledge/wiki/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 MiB1 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 <age_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)](../knowledge/wiki/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.yaml): 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 <trmnl_poll_token>`, 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 <tok>" 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`.