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:
parent
3ef53d0481
commit
f766a880ac
3 changed files with 2235 additions and 5 deletions
46
deploy/compose.languagetool.yaml
Normal file
46
deploy/compose.languagetool.yaml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# The LanguageTool sidecar (decided 2026-10-26, ADR 0024): LanguageTool's open-source
|
||||
# server beside the instance, which checks spelling and grammar for Halkyon Learn (Check
|
||||
# in the assistant's field, and underlines while writing). Switched on in .env:
|
||||
#
|
||||
# COMPOSE_FILE=compose.yaml:compose.languagetool.yaml
|
||||
#
|
||||
# It has no site, unlike Jellyfin: LanguageTool has no sign-in of its own, so only the
|
||||
# server reaches it, which checks who asks and how often, and keeps nothing. Its one
|
||||
# folder on the server, ./languagetool/fasttext (the model it tells languages apart
|
||||
# with, fetched at its first start), is owned by a user ID of its own, which no person,
|
||||
# no other container and not the CI runner uses: processes of one user ID can read
|
||||
# each other's environment and files. Setting it up: sidecars/README.md.
|
||||
|
||||
services:
|
||||
languagetool:
|
||||
# meyay/languagetool 6.8-13: LanguageTool 6.8 built from its tag (LanguageTool's own
|
||||
# downloads end at 6.6), the version Halkyon Learn's checks are tried with.
|
||||
image: meyay/languagetool@sha256:062924c66c649d706d60aed80af6bab207bb50a053733e7ac1ca0d8446d150f1
|
||||
restart: unless-stopped
|
||||
user: "61081:61081"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
# The configuration it writes at each start, and the native libraries Java
|
||||
# unpacks and loads.
|
||||
- /tmp:exec,size=64m,uid=61081,gid=61081
|
||||
environment:
|
||||
# Java's heap: 1.2 GB resident with fastText beside it, five languages loaded.
|
||||
JAVA_XMS: 256m
|
||||
JAVA_XMX: 1g
|
||||
volumes:
|
||||
- ./languagetool/fasttext:/fasttext
|
||||
# On `edge` alone, beside Caddy and the server's public API, never beside Postgres;
|
||||
# no published port, and no site in Caddy.
|
||||
networks:
|
||||
- edge
|
||||
mem_limit: 2g
|
||||
# No log at all: LanguageTool's names each check's length and language only, but
|
||||
# what is written must never be kept, and a failing rule could name its sentence.
|
||||
# To look into a failure, switch to journald for a while.
|
||||
logging:
|
||||
driver: none
|
||||
|
||||
server:
|
||||
environment:
|
||||
# The instance checks writing here (ADR 0024).
|
||||
LANGUAGETOOL_URL: http://languagetool:8081/
|
||||
|
|
@ -113,6 +113,9 @@ services:
|
|||
# The Jellyfin server suggested to people; empty: jellyfin. on the
|
||||
# instance's own domain, if one answers there; `none`: none.
|
||||
JELLYFIN_URL: ${JELLYFIN_URL:-}
|
||||
# The LanguageTool server checking spelling and grammar, reached by the server
|
||||
# alone; empty: none (compose.languagetool.yaml sets its own).
|
||||
LANGUAGETOOL_URL: ${LANGUAGETOOL_URL:-}
|
||||
# Where people's data and the backups are, which the app shows: `EU`, a
|
||||
# country (`DE`) or a subdivision (`US-CA`); empty: it says nothing.
|
||||
DATA_LOCATION: ${DATA_LOCATION:-}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue