feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
# Sidecars
|
|
|
|
|
|
|
|
|
|
|
|
Services an instance may run beside itself (decided 2026-10-14), each switched on by
|
|
|
|
|
|
naming its compose file after the instance's own in `deploy/.env`:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
COMPOSE_FILE=compose.yaml:compose.jellyfin.yaml
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
A sidecar's compose file adds its service and, if it is reached from outside, mounts
|
|
|
|
|
|
its site from this folder into Caddy's `/etc/caddy/sidecars/`, which the Caddyfile
|
|
|
|
|
|
imports. It joins the `edge` network beside Caddy and the server's public API, never the
|
|
|
|
|
|
internal one where Postgres is, and publishes no port of its own. Its process runs as a
|
|
|
|
|
|
user ID of its own, which no person, no other container and not the CI runner uses.
|
|
|
|
|
|
`redeploy.sh` deploys whatever `.env` switches on, and recreates Caddy when a sidecar's
|
|
|
|
|
|
site changes.
|
|
|
|
|
|
|
|
|
|
|
|
| Sidecar | Compose file | Site |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| Jellyfin, which people watch and listen to learn from | `compose.jellyfin.yaml` | `jellyfin.caddy` |
|
2026-10-25 23:13:46 +01:00
|
|
|
|
| LanguageTool, which checks spelling and grammar | `compose.languagetool.yaml` | none |
|
|
|
|
|
|
|
|
|
|
|
|
Both together: `COMPOSE_FILE=compose.yaml:compose.jellyfin.yaml:compose.languagetool.yaml`.
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
## Jellyfin
|
|
|
|
|
|
|
|
|
|
|
|
A Jellyfin server beside the instance, which the instance then suggests to its people
|
|
|
|
|
|
(`JELLYFIN_URL`, set by the compose file): the app's Jellyfin sign-in fills it in and
|
|
|
|
|
|
lists the people it shows on its login screen. Videos stream as stored, nothing is
|
|
|
|
|
|
transcoded. Caddy refuses every change to a user from outside, so an account without a
|
|
|
|
|
|
password (a demo's) stays everyone's; the admin signs in through an SSH tunnel only.
|
|
|
|
|
|
|
|
|
|
|
|
Steps, in order; ✱ marks a step as root.
|
|
|
|
|
|
|
|
|
|
|
|
1. ✱ **Its user and folders** (the operator, on the instance's server, as a user with
|
2026-10-14 01:24:50 +02:00
|
|
|
|
sudo): the user and group ID 61096 are Jellyfin'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); the media folder is `deploy`'s, so it can copy files
|
|
|
|
|
|
in, readable by all.
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-10-14 01:24:50 +02:00
|
|
|
|
sudo groupadd --system --gid 61096 jellyfin
|
|
|
|
|
|
sudo useradd --system --uid 61096 --gid 61096 --no-create-home --shell /usr/sbin/nologin jellyfin
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
cd ~deploy/halkyon-learn/deploy
|
|
|
|
|
|
sudo mkdir -p jellyfin/config jellyfin/cache jellyfin/media
|
|
|
|
|
|
sudo chown -R jellyfin:jellyfin jellyfin/config jellyfin/cache
|
|
|
|
|
|
sudo chown -R deploy:deploy jellyfin/media
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. **DNS** (the operator, at the domain's DNS): the Jellyfin domain, such as
|
|
|
|
|
|
`jellyfin.example.org`, pointing at the instance's server (`A`, and `AAAA` if it has
|
|
|
|
|
|
an IPv6 address).
|
|
|
|
|
|
|
|
|
|
|
|
3. **Switch it on** (the operator, on the instance's server, as `deploy`): in
|
|
|
|
|
|
`deploy/.env`,
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
COMPOSE_FILE=compose.yaml:compose.jellyfin.yaml
|
|
|
|
|
|
JELLYFIN_DOMAIN=jellyfin.example.org
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
4. **The media** (the operator, on their own machine): a folder per film, show or
|
2026-10-14 01:27:35 +02:00
|
|
|
|
album, as Jellyfin expects them, into `deploy/jellyfin/media/`, readable by all;
|
|
|
|
|
|
through `tar` and SSH, which every server has (rsync would have to be installed on
|
|
|
|
|
|
it too):
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-10-14 01:27:35 +02:00
|
|
|
|
tar -C "<the folder's parent>" -cf - "<folder>" |
|
|
|
|
|
|
ssh <deploy's ssh alias> 'tar -C halkyon-learn/deploy/jellyfin/media/movies -xf -'
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
5. **Start it** (the operator, on the instance's server, as `deploy`):
|
|
|
|
|
|
`cd ~/halkyon-learn/deploy && docker compose up -d`. Caddy and the server are
|
|
|
|
|
|
recreated with it (the site, and the instance's suggestion); the instance is away
|
|
|
|
|
|
for seconds.
|
|
|
|
|
|
|
|
|
|
|
|
6. **Set Jellyfin up through a tunnel** (the operator, on their own machine):
|
|
|
|
|
|
`ssh -N -L 8096:172.30.80.10:8096 <deploy's ssh alias>`, then http://localhost:8096.
|
|
|
|
|
|
- The wizard: an admin of a name nobody guesses, its password in a password
|
|
|
|
|
|
manager; a library of the type Movies (or Shows, Music) on `/media/…`; remote
|
|
|
|
|
|
connections allowed.
|
2026-10-20 21:49:29 +02:00
|
|
|
|
- Dashboard › Networking › **Known proxies**: `172.30.80.2` and `fd4a:6c6b:7e80::2`
|
|
|
|
|
|
(Caddy's addresses on `edge`). Jellyfin then sees each visitor's own address, and the tunnel as local.
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
- Dashboard › General: **Quick Connect** off, unless people sign in with it.
|
|
|
|
|
|
- Dashboard › Users › the admin: **Hide this user from login screens**; **Allow
|
|
|
|
|
|
remote connections to this server** off.
|
|
|
|
|
|
- For a demo, Dashboard › Users › **+**: an account without a password, shown on
|
|
|
|
|
|
login screens, seeing the demo's library alone; off: playback and conversion that
|
|
|
|
|
|
need transcoding, content downloading, remote control of other users, deleting
|
2026-10-14 01:52:42 +02:00
|
|
|
|
media. On its Profile tab, **Failed login tries before user is locked out** `-1`:
|
|
|
|
|
|
with Jellyfin's default of 3, anyone could switch the demo off with three wrong
|
|
|
|
|
|
passwords, and without a password the lockout guards nothing. People sign in to it
|
|
|
|
|
|
by tapping it and leaving the password empty: Jellyfin doesn't say which accounts
|
|
|
|
|
|
have none.
|
feat(deploy): sidecars beside the instance, Jellyfin the first, behind an edge network
An instance may now run services beside itself, each a compose file of
its own switched on in deploy/.env (COMPOSE_FILE=compose.yaml:compose.
jellyfin.yaml), so the deploy runs them with the instance. The first is
Jellyfin, which people watch and listen to learn from: the instance then
suggests it (JELLYFIN_URL from JELLYFIN_DOMAIN), and Caddy serves its
site, refusing every change to a user from outside, so a passwordless
demo account stays everyone's; its admin signs in through an SSH tunnel.
The instance's compose gets a second network, `edge`, for Caddy (at a
fixed address, which sidecars trust as their proxy) and the server;
Postgres stays on the internal one. Caddy loses its needless way to
Postgres, and a sidecar never gains one. A sidecar publishes no port,
as the CI runner may reach 127.0.0.1, and runs as a user ID of its own.
Caddy imports the sidecars' sites from sidecars/, mounted by their
compose files (none without), and redeploy.sh recreates it when one
changes. sidecars/README.md has the steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-14 00:16:53 +02:00
|
|
|
|
|
|
|
|
|
|
7. **Check from outside** (anyone, anywhere): `https://<domain>/System/Info/Public`
|
|
|
|
|
|
answers, `/Users/Public` lists the demo account, a `POST` to `/Users/Password` gets
|
|
|
|
|
|
403, and the instance's `instance.info()` names the domain as `jellyfinUrl`.
|
|
|
|
|
|
|
|
|
|
|
|
Switching it off: the compose file out of `COMPOSE_FILE`, then `docker compose up -d
|
|
|
|
|
|
--remove-orphans`; `deploy/jellyfin/` stays until removed by hand.
|
2026-10-25 23:13:46 +01:00
|
|
|
|
|
|
|
|
|
|
## 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.
|