Skip to content

feat(server): atomic first-admin bootstrap, scoped CORS, and deployable web admin - #321

Merged
kafkade merged 1 commit into
mainfrom
kafkade-first-admin-bootstrap-cors
Jul 27, 2026
Merged

feat(server): atomic first-admin bootstrap, scoped CORS, and deployable web admin#321
kafkade merged 1 commit into
mainfrom
kafkade-first-admin-bootstrap-cors

Conversation

@kafkade

@kafkade kafkade commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Description

Makes a self-hosted ldgr deployment safe and reachable out of the box, closing the M0/0c release blockers.

What's included:

  • Atomic first-admin election (server). When no LDGR_ADMIN_EMAIL is configured, the first registered user is now elected admin inside a single guarded BEGIN IMMEDIATE count-and-insert transaction (try_bootstrap_first_admin in storage/mod.rs), so under concurrent sign-ups at most one admin can ever be created. The old fallback counted users and inserted in separate lock acquisitions — a check-then-insert race where two simultaneous registrations could both become admin (and on an unconfigured, internet-exposed instance, whoever registered first silently became admin). The env-seeded LDGR_ADMIN_EMAIL path is untouched.

  • Scoped CORS (server). New LDGR_ALLOWED_ORIGINS config (comma-separated allowlist) drives a tower_http::cors layer applied in main.rs. It is empty by default (deny) — no cross-origin browser access unless explicitly opted in — and when set, allows only the methods GET/POST/PUT/PATCH/DELETE/OPTIONS, the authorization/content-type headers, and no credentials. Needed only for advanced deployments that serve the panel from a different origin than the API.

  • Batteries-included web admin (infra). The existing Caddy container now serves a static build of the Apache-2.0 web app and reverse-proxies /api/* + /health to the server, so docker compose up -d yields a reachable /admin panel at http://<host>:8081/admin with its Server URL pre-filled to the same origin. A new apps/web/Dockerfile builds the WASM bundle and the Next.js static export (output: 'export') and packages it into caddy:2. The AGPL ldgr-server binary serves zero web assets — Caddy only (ADR-006 / ADR-008 §7). A non-gating docker-web image job publishes ghcr.io/<owner>/ldgr-web.

  • Docs. docs/self-hosting.md rewritten for the batteries-included deploy (removed the separate-host instructions and the F3 same-origin "Current limitation" caveat).

  • Pre-existing web build fix. The apps/web static export did not build on main (CI only ran npm test, never npm run build). Fixed by targeting the webpack compiler the WASM config requires (next build --webpack) and aligning Tailwind CSS to the v3 toolchain its config is written against. The web CI job now runs the WASM + static build so this stays green. Note for reviewers: the Tailwind fix is a v3 pin (matches all existing v3-authored configs, zero CSS churn); a v4 migration is the alternative if preferred.

New tests: tests/bootstrap_race.rs (16 concurrent registers assert exactly one admin; open policy → rest user, invite-only → rest 403; plus sequential sanity) and tests/cors.rs (preflight allow/deny + empty-allowlist-disables-CORS).

Related Issues

Closes #297

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update
  • Refactoring (no functional changes)
  • CI / infrastructure
  • Other (describe below)

Component

  • crates/ldgr-core/ — Core library (crypto, accounting, storage)
  • crates/ldgr-cli/ — CLI tool
  • crates/ldgr-server/ — Sync server
  • bindings/swift/ — UniFFI Swift bindings
  • apps/ios/ — iOS / iPadOS / watchOS app
  • apps/web/ — Web app (Next.js + WASM)
  • docs/ — Documentation

Privacy Checklist

  • No plaintext financial data is sent to or stored on the server
  • No new metadata exposure introduced (or documented if unavoidable)
  • Any new external service interaction is documented in the trust boundary model
  • Crypto changes use audited RustCrypto crates — no custom primitives
  • Key material is never exposed in error messages, logs, or Debug output

Checklist

  • I have read CONTRIBUTING.md
  • Tests pass (cargo test --workspace)
  • Clippy passes (cargo clippy --workspace -- -D warnings)
  • Formatting passes (cargo fmt --check)
  • I have updated documentation (if applicable)

CI / infra note: No CI job names changed (they are merge gates mirrored in kafkade/github-infra terraform). Build steps were added to the existing web job and a non-gating docker-web job was added in docker-publish.yml — no new required check, so no github-infra terraform change is needed. A compose smoke-test merge gate was intentionally not added.

Not verified locally: live docker compose up end-to-end (no Docker daemon available — validated via docker compose config on both profiles, caddy validate, and a functional Caddy serve smoke test); web panel styling (no browser).

@kafkade
kafkade enabled auto-merge (squash) July 27, 2026 21:22
@kafkade
kafkade merged commit 76491dc into main Jul 27, 2026
14 checks passed
@kafkade
kafkade deleted the kafkade-first-admin-bootstrap-cors branch July 27, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M0/0c: Robust first-admin bootstrap + CORS / deployable web admin

1 participant