feat(learn): LanguageTool as a sidecar, reached through the server

compose.languagetool.yaml runs LanguageTool's open-source server
(meyay/languagetool 6.8-13, pinned by digest) on `edge` with no site and
no published port, as user 61081 on a read-only filesystem, without a
log. The server alone reaches it (LANGUAGETOOL_URL): grammar.check is
for those signed in, takes 20,000 characters, 60 checks a minute each
(counted in memory), maps a studyset's language to LanguageTool's code
and keeps nothing. instance.info() names the languages it checks; the
privacy page says what goes there. sidecars/README.md has the steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 23:13:46 +01:00
commit f766a880ac
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -18,6 +18,9 @@ site changes.
| Sidecar | Compose file | Site |
|---|---|---|
| Jellyfin, which people watch and listen to learn from | `compose.jellyfin.yaml` | `jellyfin.caddy` |
| LanguageTool, which checks spelling and grammar | `compose.languagetool.yaml` | none |
Both together: `COMPOSE_FILE=compose.yaml:compose.jellyfin.yaml:compose.languagetool.yaml`.
## Jellyfin
@ -96,3 +99,53 @@ Steps, in order; ✱ marks a step as root.
Switching it off: the compose file out of `COMPOSE_FILE`, then `docker compose up -d
--remove-orphans`; `deploy/jellyfin/` stays until removed by hand.
## LanguageTool
LanguageTool's open-source server beside the instance (ADR 0024), which checks spelling
and grammar for Halkyon Learn: Check in the assistant's field, and the underlines while
writing (the app's Settings › Writing). Only the instance's server reaches it
(`LANGUAGETOOL_URL`, set by the compose file): LanguageTool has no sign-in of its own,
so it gets no site, and the server checks who asks and how often (signed in, 60 checks a
minute each). Nothing written is kept or logged, LanguageTool's own log included: Docker
keeps none for it. It checks without n-gram data (LanguageTool's statistical checks of
words like their and there, tens of GB per language), in about 1.5 GB of memory.
Steps, in order; ✱ marks a step as root.
1. ✱ **Its user and folder** (the operator, on the instance's server, as a user with
sudo): the user and group ID 61081 are LanguageTool's alone, as the compose file runs
it (`useradd` warns that the ID lies above the system range, which is meant: it must
not meet a container's IDs).
```sh
sudo groupadd --system --gid 61081 languagetool
sudo useradd --system --uid 61081 --gid 61081 --no-create-home --shell /usr/sbin/nologin languagetool
cd ~deploy/halkyon-learn/deploy
sudo mkdir -p languagetool/fasttext
sudo chown -R languagetool:languagetool languagetool
```
2. **Switch it on** (the operator, on the instance's server, as `deploy`): in
`deploy/.env`, `COMPOSE_FILE=compose.yaml:compose.languagetool.yaml`.
3. **Start it** (the operator, on the instance's server, as `deploy`):
`cd ~/halkyon-learn/deploy && docker compose up -d`. The server is recreated with it
(its `LANGUAGETOOL_URL`); the instance is away for seconds. At its first start
LanguageTool fetches fastText's model of languages (`lid.176.bin`, 126 MB, from
Facebook's `dl.fbaipublicfiles.com`) into `deploy/languagetool/fasttext/`, which it
uses to tell which language a note is in; checks start once it is there.
4. **Check it**: on the instance's server, as `deploy`, `docker compose ps languagetool`
says `healthy` after a minute; then, from anywhere, the instance names the languages
it checks:
```sh
curl -s -X POST -H 'Content-Type: application/json' -d '{}' https://<domain>/instance/info
```
answers with `grammarLanguages`, `de-DE` and `en-US` among them.
Switching it off: the compose file out of `COMPOSE_FILE`, then `docker compose up -d
--remove-orphans`; the app's Writing settings then say the instance checks nothing, and
`deploy/languagetool/` stays until removed by hand.