feat(deploy): instances keep their dictionaries current, weekly, by themselves

EDRDG's licence asks of a server showing its dictionaries that it update
them at least monthly. The instance now does it alone: a `dictionaries`
service in compose, built from the curator (halkyon_learn_curator/
Dockerfile, its own unprivileged user 10001, 1 GB at most), runs `curate
update --every 7`, downloading EDRDG's files of the day once its files are
a week old and replacing them in a volume the server mounts read-only.
The curator never enters the server's image or process (decided
2026-10-09: a container of its own, weekly). The server rereads the
folder daily at 04:07 (DictionaryReloadFutureCall), loading what changed,
and loads now run one at a time. redeploy.sh builds both images.

An operator who'd rather supply the files removes the service and mounts
deploy/dictionaries/ into the server, as compose.yaml says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-09 16:52:37 +02:00
commit 25e01ad128
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -42,22 +42,25 @@ Future<void> run(List<String> args) async {
.filePruning
.pruneFiles();
// The dictionaries the operator put into the server's folder, those that
// changed since; in the background, as loading takes a minute or two.
// The dictionaries in the server's folder, those that changed since; in
// the background, as loading takes a minute or two. Then again daily, as
// the dictionaries service replaces them weekly.
unawaited(_loadDictionaries(pod));
await pod.futureCalls.cancel(_dictionaryReload);
await pod.futureCalls
.callRecurring(identifier: _dictionaryReload)
.cron('7 4 * * *')
.dictionaryReload
.reloadDictionaries();
}
/// Loads the dictionaries the operator put into the server's folder (or
/// `DICTIONARIES_DIR`). [loadDictionaries] throws nothing; a session that
/// can't be had is reported here, as an error in the background would end
/// the server.
/// Loads the dictionaries in the server's folder ([dictionaryFolder]).
/// [loadDictionaries] throws nothing; a session that can't be had is
/// reported here, as an error in the background would end the server.
Future<void> _loadDictionaries(Serverpod pod) async {
try {
await pod.withSession(
(session) => loadDictionaries(
session,
Directory(Platform.environment['DICTIONARIES_DIR'] ?? 'dictionaries'),
),
(session) => loadDictionaries(session, dictionaryFolder()),
);
} on Object catch (error) {
stderr.writeln('Loading the dictionaries failed: $error');
@ -69,3 +72,6 @@ const _pruning = 'prune-abandoned-anonymous-people';
/// The identifier of the daily pruning of files.
const _filePruning = 'prune-unreferenced-files';
/// The identifier of the daily rereading of the dictionaries.
const _dictionaryReload = 'reload-dictionaries';