First-time setup: the first login made while no administrator exists becomes it
Build / Build (push) Successful in 10m49s
Deploy / privapub.thepra.dev (push) Successful in 14m0s

Sign-up stays closed (invitations only), so a new server's first login could only be made over SSH with admin
create-root, and an invitation needs a group, which needs a login. While no administrator exists, GET
/clientapi/user/setup says so and POST takes a SetupForm (login, password, first persona): the login is made the
administrator with its persona, and setup closes for good (owner decision 2026-10-16: first come, first served, no setup
code).

ServerSetup is one document with a fixed id, so the first request to insert it wins and two setups at once make one
administrator. A setup that fails before its login is made gives the claim back; one that named an administrator never
does, whoever is demoted or deleted later, and the CLI stays the way back. admin promote and create-root --admin seal it
too, migration _018 seals a server that already had an administrator, and no backup holds it nor does a restore drop it,
since a restore tombstones an administrator made after the backup. Public sign-up, NodeInfo and the instance API are
unchanged. RootUser.SetAdmin is the one place an administrator is made (with isModerator, which IsAdminPolicy asks for),
and the invitation endpoints share InsertPersona with setup.

Tests: ServerSetupTests on the invitations-only host (setup, the race, failed setups, sign-up still closed, the CLI's
seal) and the migration; the whole suite passes (962). In the pasture, decePub's setup and invitation pages pass end to
end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-16 23:21:41 +02:00
1 parent 088c54ddf3
commit 8156c1927c
12 files changed
+396 -27

No files matched your search

+16 -4
View File
@@ -150,9 +150,20 @@ curl-able. Outbound fetches only go to https DNS names resolving to public addre
`Federation__AllowPrivateNetworks=true`, `Federation__AllowPlainHttp=true` and `Federation__AcceptAnyCertificate=true`,
which startup refuses in Production.
Sign-up is closed in production (invitations only). The server's first login is made from the client instead, while no
administrator exists (owner decision 2026-10-16, first come, first served):
- `GET /clientapi/user/setup` says whether setup waits (`ViewServerSetup.IsPending`), and `POST` takes a `SetupForm`
(login, password, first persona). That login is made the administrator, with its persona.
- `ServerSetup` (`Models/ServerSetup.cs`) closes setup for good: one document with the fixed id `setup`. The first request
to insert it wins, so two setups at once make one administrator.
- It is never given back once it names an administrator, whoever is demoted or deleted later.
- `admin promote` and `create-root --admin` seal it too, and migration `_018` seals a server that already had an
administrator.
- It is never in a backup and never dropped by a restore.
The admin CLI runs with every service built but nothing started (no Kestrel, no hosted services), so it can run next to
the live service. Sign-up is closed in production (invitations only), so the first login is made here; promoting is how
an admin is made (signing up as "admin" grants nothing):
the live service. It is the way back when setup is closed and no administrator can sign in; promoting is how an admin is
made (signing up as "admin" grants nothing):
```bash
cd /var/www/privapub.thepra.dev
@@ -503,8 +514,9 @@ group www-data and reaches the private mongod; `sudo -u www-data` works too.
backup rotates out), copies where a link can't be made. `--db-only` lists them instead.
**Never in a backup** (`ServerBackup.Excluded`): `InteractionSalt` (owner rule), `Job` and `Delivery` (work in flight),
`EmailRecovery`, `openiddict.tokens` and `.authorizations` (sessions), `MaintenanceLock`, and `AppConfiguration`
(its SMTP password; it is made again from appsettings at boot). One backup or restore runs at a time
`EmailRecovery`, `openiddict.tokens` and `.authorizations` (sessions), `MaintenanceLock`, `ServerSetup` (a restore
never opens setup again), and `AppConfiguration` (its SMTP password; it is made again from appsettings at boot). One
backup or restore runs at a time
(`MaintenanceLock`, a heartbeat document; a holder silent for 2 minutes is taken over), and the media janitor purges
nothing meanwhile. `BackupScheduler` backs up at `Backups:NightlyAt` (03:30 UTC), or at once when it missed the night;
rotation keeps 7 daily and 4 weekly nightly backups, 3 pre-deploy, 3 pre-restore, and manual and uploaded ones until