feat(open-notebook): Quadlet pair — app + SurrealDB on a private bridge
Two containers, because that is what upstream runs (docker-compose.yml:6,13): the app image under supervisord, plus SurrealDB. The `v1-latest-single` tag is not in play — it bundles its own database, is deprecated upstream, and would give the host a second hidden DB. Both images are digest-pinned with Pull=never and a documented one-time pull, the frank-dragoon posture: a Quadlet on :latest re-resolves on the first restart after an unrelated daemon-reload, and the failure is silent. Port map paid for by what is already on the host: seerr holds 127.0.0.1:5055 (since 2026-10-14, live `ss -ltn` agrees), so the API leaves on 5056 and the env file has to set API_URL to match, because the frontend derives the API URL from the window origin when it is unset. 8502 publishes on 0.0.0.0 — the service carries its own password and the episode-audio route is unauthenticated. 8000 is published nowhere, where upstream exposes it on loopback with root credentials. The app joins dankclaw_default first and the private systemd-open-notebook second: it reaches the published fleet endpoints through the former (host.containers.internal, the arrangement Prometheus is proven against) and finds its database by container name through the latter. Generated --network flags keep file order, so the tunable file is listed before the host secret file in both units — podman applies last-match-wins. No UserMapping: the app runs as root in its namespace (no USER in the Dockerfile) and writes only paths owned by that root. Exec= carries no database credential — `exec start --user --pass` puts it in world-readable argv; the image's own entrypoint passes it on stdin, so only the container env holds it.
This commit is contained in:
1 parent
f206ddfde5
commit
09b0badaf5
3 files changed
+219
No files matched your search
@@ -0,0 +1,84 @@
|
||||
# Quadlet unit: open-notebook's SurrealDB (rootless, boot-persistent).
|
||||
#
|
||||
# Install path on dankclaw: ~/.config/containers/systemd/open-notebook-surrealdb.container
|
||||
# Unit name: open-notebook-surrealdb.service (container name: open-notebook-surrealdb)
|
||||
#
|
||||
# WHAT IT IS. open-notebook's only datastore: notebooks, notes, sources,
|
||||
# provider credentials and podcast episodes all live here. The app talks to it
|
||||
# over the WebSocket RPC on the private bridge (SURREAL_URL in
|
||||
# scripts/open-notebook.env), which is why the container NAME is part of the
|
||||
# contract — renaming it breaks the app's database URL, not just this file.
|
||||
#
|
||||
# IMAGE. `surrealdb/surrealdb:v2` pinned by manifest-list digest, resolved from
|
||||
# the registry on 2026-10-18 (that digest is SurrealDB 2.7.0, image built
|
||||
# 2026-09-23; `v2` itself is a MOVING tag, so the digest — not the tag — is what
|
||||
# makes this reproducible). Upstream open-notebook's own compose file pins the
|
||||
# same `v2` tag, and its Dockerfile copies /surreal out of the same image for
|
||||
# the retired single-container variant, so v2 is the version this app is built
|
||||
# against. Pull=never + the documented pull step (docs: [[Open Notebook]]):
|
||||
#
|
||||
# podman pull docker.io/surrealdb/surrealdb:v2@sha256:e5d7387e3727bbe2f7cd6d869e48cd34777272258572ead37c9656b25f745723
|
||||
#
|
||||
# NO PUBLISHED PORT. 8000 is deliberately not published: the app reaches it by
|
||||
# name on systemd-open-notebook, and a published 8000 would put the RPC with
|
||||
# the root credentials on every interface (upstream publishes 127.0.0.1:8000
|
||||
# "purely for local debugging" — a `podman exec` probe is enough here). The
|
||||
# binary binds 0.0.0.0:8000 INSIDE its own namespace via the image's
|
||||
# SURREAL_BIND env; without that image default it would bind loopback only and
|
||||
# the app could never connect, so no --bind flag belongs here.
|
||||
#
|
||||
# CREDENTIALS, AND WHY THEY ARE NOT IN THE COMMAND LINE. The `surreal start`
|
||||
# CLI reads its options from the environment (`--username` is [env:
|
||||
# SURREAL_USER=], `--password` is [env: SURREAL_PASS=] — verified against the
|
||||
# pinned image's own `start --help`), so the credentials arrive through the two
|
||||
# EnvironmentFile lines and the argv stays clean. Passing `--pass` instead —
|
||||
# what upstream's compose does — would put the database password in
|
||||
# /proc/<pid>/cmdline, readable by any `ps` on the host. Note the two
|
||||
# spellings: the DATABASE reads SURREAL_PASS, the APP reads SURREAL_PASSWORD,
|
||||
# and scripts/open-notebook.env declares both because they must hold the same
|
||||
# value; the deployer writes that pair when it seeds the host secret file.
|
||||
#
|
||||
# NO USER= OVERRIDE. The image runs as uid 65532 (Chainguard glibc-dynamic
|
||||
# base). With a NAMED volume that works unmodified: podman creates the volume's
|
||||
# backing directory owned by the container's user (measured with this digest —
|
||||
# the host directory lands on the mapped subuid and the store starts clean),
|
||||
# which is exactly why upstream's `user: root` line is not repeated here: that
|
||||
# line is commented in upstream as being "required for bind mounts", and this
|
||||
# unit has none. The consequence is that the DB files are NOT readable by dank
|
||||
# without `podman unshare` — accepted for a datastore nobody edits by hand;
|
||||
# episode AUDIO is a host directory for the opposite reason (see the app unit).
|
||||
#
|
||||
# HEALTH. The base image is distroless: no shell, no curl, no wget, so no
|
||||
# CMD-SHELL probe and no wget/curl probe is possible here (same class of unit
|
||||
# as loki, whose probe had to move into the image). SurrealDB ships its own
|
||||
# probe instead — `surreal is-ready` connects to ws://localhost:8000 by default
|
||||
# and needs no credentials — verified against this digest: it prints `OK` and
|
||||
# exits 0 while the server is up. Argument-only HealthCmd, like every unit on
|
||||
# this host, because the quadlet generator splits it on spaces.
|
||||
|
||||
[Unit]
|
||||
Description=SurrealDB (open-notebook datastore)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
ContainerName=open-notebook-surrealdb
|
||||
Image=docker.io/surrealdb/surrealdb:v2@sha256:e5d7387e3727bbe2f7cd6d869e48cd34777272258572ead37c9656b25f745723
|
||||
Pull=never
|
||||
Network=open-notebook.network
|
||||
EnvironmentFile=/home/dank/serverAppConfigs/scripts/open-notebook.env
|
||||
EnvironmentFile=/home/dank/Containers/open-notebook/env
|
||||
Exec=start --log info rocksdb:/mydata/mydatabase.db
|
||||
Volume=open-notebook-db:/mydata
|
||||
HealthCmd=/surreal is-ready
|
||||
HealthInterval=30s
|
||||
HealthTimeout=10s
|
||||
HealthRetries=3
|
||||
HealthStartPeriod=20s
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
Reference in new issue
Block a user