Commit graph

11 commits

Author SHA1 Message Date
randogoth
ff289671a5 feat: add the registration config keys and their module options
Six keys, and the parser's first list, bool and word-valued ones:
registration, registrations_per_hour, info_replies_per_hour, admins,
admin_notify, register_hosts.

Lists drop empty items, because `admins =` is exactly what the Nix module
renders for an unset option, and addresses are validated at load rather than
at first use — a typo would otherwise pass the start and then silently never
match an admin command. Validated values are normalised through
Address::short(), so a spelling carrying the default port collapses to the one
the daemon will compare against.

registration defaults to silent, so adding these keys changes nothing for
anyone who writes in.

Verified the generator and the parser agree by feeding the module's rendered
output to the binary, in both the fully-set and all-defaults shapes — which
matters because config.rs hard-errors on an unknown key.

The bucur half waits for the lock: that host pins this flake at the rev before
these options existed, so setting them there now would break its eval. It
lands with the deploy.
2026-10-14 19:11:12 +03:00
randogoth
e606b3a1e0 feat: add the registration mode and the global hourly budgets to state
Mode is persisted rather than read from config alone, so an admin can flip
registration by mail instead of needing a redeploy; config supplies only the
initial value. It defaults to Silent, which is today's behaviour byte for
byte, so landing this cannot start mailing strangers on its own.

Limits holds the two daemon-wide hourly counters. Identity keys are free to
mint, so a per-key budget does not bind an attacker and these are what
actually do; each vector is pruned to the hour on read and so is bounded by
its own cap.

Also fixes reply_allowed's ledger growth, which stops being a wart here and
becomes a vulnerability: until now only accepted authors reached it, but once
a stranger can draw a reply any throwaway keypair appends a permanent row.
Ledgers idle for an hour are dropped and the count is capped, with a
regression test that drives it past the cap.

drained carries the one-time backlog guard: a live mail.db holds every
requests-tier mail ever received, and the first poll after upgrading must not
mail all of them.
2026-10-14 19:05:10 +03:00
randogoth
0e6687e944 feat: allow a host in the feed path, behind a separate slug guard
A remote author's feed needs a namespaced path or two servers' `alice` collide
on one file. valid_slug accepts `local`, `local@host` and `local@host:port`;
local authors keep their bare path, so the published feed keeps its URL and
nothing migrates.

valid_username is untouched and is now only the nick rule: a nick reaches
`# nick =` metadata that other twtxt clients parse, so widening it with the
path rule would have leaked `@` and `:` in there. A test pins the two apart.

The traversal guard changes character here. A path could not contain a dot
before, which is why `/twtxt/../etc/passwd` 404'd for free; dots are legal now,
so the rejections are explicit and path() also asserts the join stayed inside
the feed directory. username_of rejects `/` before decoding and decodes only a
literal `%40`, since some twtxt clients encode the `@`.

Incidentally fixes a legal smolmail user never being able to get a feed: the
old rule banned `.` and `_`, so `admit alice.b@smol.place` failed at create.
2026-10-14 19:01:41 +03:00
randogoth
bf6e2eaa15 refactor: reject mail with a reason identifier, not a sentence
dispatch and apply_commands returned English prose that became the reply
subject, so the pure layers owned author-facing copy. They now return
reason::Reason and the mail-building edge resolves it through the catalogue.

core::rules needed no change: RuleError was already a typed enum, and its
Display stays for author-side tools. The daemon maps the variants itself
rather than borrowing that prose.

Error-path tests now assert on variants, so rewording main.ftl cannot break
them, and PruneModeUnknown carries the value the author actually sent.

Reason::every_variant_resolves_to_copy walks the enum, so a variant added
without a catalogue entry fails in tests rather than mailing someone
"???reason-…???".
2026-10-14 18:57:57 +03:00
randogoth
3279fd3a2d refactor: move author-facing mail copy into a Fluent catalogue
Ports carrier's src/i18n.rs and its locales/en-US/main.ftl layout, same
fluent-bundle and unic-langid versions, so the two catalogues stay portable
between them. Only mail the daemon sends an author moves; operator CLI output
stays inline, since the operator runs the binary and reads English.

The error-reply reasons from dispatch and ApplyError are deliberately left
behind: dispatch is pure by contract, so those become reason identifiers
resolved at the mail-building edge in a separate change rather than having the
pure layer reach for a bundle.

Adds a test carrier lacks. FluentResource::try_new and add_resource both panic
on a malformed or duplicate-id catalogue, and nothing touches the bundle until
the daemon first mails someone, so without it a bad catalogue surfaces inside
poll() on a live instance.

Sentences are verbatim; the bodies are now hard-wrapped where they were single
long lines, which reads better in a plain-text client.
2026-10-14 18:48:31 +03:00
randogoth
f2d2d75063 feat: add flake with static build, release app and NixOS module
packages.static is a static musl binary so a low-power deploy target can
fetch a prebuilt one rather than compile Rust; apps.release-static publishes
it to a Forgejo release, guarded on HEAD carrying the version's tag.

fumi-core is a path dependency on ../fumi, which escapes the flake source, so
fumi is vendored as its own input and re-laid beside the source tree in the
store. Over https, not a local path: a flake importing this one has to resolve
this input too when it locks.

services.smolboid runs as a real user rather than DynamicUser, because admit,
block and the fumi bootstrap are operator commands against the same master and
store and need a stable uid to sudo into.
2026-10-14 17:20:52 +03:00
randogoth
21c7cbe4ed docs: add README and refresh audit 2026-10-05 11:14:23 +03:00
randogoth
267625b411 fix: cap listener connections, serve HEAD, fix feed parser CR round-trip 2026-10-05 11:14:17 +03:00
randogoth
1e782310b6 test: add rig, stress and pentest suites with audit 2026-10-05 11:00:17 +03:00
randogoth
8330b65ff9 feat: add smolboid daemon: mail loop, feed store and HTTP listener 2026-10-05 11:00:17 +03:00
randogoth
fd3f9bcf24 feat: add smolboid-core feed format with payload-signature verification 2026-10-05 11:00:10 +03:00