78121aa475
gates / gates (push) Successful in 3m57s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
50 lines
4.3 KiB
Markdown
50 lines
4.3 KiB
Markdown
# 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.
|