feat(open-notebook): put the podcast generator behind the guard chain

The service came up internal-only: UI on the LAN at 8502, API at 5056, both
published on every interface, with only its own shared password in front. That
put the episode library outside the fleet's auth chain and one port scan away
from anything on the network.

- Caddy: guarded vhost notebook.franksdank.cloud (Anubis -> Authentik; provider
  notebook-proxy, internal_host caddy:8017) plus the post-auth route to the
  published UI port. One upstream is enough, because the image's own Next.js
  layer rewrites /api/* to the FastAPI beside it — so the API needs no outward
  face at all, and 5056 moves to loopback, where only the deployer's health gate
  dials it.
- scripts/open-notebook.env: API_URL and CORS_ORIGINS become the single guarded
  origin. Left to itself the app auto-detects <host>:5055, the port seerr holds
  on this host; a LAN-origin API_URL would also take the browser around the
  guard chain, where its authenticated session does not ride.
- addn-hosts: the LAN A record. The homepage card goes live and links the vhost.
- Seam: the API origin is derived from whatever the Caddyfile routes to 8502, so
  renaming the vhost moves the test instead of leaving it blessing a stale name,
  and a new test proves the vhost hands the WHOLE request to Anubis.
This commit is contained in:
Frank committed 2026-10-19 16:13:53 -07:00
1 parent 933420fd29
commit e5580abb6f
6 files changed
+125 -46

No files matched your search

+29
View File
@@ -162,6 +162,25 @@ marreta.franksdank.cloud {
}
}
# --- Open Notebook (guarded: Anubis → Authentik; notebook-proxy provider, internal_host caddy:8017) ---
# The podcast generator. ONE upstream carries the whole app: the image's own
# Next.js layer rewrites /api/* to the FastAPI beside it (INTERNAL_API_URL,
# default http://localhost:5055 inside the container), so a browser on this
# vhost never needs the API port. That is why scripts/open-notebook.env sets
# API_URL to exactly this origin — left unset, the app auto-detects
# `<host-you-opened>:5055`, and 5055 on this host belongs to seerr — and why
# the published API port sits on loopback. App-level login
# (OPEN_NOTEBOOK_PASSWORD) stays on behind the chain: two owners, edge identity
# and the app's own session.
# Dialled by published LAN port, not container name: open-notebook is a
# rootless quadlet and this Caddy lives in root's storage, so root's DNS does
# not see the container (the same reason marreta and SearXNG are dialled by IP).
notebook.franksdank.cloud {
reverse_proxy anubis:8923 {
header_up X-Real-Ip {remote_host}
}
}
# --- Pi-hole (quadlet on slirp4netns; published 8083) ---
pihole.franksdank.cloud {
basicauth {
@@ -420,6 +439,16 @@ http://:8017 {
handle @marreta {
reverse_proxy 192.168.0.143:8082
}
@notebook host notebook.franksdank.cloud
handle @notebook {
# Bare proxy, like ai.franksdank.cloud: the app's SSE answers
# (/api/search/ask, chat sessions) stream for minutes through this
# same listener with no flush or timeout tuning, so nothing here
# needs it — a read/write timeout on the route would be the thing
# that cuts them.
reverse_proxy 192.168.0.143:8502
}
}
# --- Optional: Jackett ---