Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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, imageghcr.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-signupin 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.ymlhas no signup/register key). The settings are read from the database on every request (dataInitmiddleware →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, imagealpine, 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.