Files
felhom.eu/documentation/audits/day-2026-10-08/design-R-717.md
T

4.3 KiB

R-717 — opengist's sign-up switch: its container has no tool to write it: a one-page design (2026-10-08)

Status: design only. Read today: controller main a0370b4ed8ef, catalog main 32d134639c0c, opengist upstream master (fetched 2026-10-08). Architecture: 09-update-architecture.md §3 decisions 46-47 (setup gate, sign-up block), 01-topology-and-trust.md §5 (the gate).

Where it stands

  • Wishlist is done (controller v0.301.0 open_command, proven on 9202 2026-10-06). The row is narrowed to opengist.
  • Opengist (templates/opengist/docker-compose.yml, image ghcr.io/thomiceli/opengist:1.15) is closed by the address block alone: signup_block: "PathRegexp((?i)^/+-/+register)" (.felhom.yml:42). Measured 2026-09-29: every trick shape — trailing slash, upper/mixed case, percent-encoded, double slash, query string — answers 403 (audits/signup-lock-2026-09-29/B/B6-tricks-all-regex.txt). The app publishes no port; traefik is its only door.
  • Upstream, read today: the switch is row disable-signup in opengist's admin-settings table; startup seeds it to "0" from a hard-coded map — no config key and no env feeds it (internal/db/db.go; config.yml has no signup/register key). The settings are read from the database on every request (dataInit middleware → loadSettings → db.GetSettings(), internal/web/server/middlewares.go). So a row written while the app runs takes effect at the next request — the row's „needs its sqlite with the app stopped" is not needed.
  • The controller already embeds a pure-Go SQLite (modernc.org/sqlite, go.mod:11) and already runs short helper containers on app volumes (docker run --rm -v vol:…, internal/stacks/undo.go:205-235, image alpine, which has no sqlite).

Options

What Costs Risk
A — the box writes the row with its own image New after_setup form sqlite: {volume, file, close_sql, open_sql, check_sql}; the controller runs docker run --rm -v opengist_data:/v <its own image> <subcommand> that opens the file with modernc sqlite (busy timeout), runs the statement, reads it back and prints the marker. Feeds the existing close/open/loop machinery unchanged. ~1 session controller + catalog line + a 9202 proof. No new external image. A second writer on a live SQLite (normal locking; same kernel). A schema rename upstream → the read-back fails → the existing failure record, never silent.
B — a sidecar with a sqlite tool Add a small sqlite image as a second service in the template. ~½ session. A new external dependency (fenced — operator only), RAM and an image to keep current on every box.
C — keep the address block alone Accept the measured block as the lock for opengist; close the row as decided. Nothing. The switch stays „open" inside the app; only a path traefik does not see could reach it, and the app has no such path today.

Pick: C, with A ready. The block is measured against every trick shape and the app has no door but traefik, so A buys defence in depth for a P3-LOW. A is the right build if the operator wants the app's own switch closed too: it adds no dependency and serves any later app whose switch lives only in its database.

First slice of A (only on the operator's yes), with its red test: after_setup.go accepts the sqlite form; a test with a temp SQLite file runs close → the read-back prints the marker and the row reads 1; open → 0; a missing table → an error and no marker (red today: the form is unknown and the spec is refused). Then the opengist template line and a 9202 proof in wishlist's shape (stranger 403/refused after setup, a family member signs up inside the window, closed again after it).

Small, for the parent now (comments only, no decision): internal/stacks/after_setup.go:33 names opengist among the apps „closed by command" — today only wishlist is; internal/stacks/signup_block.go:22 shows opengist's block as PathPrefix(`/-/register`) while the template uses the case-insensitive PathRegexp above.

One question for the operator

Is the measured address block enough for opengist, or should the box also close opengist's own sign-up switch (about one working session)? If you do nothing: opengist stays closed by the address block alone, and the row closes as decided.