Initial documentation of the hubris Proxmox homelab as a cross-linked markdown wiki. Per-node pages, cross-cutting infrastructure pages, an investigation log, and an operations cheatsheet. Each node and topic ends with a Changelog section so changes can be tracked in-place going forward. Refreshed against live state on 2026-04-28 — 14 active LXCs (109 syncthing currently stopped) + 1 VM (108 haos). Reflects post-A/B-test state of the 2026-04-21 hubris crash-loop investigation.
7.3 KiB
105 — apps
Docker host for everything that doesn't justify its own LXC. Currently runs Artifacto, Booklore, PlantUML server, Portainer (and historically WriteFreely / blog).
At a glance
- Hostname:
apps - IP:
192.168.8.205 - Privilege: privileged (so container UID = host UID for bind-mount sanity)
- Resources: 2 cores / 4 GiB RAM / 30 GiB rootfs
- Mounts:
/mnt/library↔/mnt/library - Public hostnames: see table below
Public hostname → service map
| Hostname | Container | Backend port | Notes |
|---|---|---|---|
docker.hubris.network |
Portainer | :9443 |
Native OAuth2 via Authentik. Trusted-origins requires hostname only (no scheme/port). |
books.hubris.network |
Booklore | :6060 |
Native OIDC. Redirect URI /oauth2-callback. |
artifacto.hubris.network |
Artifacto | :3100 |
Public /p/*, /static/*, /healthz exposed via VPS traefik. |
blog.hubris.network |
WriteFreely | :8080 |
Native OIDC via [oauth.generic]. |
git.hubris.network/_plantuml/* |
PlantUML server | :8079 |
Same-origin route from gitea (104). |
Stacks
Artifacto (/opt/artifacto/)
Self-hosted HTML-artifact share (Go + SQLite). Repo dtoro/Artifacto is the working tree itself — git push origin main triggers rebuild.
- Compose: tracked
docker-compose.ymlbinds./data:/dataand127.0.0.1:3100:3000.docker-compose.override.ymluses the!overrideYAML tag onvolumes:andports:to redirectdatato/mnt/library/documents/artifacts. - Data dir:
/mnt/library/documents/artifacts— owner UID 10001 : GID 10001 (matches theappuser baked into the Dockerfile), mode 750. Parent/mnt/library/documentsstayswww-data:www-data 750; bind targets the child directly so no traversal needed. .env(untracked) holdsADMIN_PASSWORD,SESSION_SECRET(openssl rand -hex 32),BASE_URL=https://artifacto.hubris.network,SSO_GATEWAY_SECRET(must matchARTIFACTO_GATEWAY_SECRETin/etc/caddy/caddy.envon caddy). Don'tgit checkout .env.- Auth: Authentik forward-auth on the admin surface (
/,/login,/a/*,/api/*).X-Authentik-Usernameis honored only when theX-Artifacto-Gatewayheader matchesSSO_GATEWAY_SECRET— prevents direct LAN connections from spoofing identity. - Public exposure: VPS traefik gates path scope to share URLs; admin surface returns 404 at the edge. See ingress.
Auto-deploy pipeline (Artifacto)
Receiver at /opt/artifacto-deploy/ (outside the app repo): deploy.sh + webhook.py, secret in /etc/artifacto-deploy/secret, systemd unit artifacto-deploy-webhook.service. Listens on 0.0.0.0:9798, validates X-Gitea-Signature HMAC-SHA256, filters to refs/heads/main, runs deploy in a daemon thread (returns 202 immediately). Gitea webhook id 7 on dtoro/Artifacto.
- Logs:
pct exec 105 -- journalctl -u artifacto-deploy-webhook -f - Manual deploy:
pct exec 105 -- /opt/artifacto-deploy/deploy.sh - Health:
pct exec 105 -- curl -s http://127.0.0.1:9798/health→ok - Slug length bumped 5 → 8 chars (in
internal/store/artifacts.go:41) once the endpoint went public.
PlantUML (/opt/plantuml/)
plantuml/plantuml-server:jetty listening on 192.168.8.205:8079, context path /plantuml (BASE_URL=plantuml). Reached only via gitea's caddy handle_path /_plantuml/* rewrite — no DNS record of its own. Same-origin from gitea so the embedded markdown previewer can call it without CORS.
Portainer
Native OAuth2 (Settings → Authentication → OAuth → Custom). Manual endpoints (no OIDC discovery). Uses portainer-uid custom-claim scope from Authentik. Container is not compose-managed — safe to docker run recreate; data lives in named volume portainer_data. CLI flag: --trusted-origins docker.hubris.network (hostname only — IsTrustedOrigin rejects strings containing ://).
Booklore
Native OIDC via Authentik (Settings → OIDC). Redirect URI /oauth2-callback (NOT /api/oidc). Container needs extra_hosts: auth.hubris.network:192.168.8.175. Edit via Portainer UI if it's a Portainer-managed stack.
⚠️ Never
docker compose upPortainer-managed stacks from the host shell. Portainer's compose state lives at/var/lib/docker/volumes/portainer_data/_data/compose/<N>/. Runningdocker compose up -d <svc>from the host triggers recreates of OTHER services in the stack and silently destroys bind-mounted data. This wiped Booklore's mariadb data on 2026-04-22. Use the Portainer UI editor for compose changes. See mesh migration for the full warning.
WriteFreely (/opt/writefreely-docker/)
Native OIDC via [oauth.generic] in config/config.ini. host = https://auth.hubris.network, scope openid profile email, map_user_id = sub. Redirect URI in Authentik: https://blog.hubris.network/oauth/callback/generic. Container has extra_hosts: auth.hubris.network:192.168.8.175. First-time users with a local WF account must connect SSO via Customize/Settings → Linked Accounts.
Permissions
- LXC is privileged → in-container UID == host UID.
- Member of media GID 10000. Adding
mediato the LXC alone is not enough for inner Docker containers — each container needs--group-add 10000oruser: "<uid>:10000"to write into/mnt/library.
Related
- Gitea (104) — uses the PlantUML server
- Caddy (121)
- Authentik (124)
- DNS
- Auto-deploy
- Public ingress (Artifacto + blog)
Changelog
2026-04-28 — wiki entry created
Initial documentation.
2026-04-22 — Artifacto deployed + Authentik gateway-secret auto-login wired
Repo dtoro/Artifacto. Compose, data dir, .env, webhook (id 7), Caddyfile site block, gateway-secret pattern, public-internet share exposure on the VPS — all set up that day. Slug length bumped 5 → 8 for brute-force resistance.
2026-04-22 — Booklore mariadb data wiped (lesson, not a feature)
docker compose up -d from the host shell on a Portainer-managed stack recreated services and re-initialized the bind-mounted ./mariadb/config. Recovered by reseeding from snapshot. Rule recorded: edit Portainer-managed stacks via the Portainer UI.
2026-04-22 — Portainer wired into Authentik (native OAuth2)
Custom-claim portainer_uid scope. CLI added --trusted-origins docker.hubris.network.
2026-04-22 — app.ini host whitelist on gitea extended
Added 192.168.8.205 so the Artifacto webhook delivery wouldn't be rejected. See gitea (104).
2026-04-21 — Booklore + WriteFreely wired into Authentik OIDC
Native OIDC for both. extra_hosts override added inside each container so DNS resolves auth.hubris.network to the Caddy IP.
2026-04-20 — PlantUML server deployed
Used by gitea (104)'s footer.tmpl injection. Same-origin via caddy handle_path /_plantuml/* rewrite — no separate hostname.