R5 the server's own settings: instance, rules, announcements, relays, media limits, the staff's log
Build / Build (push) Successful in 1m3s
Build / Build (push) Successful in 1m3s
A singleton ServerSettings (title, descriptions, contact, thumbnail, privacy policy, terms, rules, relays, media overrides, PublishActivity), cached by InstanceSettings and read by the instance API, NodeInfo, the instance actor, the web pages and the mailer; its defaults are the old literals, so nothing changes until an administrator edits. /clientapi/admin/settings (and /thumbnail), /announcements, /relays for administrators; /clientapi/moderator/log, the staff's log (AdminAction, written by domain blocks, backups, settings and the CLI; no IP, never a root paired with its personas). The Mastodon API's rules, extended description, privacy policy, terms of service, domain blocks (Published, Obfuscate), announcements (dismiss, reactions, streaming) and activity (only when published). Reports carry rule ids. Media overrides clamped to hard caps. Relays named by the settings reconcile next to the configured ones. Verified: interop.sh instance accounts relay with mastodon, relay, aoderelay and mailpit, 119 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
1 parent
f67e2ae35e
commit
70d1ffe9a2
46 files changed
+2223
-107
No files matched your search
@@ -82,8 +82,9 @@ PrivaPub/ ASP.NET Core Web API, net10.0
|
||||
Program.cs host, Mongo init (GUIDs Standard), pipeline, /build.json
|
||||
Middleware/SocialPubConfigurations.cs every DI registration (auth, federation, services, swagger, CORS)
|
||||
Controllers/ClientToServer/ /clientapi/*: RootUser (signup, setup, login, invitations, recovery), PrivateAvatar,
|
||||
Group, Post (posts + DMs), Social, Timeline, Data, PersonaArchive, Moderator (reports),
|
||||
Admin (logins), DomainBlock, Statistics, Backup
|
||||
Group, Post (posts + DMs), Social, Timeline, Data, PersonaArchive, Moderator (reports,
|
||||
the staff's log), Admin (logins), DomainBlock, Statistics, Backup, InstanceAdmin (the
|
||||
server's settings, thumbnail, announcements, relays)
|
||||
Infrastructure/
|
||||
Http/ FederationHttp + SafeHttpHandlerFactory + IpRangeGuard: the only way out
|
||||
Jobs/ JobQueue (leases), JobWorker, Backoff, HostCircuitBreaker
|
||||
@@ -116,8 +117,11 @@ PrivaPub/ ASP.NET Core Web API, net10.0
|
||||
Crawler/ Crawler, PeerHosts, RobotsTxt (the opt-in crawler behind `/stargazing`)
|
||||
Relays/ Relays (the instance actor's relay subscriptions)
|
||||
Domain/
|
||||
Content/ ContentRenderer (Markdown or plain text → HTML with h-card mentions and hashtags)
|
||||
Social/ FollowService (local in-process, remote Follow/Accept), Notifications
|
||||
Content/ ContentRenderer (Markdown or plain text → HTML with h-card mentions and hashtags;
|
||||
Document: the server's own texts)
|
||||
Instance/ InstanceSettings (the server's own settings in memory), MediaLimitsFromSettings
|
||||
Moderation/ ModerationLog (the staff's log, AdminAction)
|
||||
Social/ FollowService (local in-process, remote Follow/Accept), Notifications, Announcements
|
||||
Timelines/ Fanout (TimelineEntry rows, Mastodon's home rules), TimelineService
|
||||
Privacy/ VisibilityPolicy (IsPublic expression, CanSee)
|
||||
Relationships/ RelationshipService (blocks, mutes, account domain blocks; Hidden), ReportService
|
||||
@@ -137,7 +141,8 @@ PrivaPub/ ASP.NET Core Web API, net10.0
|
||||
Web/Pages/ Razor: /@{user}, /@{user}/{id} (public posts only, strict CSP, noindex);
|
||||
OAuth/: /oauth/login (root password), /oauth/authorize (choose persona, consent)
|
||||
Services/ RootUsersService, GroupUsersService, PostsService, AppConfigurationService, …
|
||||
Models/ Mongo entities: User/, Group/, Post/, Federation/, Jobs/, AppConfiguration
|
||||
Models/ Mongo entities: User/, Group/, Post/, Federation/, Jobs/, AppConfiguration, ServerSetup,
|
||||
ServerSettings, AdminAction
|
||||
StaticServices/ DbEntities (Find<T> accessors), AuthTokenManager (JWT), PasswordHasher
|
||||
Data/InitDb.cs first-run seeding (languages)
|
||||
PrivaPub.ClientModels/ DTOs + validation resources shared with clients
|
||||
@@ -189,7 +194,80 @@ ASPNETCORE_ENVIRONMENT=Production ./PrivaPub admin reset-totp <root> # a logi
|
||||
```
|
||||
|
||||
The deploy runs them as `build-runner`, which owns the published files, reads `appsettings.Production.json` through
|
||||
group www-data and reaches the private mongod; `sudo -u www-data` works too.
|
||||
group www-data and reaches the private mongod; `sudo -u www-data` works too. Every one of them that changes a login or a
|
||||
persona (promote, demote, create-root, move-persona, delete-root, reset-totp) and `restore` is kept in the staff's log as
|
||||
the command line's (`cli`).
|
||||
|
||||
## The server's own settings (completion program R5)
|
||||
|
||||
- **One document** (`Models/ServerSettings.cs`, fixed id `settings`), read through the `IInstanceSettings` singleton
|
||||
(`Domain/Instance/InstanceSettings.cs`): `Current` is never null and never changed in place; `Update(change, byRootId)`
|
||||
changes the stored document (or the defaults), saves it and swaps it in; it is read at start (after migrations), after
|
||||
each save and every five minutes (`InstanceSettingsRefresher`), so an edit made elsewhere takes. **The defaults are what
|
||||
PrivaPub served before, word for word** (title "PrivaPub", `ServerSettings.DefaultDescription`, the privacy policy of
|
||||
2026-10-04 as Markdown that renders to the old paragraph, `/media/missing-header.png`), so nothing changes until an
|
||||
administrator saves. Registrations stay configuration-only (`Registrations:Mode`, owner decision; the deploy checks it).
|
||||
- **Where each fact comes from now:**
|
||||
- the title: both instance APIs' `title`, NodeInfo `metadata.nodeName`, the instance actor's `name`,
|
||||
`ProvenanceController.DescribeSelf`, the web pages' footer (`_Layout`), the `/oauth/login` heading and the emails'
|
||||
From name (`Mailer`);
|
||||
- the short description: v1 `short_description`/`description`, v2 `description`, NodeInfo `nodeDescription`, and the
|
||||
instance actor's `summary` once it is not the default (until then the actor's own sentence);
|
||||
- the description (Markdown): `/api/v1/instance/extended_description` (dated when it last changed, today until then);
|
||||
- contact email and contact persona (`ContactAccountId`, a local persona the administrator chooses, publicly tied to
|
||||
running the server; shown while it exists and its login may act): v1 `email`/`contact_account`, v2 `contact`;
|
||||
- the thumbnail: v1 `thumbnail`, v2 `thumbnail.url`;
|
||||
- the rules (`ServerRule`: a GUID id, text, hint, position; soft-deleted, so old reports keep their meaning): both
|
||||
instance APIs' `rules` and `/api/v1/instance/rules`, those not deleted, in order;
|
||||
- the privacy policy and the terms of service (Markdown): `/api/v1/instance/privacy_policy` and Mastodon 4.4's
|
||||
`/api/v1/instance/terms_of_service[/{yyyy-MM-dd}]` (one version kept; 404 while there are none).
|
||||
|
||||
The server's texts are rendered by `ContentRenderer.Document` (Markdig with raw HTML off, then `ContentSanitizer`;
|
||||
nothing is looked up).
|
||||
- **Administrator routes** (`InstanceAdminController`, `[Authorize(Policy = Policies.IsAdmin)]` and checked again against
|
||||
the database): `GET`/`POST /clientapi/admin/settings` (`ViewServerSettings` / `ServerSettingsForm`: a rule named by its
|
||||
id keeps it, a new one gets one, one left out is deleted; a description's or policy's date moves only when its text
|
||||
does), `POST`/`DELETE /clientapi/admin/settings/thumbnail` (multipart `file`), `/clientapi/admin/announcements/
|
||||
list|insert|update|delete`, `/clientapi/admin/relays`, `/relays/insert`, `/relays/delete?address=`.
|
||||
- **The thumbnail** goes through `IMediaService.ServerImage` (checked before it is decoded, libvips, `keep=none`, fitted
|
||||
within 1200×630) into a `MediaAttachment` nobody owns, held by `ServerUse = "thumbnail"`: the janitor's never-posted
|
||||
sweep leaves such rows alone, `admin media audit` keeps the one the settings name (and trashes any other), no quota
|
||||
counts them, and a replaced or removed thumbnail is trashed.
|
||||
- **Media limits** (`ServerSettings.Media`: quota, image and video sizes, duration): `MediaLimitsFromSettings`
|
||||
(`IPostConfigureOptions<MediaOptions>`) applies them over `Media:*` within hard caps (images 40 MB, video 99 MB, which
|
||||
nginx's 100m and `MediaController`'s request limit allow, four hours), and `MediaLimitsChangeSource` makes every
|
||||
`IOptionsMonitor<MediaOptions>` reader (uploads, the quota, the instance API, `/clientapi/user/storage`) see a save at
|
||||
once.
|
||||
- **Rules in reports:** `POST /api/v1/reports` takes `rule_ids[]` (kept when the rule exists, deleted ones too, since the
|
||||
dialog may have listed them; a report naming rules without a category is a `violation`, and only a violation names
|
||||
rules, as on Mastodon); `Report.RuleIds`, the entity's `rule_ids`, and the moderators' `ViewReport.RuleIds` and `Rules`
|
||||
(their texts). Rules never federate.
|
||||
- **Announcements** (`Domain/Social/Announcements.cs`, `Api/Mastodon/Controllers/AnnouncementsController.cs`): written in
|
||||
Markdown by an administrator, shown while published and not ended; `GET /api/v1/announcements` (anonymous: without
|
||||
`read` and `me`), `POST …/{id}/dismiss`, `PUT`/`DELETE …/{id}/reactions/{name}` (one unicode emoji, normalised as a
|
||||
post's reactions are, eight different ones at most; anything else 422). Dismissals (`AnnouncementRead`) and reactions
|
||||
(`AnnouncementReaction`) are kept per persona, never federated, and go with a deleted persona (`RootRemoval`). Every
|
||||
persona's `user` stream hears `announcement`, `announcement.reaction` and `announcement.delete` (`Streams.ToUsers`,
|
||||
mapped per persona in `StreamingController.Payload`).
|
||||
- **Public domain blocks:** `DomainBlock.Published` and `Obfuscate` (off by default: publishing a block tells the blocked
|
||||
server, and everyone). `/api/v1/instance/domain_blocks` lists only published ones, as Mastodon does (`domain`, partly
|
||||
starred when obfuscated, `digest` the SHA-256 of the domain, `severity`, `comment` the public comment).
|
||||
- **Relays** are `Federation:Relays` ∪ `ServerSettings.Relays` (`IRelays.Named`). One `Reconcile` runs at a time; the
|
||||
page's insert and delete start one at once in the background (resolving a relay is a fetch) and answer 202. A
|
||||
configured relay can't be removed from the page (409). A subscription no longer named gets no `Undo` while the same
|
||||
relay actor is still named under another address.
|
||||
- **Activity:** `/api/v1/instance/activity` answers `[]` unless `PublishActivity` (off by default); then twelve weeks,
|
||||
this one first, counted by `Counted.Weekly` (local posts as `LocalPosts` counts them, by their ids; logins made by
|
||||
`RootUser.CreatedAt`; `logins` "0", since sign-ins are not kept), worked out at most hourly. Personas are never counted
|
||||
by their creation (their dates are spread on purpose).
|
||||
- **The staff's log** (`Models/AdminAction.cs`, `Domain/Moderation/ModerationLog.cs`): who (a root id, or `cli`), the action,
|
||||
the target's kind, id and label, the report, a note; never an IP address, and never a root next to its own personas
|
||||
(an act on a persona the acting root owns is kept without who acted; a root's entry names none of its personas). It
|
||||
keeps domain blocks, backups (create, delete, restore), settings saves (the fields changed, never their values),
|
||||
rules, the thumbnail, announcements, relays, report resolutions and the CLI's acts. `GET
|
||||
/clientapi/moderator/log?before=&limit=` (`ViewAdminActionsPage`, newest first, by id): administrators read all of it
|
||||
with login names; a moderator who isn't one reads what isn't about a login, named only when it acted itself or it was
|
||||
the command line. Writing never fails the act it records.
|
||||
|
||||
## Federation invariants
|
||||
|
||||
@@ -363,8 +441,10 @@ group www-data and reaches the private mongod; `sudo -u www-data` works too.
|
||||
- `MediaJanitor` deletes them a day later (`TrashGrace`).
|
||||
|
||||
What a login's media take (every persona's, trashed ones no longer) is `Counted.MediaOfRoot`, shown to the login at
|
||||
`/clientapi/user/storage`; `Media:QuotaBytesPerRoot` (0, none, by default) refuses an upload over it with 422; the
|
||||
administrator's statistics overview has the totals, the proxy cache and the trash.
|
||||
`/clientapi/user/storage`; `Media:QuotaBytesPerRoot` (0, none, by default, or the administrator's override in the
|
||||
server's settings) refuses an upload over it with 422; the administrator's statistics overview has the totals, the
|
||||
proxy cache and the trash. The server's own pictures (`ServerUse`, its thumbnail) belong to nobody and are held while
|
||||
the settings name them.
|
||||
|
||||
Nothing is deleted because it looks unused. `PrivaPub admin media audit [--fix]` compares disk and database: with
|
||||
`--fix` (as www-data) it gives pictures shown from before their rows a row, and trashes media of deleted posts or
|
||||
@@ -572,7 +652,9 @@ group www-data and reaches the private mongod; `sudo -u www-data` works too.
|
||||
the union, P's row winning; roots, personas, groups, posts and remote accounts deleted since are deleted again, with
|
||||
what their deletion takes away; P's moderation of a persona and a root's password, e-mail, ban and policies stay;
|
||||
roots, personas and groups made since become tombstones (deleted, names kept), local posts made since answer 410,
|
||||
media made or trashed since go to the trash;
|
||||
media made or trashed since go to the trash (but the server's own pictures, `ServerUse`); the staff's log
|
||||
(`AdminAction`) is the union, so a restore never loses an entry, its own request included, and the server's settings
|
||||
are P's: the live settings outlive a restore;
|
||||
5. every session ends (a new `SessionStamp` for each root, persona tokens deleted), circuits close, and a
|
||||
`RestoreRecord` (never in a backup) tells what happened (`admin restore --status`).
|
||||
|
||||
@@ -793,6 +875,16 @@ tools/pasture/run.sh down # removes e
|
||||
announced by the other), that alice's public post goes to the relays (and through both to Mastodon: Activity-Relay's
|
||||
forward on its FEP-8b32 proof, after the scenario makes Mastodon read alice's keys anew) and nothing less public, and
|
||||
has Mastodon leave again, so the town sees no relayed posts. 16 checks.
|
||||
- **The server's own settings (`scenarios/instance.sh`, needs relay and aoderelay; last in batch A):** promotes
|
||||
pastureroot (`admin promote`, as `stats_check` does) and edits everything through `/clientapi/admin/settings`, then reads
|
||||
it back from both instance APIs, NodeInfo, the instance actor (exempt from SecureMode), `/instance/rules`,
|
||||
`extended_description`, `privacy_policy`, `terms_of_service` (and by date) and the OAuth page; a published (obfuscated)
|
||||
and an unpublished domain block; a report with `rule_ids` reaching the moderators with the rules' texts; an announcement
|
||||
listed, dismissed by one persona, reacted to and streamed on `user`; an image limit reaching the instance API and a 1 KB
|
||||
quota refusing an upload, then reset; the thumbnail; a relay added through the settings (`https://relay.test/inbox`,
|
||||
beside the configured actor), accepted and removed without ending the configured subscription; the activity once
|
||||
published; the staff's log listing it all without an address. It saves the settings it found at the start back at the
|
||||
end (the description's and the policy's dates then say today).
|
||||
- **Mailpit (1.31.4, `peers/mailpit.sh`, as `mailpit.test`):** the pasture's mail server. `appsettings.Pasture.json`
|
||||
sends PrivaPub's mail to `pasture-mailpit:1025` (plain SMTP, any login accepted) and its recovery links to
|
||||
`https://decepub.test`. `scenarios/accounts.sh` reads what arrived through Mailpit's API: an address set with the
|
||||
|
||||
Reference in new issue
Block a user