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.
This commit is contained in:
randogoth 2026-10-14 19:11:12 +03:00
parent e606b3a1e0
commit ff289671a5
2 changed files with 228 additions and 0 deletions

View file

@ -151,6 +151,12 @@
cap_bytes = ${toString cfg.capBytes}
poll_secs = ${toString cfg.pollSecs}
timeout = ${toString cfg.timeout}
registration = ${cfg.registration}
registrations_per_hour = ${toString cfg.registrationsPerHour}
info_replies_per_hour = ${toString cfg.infoRepliesPerHour}
admins = ${lib.concatStringsSep ", " cfg.admins}
admin_notify = ${if cfg.adminNotify then "on" else "off"}
register_hosts = ${lib.concatStringsSep ", " cfg.registerHosts}
'';
in
{
@ -266,6 +272,81 @@
description = "Network timeout for smolmail operations, in seconds.";
};
registration = mkOption {
type = types.enum [ "silent" "open" "closed" ];
default = "silent";
description = ''
Whether strangers may register themselves by mail. `silent`
drops an unknown sender without a reply, which is how every
version before self-service behaved — so upgrading never
starts mailing strangers on its own. `open` lets them
register; `closed` tells them registration is shut.
Only the initial value: an admin's `Twt-Registration` is
persisted in the daemon's state and outranks it, so the mode
can change by mail without a redeploy.
'';
};
registrationsPerHour = mkOption {
type = types.ints.positive;
default = 10;
description = ''
Daemon-wide cap on registration attempts that reach the
network, per hour. Attempts rather than successes: one that
fails its key check costs the same outbound dial.
This is the cap that matters — identity keys are free to
mint, so a per-sender budget does not bind an attacker.
'';
};
infoRepliesPerHour = mkOption {
type = types.ints.positive;
default = 30;
description = "Daemon-wide cap on info replies to unknown senders, per hour.";
};
admins = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "tobias@smol.place" ];
description = ''
Addresses allowed to send `Twt-Admit`, `Twt-Block` and
`Twt-Registration`, in `user@host` short form. Each is
validated when the daemon loads its config, so a typo fails
the start rather than silently never matching.
Admit your own admin identity once with the CLI so the daemon
pins your key and hands you a token; until it holds one, its
acks land in your requests tier.
'';
};
adminNotify = mkOption {
type = types.bool;
default = true;
description = ''
Mail every admin when a feed is created, with the author's
address and key fingerprint — and for a remote author the
unpinned server key their host presented.
'';
};
registerHosts = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "smol.place" ];
description = ''
Hosts whose users may register. Empty means any host.
Note what empty allows: the daemon will dial a `host:port` an
unknown sender chose, a mild port-scan primitive with timing
feedback from inside this machine's network. Bounded by
`registrationsPerHour`, closed entirely by listing hosts here.
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;