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.
107 lines
6.3 KiB
Bash
107 lines
6.3 KiB
Bash
# open-notebook.env — configuration for **open-notebook**, the fleet's podcast
|
|
# generator on Dankclaw (user path https://notebook.franksdank.cloud; UI host
|
|
# port **8502**, API host port **5056 loopback-only**). Read by
|
|
# BOTH Quadlet units (dankclaw/open-notebook/quadlet/) as the FIRST
|
|
# EnvironmentFile= line, which is why every path and host name below is the
|
|
# value the CONTAINER must see.
|
|
#
|
|
# TWO FILES, ONE ORDER. This file is tracked; it holds knobs, not credentials.
|
|
# The three secret keys below are declared EMPTY on purpose — the `scripts/*.env`
|
|
# gitignore exception exists for "paths and numbers, never credentials" — and the
|
|
# real values live in the host file the deployer seeds at
|
|
# /home/dank/Containers/open-notebook/env (mode 600, seeded from dankclaw/.env,
|
|
# the same convention grafana/marreta/pihole follow). Both files are handed to
|
|
# podman in EnvironmentFile= order and the LAST one wins (verified against the
|
|
# generated run line), so the host file overrides these blanks. Do not reorder
|
|
# the two lines.
|
|
#
|
|
# WHY THE DEPLOYER REFUSES TO START THESE UNITS WITHOUT THE SECRET FILE. An empty
|
|
# OPEN_NOTEBOOK_PASSWORD is not "unconfigured", it is **authentication off**, and
|
|
# the episode-audio download is one of the routes the API serves without a
|
|
# session; an empty SURREAL_PASS lets the database come up with different root
|
|
# credentials than the app will present; an empty OPEN_NOTEBOOK_ENCRYPTION_KEY
|
|
# means no provider credential can be saved at all. So the guard is in
|
|
# deploy-remote.sh (seed_open_notebook_env), not in the app's opinion.
|
|
#
|
|
# THE CPU-SHUTDOWN GUARD (the two knobs that matter most on this box). Defaults
|
|
# are TTS_BATCH_SIZE=5 and OPEN_NOTEBOOK_WORKER_MAX_TASKS=5 — five parallel
|
|
# synthesis requests from five parallel jobs. frank-tts answers each request by
|
|
# spawning its own engine process (~8 GiB resident, 8 threads, measured in the
|
|
# frank-tts CPU trial of 2026-10-18), so the defaults mean ~40 GiB and forty
|
|
# threads aimed at a machine that also serves frank-dragoon and frank-zealot and
|
|
# whose TTS engine runs on the SAME CPU. Both numbers are 1 here: one episode at
|
|
# a time, one voice line synthesized at a time. That is a throughput choice, not
|
|
# a correctness one — a podcast is a background job — and it is the difference
|
|
# between a slow render and a box that stops answering the text endpoints
|
|
# mid-sentence. Raise them only with a TTS engine that batches internally.
|
|
#
|
|
# THE TTS CLOCK. ESPERANTO_TTS_TIMEOUT is the worker's patience per DIALOGUE
|
|
# LINE, not per episode: one episode is dozens of POSTs to /v1/audio/speech
|
|
# (the app synthesises each speaker turn separately and concatenates the clips
|
|
# with ffmpeg afterwards), and a CPU engine takes single-digit seconds for a
|
|
# line, with the tail of a long turn well past the 300 s default. 660 sits one
|
|
# round-trip OUTSIDE SHIM_TIMEOUT=600 in scripts/frank-tts.env — the shim's own
|
|
# per-request ceiling — so a frank-tts give-up arrives as frank-tts's error and
|
|
# not as open-notebook abandoning a synthesis still in flight. An abandoned
|
|
# request does not cancel the engine process; it just leaves the episode stuck in
|
|
# "generating" while the audio arrives.
|
|
#
|
|
# THE SCRIPTWRITER. ESPERANTO_LLM_TIMEOUT is the app's own default (180 s), kept
|
|
# visible beside the TTS number because both belong to one podcast run: the
|
|
# outline call and the transcript call are the slow text work, and frank-zealot
|
|
# answers them well inside 180. Provider choice is made in the UI, not here —
|
|
# see [[Open Notebook]] for the one-time credential provisioning — and it is
|
|
# frank-zealot on purpose: script writing asks for a JSON-schema response, which
|
|
# the OpenAI-compatible llama.cpp endpoint is known to serve, while halogen's
|
|
# structured-output behaviour on frank-dragoon is UNVERIFIED. halogen remains
|
|
# the unproven alternative, not the fallback.
|
|
#
|
|
# THE DATABASE COORDINATES. SURREAL_URL addresses the sibling container BY NAME
|
|
# on systemd-open-notebook (container DNS is scoped to the owning network, so
|
|
# this is the only form that works). NAMESPACE/DATABASE/USER are plain names.
|
|
# SURREAL_PASSWORD and SURREAL_PASS are ONE value under two spellings: the app
|
|
# reads SURREAL_PASSWORD, the `surreal` CLI reads SURREAL_PASS. Keep them equal;
|
|
# the deployer writes both into the seeded file from a single SURREAL_PASSWORD in
|
|
# dankclaw/.env, so hand-editing one line is the only way to desync them.
|
|
|
|
SURREAL_URL=ws://open-notebook-surrealdb:8000/rpc
|
|
SURREAL_NAMESPACE=open_notebook
|
|
SURREAL_DATABASE=open_notebook
|
|
SURREAL_USER=open_notebook
|
|
# Secrets — filled on the host, never here (see the header).
|
|
SURREAL_PASSWORD=
|
|
SURREAL_PASS=
|
|
OPEN_NOTEBOOK_ENCRYPTION_KEY=
|
|
OPEN_NOTEBOOK_PASSWORD=
|
|
|
|
# WHERE THE BROWSER FINDS THE API. The guarded vhost
|
|
# (notebook.franksdank.cloud → Caddy → 8502) is the only user path, and the
|
|
# image's own Next.js layer rewrites `/api/*` to the FastAPI running beside it,
|
|
# so ONE origin serves UI and API and API_URL has to name that vhost. This is
|
|
# not cosmetic: unset, the app auto-detects `<host-you-opened>:5055` — the port
|
|
# upstream ships, and the one seerr holds on this host — and hands the browser
|
|
# an address nothing answers; pointed at the LAN `:5056` instead, every call
|
|
# leaves the guarded origin and an authenticated browser session does not ride
|
|
# it. Same-origin traffic needs no CORS at all, but CORS_ORIGINS stays set to
|
|
# the one origin because the app warns at start while it is unset and defaults
|
|
# to `*` — a wildcard in front of an endpoint that serves every episode's
|
|
# audio. No trailing slash: the UI concatenates `${API_URL}/api/...`.
|
|
API_URL=https://notebook.franksdank.cloud
|
|
CORS_ORIGINS=https://notebook.franksdank.cloud
|
|
|
|
# Concurrency: see "THE CPU-SHUTDOWN GUARD" above. Defaults are 5 and 5.
|
|
TTS_BATCH_SIZE=1
|
|
OPEN_NOTEBOOK_WORKER_MAX_TASKS=1
|
|
|
|
# Timeouts: see "THE TTS CLOCK" and "THE SCRIPTWRITER" above.
|
|
ESPERANTO_TTS_TIMEOUT=660
|
|
ESPERANTO_LLM_TIMEOUT=180
|
|
|
|
# Heavy extraction runtimes stay OFF, which is also what keeps boot fast: each
|
|
# one installs a multi-gigabyte ML/Chromium stack into the image's venv on first
|
|
# startup (cached on the /app/data volume after that). Podcast generation reads
|
|
# notes and sources that are already text, so neither is needed here; enable one
|
|
# deliberately, per the upstream docs, if source ingestion starts to matter.
|
|
# OPEN_NOTEBOOK_ENABLE_DOCLING=true
|
|
# OPEN_NOTEBOOK_ENABLE_CRAWL4AI=true
|