The Jellyfin sidecar takes its library from a folder on the host where .env names one (JELLYFIN_MEDIA, read-only), else from deploy/jellyfin/media as before. On halkyon.li that is /data/media: the library moves out of the developer's home, which Jellyfin's own user can't enter. The sidecar README says how the home end's configuration, accounts and libraries move in, with its network settings corrected for Caddy before it starts. BREAKING CHANGE: compose.jellyfin-home.yaml, deploy/home-jellyfin/ and jellyfin.caddy's JELLYFIN_UPSTREAM are gone with ADR 0032, whose server end retires with learn.halkyon.eu's server; it was never brought up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
500 lines
26 KiB
Markdown
500 lines
26 KiB
Markdown
# 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.learn.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` |
|
||
| LanguageTool, which checks spelling and grammar | `compose.languagetool.yaml` | none |
|
||
| Halkyon Train's server, with BRouter for its routes | `compose.train.yaml` | `train.caddy` |
|
||
| Train's own map tiles, beside Train | `compose.maps.yaml` | `train-maps.caddy` (in Train's site) |
|
||
| Photon, which finds places for Train's route planner, beside Train | `compose.search.yaml` | none |
|
||
| Halkyon Todo's server | `compose.todo.yaml` | `todo.caddy` |
|
||
| Todo's place search, on Train's Photon | `compose.todo-search.yaml` | none |
|
||
| Halkyon Meditate's server | `compose.meditate.yaml` | `meditate.caddy` |
|
||
| Meditate's library, the operator's recordings, read-only | `compose.meditate-library.yaml` | none |
|
||
|
||
Both together: `COMPOSE_FILE=compose.learn.yaml:compose.jellyfin.yaml:compose.languagetool.yaml`.
|
||
|
||
## 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
|
||
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. The folders are named in full: `deploy`'s home is closed to
|
||
other users (Ubuntu's default), so a user with sudo can't `cd` into it, but `sudo`
|
||
reaches it by its path, which the shell makes of `~deploy` without entering it.
|
||
|
||
```sh
|
||
sudo groupadd --system --gid 61096 jellyfin
|
||
sudo useradd --system --uid 61096 --gid 61096 --no-create-home --shell /usr/sbin/nologin jellyfin
|
||
sudo mkdir -p ~deploy/halkyon/deploy/jellyfin/config \
|
||
~deploy/halkyon/deploy/jellyfin/cache ~deploy/halkyon/deploy/jellyfin/media
|
||
sudo chown -R jellyfin:jellyfin ~deploy/halkyon/deploy/jellyfin/config \
|
||
~deploy/halkyon/deploy/jellyfin/cache
|
||
sudo chown -R deploy:deploy ~deploy/halkyon/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.learn.yaml:compose.jellyfin.yaml
|
||
JELLYFIN_DOMAIN=jellyfin.example.org
|
||
```
|
||
|
||
4. **The media** (the operator, on their own machine): a folder per film, show or
|
||
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):
|
||
|
||
```sh
|
||
tar -C "<the folder's parent>" -cf - "<folder>" |
|
||
ssh <deploy's ssh alias> 'tar -C halkyon/deploy/jellyfin/media/movies -xf -'
|
||
```
|
||
|
||
5. **Start it** (the operator, on the instance's server, as `deploy`):
|
||
`cd ~/halkyon/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.
|
||
- 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.
|
||
- 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
|
||
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.
|
||
|
||
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.
|
||
|
||
## Jellyfin on halkyon.li, moved in from ADR 0032's home end
|
||
|
||
On halkyon.li (ADR 0039, D15), the Jellyfin that ran as ADR 0032's home end becomes the
|
||
sidecar above, with its configuration, accounts and library kept, on the same machine.
|
||
Its library stays on the host rather than in `deploy/jellyfin/media`. Once, in place of
|
||
the *Jellyfin* steps 1 and 4 to 6, after `../host/README.md`, step 4 (which stopped it),
|
||
✱ as root:
|
||
|
||
1. ✱ **Its user**, the sidecar's (ID 61096), as *Jellyfin*, step 1 has it.
|
||
2. ✱ **The library on the HDD, readable by Jellyfin**: the developer's home is closed to
|
||
other users, and Jellyfin runs as its own. `/home` is a bind mount of `/data/home`,
|
||
so this is a rename on one filesystem, instant whatever its size:
|
||
|
||
```sh
|
||
sudo mv /home/raisondetredev/Media /data/media
|
||
sudo chmod -R a+rX /data/media
|
||
```
|
||
|
||
`.env`: `JELLYFIN_MEDIA=/data/media`. Jellyfin's libraries keep their paths
|
||
(`/media/…`), as the folder is mounted where it was.
|
||
3. ✱ **Its configuration and cache into the deploy**, still Jellyfin's:
|
||
|
||
```sh
|
||
old=/home/raisondetredev/project-halkyon/halkyon/deploy/home-jellyfin
|
||
sudo install -d -o 61096 -g 61096 ~deploy/halkyon/deploy/jellyfin
|
||
sudo mv "$old/jellyfin/config" "$old/jellyfin/cache" ~deploy/halkyon/deploy/jellyfin/
|
||
```
|
||
|
||
4. ✱ **Its network settings for Caddy**, before it starts: ADR 0032's would count the
|
||
admin's requests as remote and lock them out. In
|
||
`~deploy/halkyon/deploy/jellyfin/config/network.xml` (`sudoedit`), the local networks
|
||
emptied and Caddy named as the proxy, as *Jellyfin*, step 6 has them:
|
||
|
||
```xml
|
||
<LocalNetworkSubnets />
|
||
<KnownProxies>
|
||
<string>172.30.80.2</string>
|
||
<string>fd4a:6c6b:7e80::2</string>
|
||
</KnownProxies>
|
||
```
|
||
|
||
5. ✱ **ADR 0032's tunnel removed**, its key with it (it never had a peer):
|
||
|
||
```sh
|
||
sudo rm -rf "$old"
|
||
```
|
||
|
||
6. **Switch it on** as *Jellyfin*, step 3 (`JELLYFIN_DOMAIN=jellyfin.halkyon.li`), and
|
||
start it (step 5). The admin then works through *Jellyfin*, step 6's tunnel from
|
||
melchior, and checks under Dashboard › Networking that the settings of step 4 hold.
|
||
Check from outside as step 7.
|
||
|
||
## 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). The folder is named in full, as Jellyfin's.
|
||
|
||
```sh
|
||
sudo groupadd --system --gid 61081 languagetool
|
||
sudo useradd --system --uid 61081 --gid 61081 --no-create-home --shell /usr/sbin/nologin languagetool
|
||
sudo mkdir -p ~deploy/halkyon/deploy/languagetool/fasttext
|
||
sudo chown -R languagetool:languagetool ~deploy/halkyon/deploy/languagetool
|
||
```
|
||
|
||
2. **Switch it on** (the operator, on the instance's server, as `deploy`): in
|
||
`deploy/.env`, `COMPOSE_FILE=compose.learn.yaml:compose.languagetool.yaml`.
|
||
|
||
3. **Start it** (the operator, on the instance's server, as `deploy`):
|
||
`cd ~/halkyon/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.
|
||
|
||
## Train
|
||
|
||
Halkyon Train's server beside the instance (Halkyon Train's ADR 0030, D46), at its own
|
||
domain, a member of the register Learn's server holds (ADR 0016), its database
|
||
`halkyon_train` in the instance's PostgreSQL, and BRouter planning its routes (D11, D44)
|
||
from the segments of the operator's region (Europe's by default, about 3.3 GB on
|
||
28 Oct 2026), refreshed monthly. `redeploy.sh` builds, dumps, migrates and waits for it
|
||
as for Learn's server once `.env` names its compose file, and `backup.sh` backs its
|
||
database up beside Learn's. The map stays OpenFreeMap's until the instance serves its
|
||
own tiles (D22, D23).
|
||
|
||
Steps, in order; ✱ marks a step as root.
|
||
|
||
1. **Room on the disk** (the operator, on the instance's server): `df -h ~deploy`.
|
||
BRouter's segments take about 3.3 GB for Europe, and a refresh writes each one beside
|
||
the old before replacing it; Train's dumps go to `~/backups` with Learn's.
|
||
2. **Its database** (the operator, on the instance's server, as `deploy`, in
|
||
`~/halkyon/deploy`): a role of Train's own, with the password that goes into
|
||
`.env` as `TRAIN_POSTGRES_PASSWORD` (`openssl rand -hex 32`), and its database:
|
||
|
||
```sh
|
||
docker compose exec -T postgres psql -U halkyon_learn -d postgres -v ON_ERROR_STOP=1 \
|
||
-v password="<TRAIN_POSTGRES_PASSWORD>" <<'SQL'
|
||
CREATE ROLE halkyon_train LOGIN PASSWORD :'password';
|
||
CREATE DATABASE halkyon_train OWNER halkyon_train;
|
||
REVOKE CONNECT ON DATABASE halkyon_train FROM PUBLIC;
|
||
REVOKE CONNECT ON DATABASE halkyon_learn FROM PUBLIC;
|
||
SQL
|
||
```
|
||
|
||
`CREATE ROLE` makes the role Train's server signs in as, which may log in and is
|
||
nothing more (no superuser, no creating roles or databases); `CREATE DATABASE … OWNER`
|
||
gives it its own database, where its migrations make the tables. The two `REVOKE`s
|
||
take away the right every role has by default to connect to a database: only its
|
||
owner (and the instance's superuser, `halkyon_learn`, which Learn's server signs in
|
||
as) connects to `halkyon_train`, and `halkyon_train` no longer connects to Learn's.
|
||
Nothing of Learn's changes. (Learn's server, signing in as the superuser, could still
|
||
read Train's; giving it a role of its own is a step of its own.)
|
||
3. ✱ **BRouter's user and folder** (the operator, as a user with sudo): the user and
|
||
group ID 61177 are BRouter's alone, as the compose file runs it; the folder its
|
||
segments go to is theirs.
|
||
|
||
```sh
|
||
sudo groupadd --system --gid 61177 brouter
|
||
sudo useradd --system --uid 61177 --gid 61177 --no-create-home --shell /usr/sbin/nologin brouter
|
||
sudo mkdir -p ~deploy/halkyon/deploy/brouter/segments
|
||
sudo chown -R brouter:brouter ~deploy/halkyon/deploy/brouter/segments
|
||
```
|
||
|
||
4. **DNS** (the operator, at the domain's DNS): Train's domain, `train.halkyon.eu`,
|
||
pointing at the instance's server (`A`, and `AAAA` if it has an IPv6 address).
|
||
5. **Switch it on** (the operator, as `deploy`): in `deploy/.env`, `compose.train.yaml`
|
||
added to `COMPOSE_FILE`, and Train's settings (`.env.example`, *With Halkyon Train*):
|
||
|
||
```sh
|
||
COMPOSE_FILE=compose.learn.yaml:compose.languagetool.yaml:compose.train.yaml
|
||
TRAIN_DOMAIN=train.halkyon.eu
|
||
TRAIN_POSTGRES_PASSWORD=<as in step 2>
|
||
TRAIN_SESSION_KEY_HASH_PEPPER=<openssl rand -hex 32>
|
||
```
|
||
|
||
`INSTANCE_SECRET` must be set (Learn's register takes no member without it). The
|
||
compose file names Train to Learn's register (`INSTANCE_MEMBER_TRAIN`), whatever
|
||
`INSTANCE_MEMBERS` holds.
|
||
6. **Start it** (the operator, as `deploy`): `docker compose up -d --build`. Caddy and
|
||
Learn's server are recreated with it (Train's site, and the register's member); the
|
||
instance is away for seconds. Train's server applies its migrations as it starts.
|
||
The segments come in the first hour or so (`docker compose logs brouter-segments`
|
||
says when); until then BRouter finds no route. Where brouter.de doesn't answer or
|
||
the folder isn't BRouter's, the log says so and it tries again an hour later.
|
||
7. **Check it**: `https://train.halkyon.eu/` answers; in Train, signed in with a Learn
|
||
account, a route plans between two points of the region.
|
||
|
||
Workouts' weather (Halkyon Train's ADR 0036) comes from Open-Meteo through Train's
|
||
server, on by default, the free API from the server's address (non-commercial use alone,
|
||
10,000 requests a day). `WEATHER=off` in `deploy/.env` stops it; an instance with ads or
|
||
subscriptions needs one of Open-Meteo's paid plans, its key in `OPEN_METEO_API_KEY`.
|
||
|
||
|
||
## Train's map
|
||
|
||
The instance's own map for Halkyon Train (Halkyon Train's ADR 0030, D50), beside Train
|
||
(`compose.train.yaml` first): Protomaps' basemap cut for Europe from Protomaps' daily
|
||
build (the planet's was 139 GB on 29 Oct 2026; Europe's cut is estimated at some 45 GB,
|
||
to be measured), served by `pmtiles serve` and drawn by Train's own style, which Train's
|
||
server serves. Only people signed in to Train get tiles: `map.source` hands the app a
|
||
key, which Caddy has Train's server check before each tile. No tile is logged. The cut
|
||
is renewed monthly where Protomaps has a newer build, written beside the old one and
|
||
moved over it. Off, Train's maps are OpenFreeMap's (or `MAP_STYLE_URL`'s) again.
|
||
|
||
Steps, in order; ✱ marks a step as root.
|
||
|
||
1. **Room on the disk** (the operator, on the instance's server): `df -h ~deploy`.
|
||
A cut takes some 45 GB, and twice that while a new one is cut beside it.
|
||
2. ✱ **The tiles' user and folder** (the operator, as a user with sudo): the user and
|
||
group ID 61178 are the tiles' alone, as the compose file runs them.
|
||
|
||
```sh
|
||
sudo groupadd --system --gid 61178 maptiles
|
||
sudo useradd --system --uid 61178 --gid 61178 --no-create-home --shell /usr/sbin/nologin maptiles
|
||
sudo mkdir -p ~deploy/halkyon/deploy/maps/tiles
|
||
sudo chown -R maptiles:maptiles ~deploy/halkyon/deploy/maps/tiles
|
||
```
|
||
|
||
3. **Switch it on** (the operator, as `deploy`): in `deploy/.env`, `compose.maps.yaml`
|
||
added to `COMPOSE_FILE` after Train's, and the secret the keys are made with:
|
||
|
||
```sh
|
||
COMPOSE_FILE=compose.learn.yaml:compose.languagetool.yaml:compose.train.yaml:compose.maps.yaml
|
||
MAP_KEY_SECRET=<openssl rand -hex 32>
|
||
```
|
||
|
||
`MAP_STYLE_URL` is then not used. Changing `MAP_KEY_SECRET` refuses every key handed
|
||
out before, until the apps ask for the source again (as each opens).
|
||
4. **Start it** (the operator, as `deploy`): `docker compose up -d --build`. Caddy and
|
||
Train's server are recreated with it. The first cut takes hours
|
||
(`docker compose logs -f map-tiles-refresh` says when it is done); until then Train's
|
||
maps stay empty. A failed cut is tried again an hour later.
|
||
5. **Check it**: in Train, signed in, a workout's map is drawn and names the instance as
|
||
its source; `curl -I https://train.halkyon.eu/tiles/europe/0/0/0.mvt` is refused
|
||
with 401. Not yet tried on the server (29 Oct 2026): whether `pmtiles serve` reads a
|
||
new cut moved over the old one without a restart (else `docker compose restart
|
||
map-tiles` after each cut), and Caddy's `import` of an empty folder where the map is
|
||
off.
|
||
|
||
## Train's search
|
||
|
||
Photon beside Train (Halkyon Train's ADR 0030, D50; `compose.train.yaml` first), which
|
||
finds the places Train's route planner searches for, on GraphHopper's weekly database
|
||
of Europe (a 32.6 GB download on 29 Oct 2026, more unpacked, to be measured; Germany's
|
||
alone 7.5 GB, `PHOTON_REGION=europe/germany`). Only Train's server reaches it, for
|
||
people signed in; nothing searched is kept or logged. The database is renewed monthly:
|
||
downloaded and unpacked beside the one in use, checked against its MD5, then put in
|
||
its place, Photon away for a minute or two.
|
||
|
||
Steps, in order; ✱ marks a step as root.
|
||
|
||
1. **Room and memory** (the operator, on the instance's server): `df -h ~deploy` and
|
||
`free -h`. The database takes tens of GB, twice that while a new one comes; Photon
|
||
wants `PHOTON_HEAP` (6g by default) and room beside it (`PHOTON_MEMORY`, 8g).
|
||
2. ✱ **Photon's user and folder** (the operator, as a user with sudo): the user and
|
||
group ID 61179 are Photon's alone, as the compose file runs it.
|
||
|
||
```sh
|
||
sudo groupadd --system --gid 61179 photon
|
||
sudo useradd --system --uid 61179 --gid 61179 --no-create-home --shell /usr/sbin/nologin photon
|
||
sudo mkdir -p ~deploy/halkyon/deploy/photon/data
|
||
sudo chown -R photon:photon ~deploy/halkyon/deploy/photon/data
|
||
```
|
||
|
||
3. **Switch it on** (the operator, as `deploy`): in `deploy/.env`, `compose.search.yaml`
|
||
added to `COMPOSE_FILE` after Train's:
|
||
|
||
```sh
|
||
COMPOSE_FILE=compose.learn.yaml:compose.languagetool.yaml:compose.train.yaml:compose.maps.yaml:compose.search.yaml
|
||
```
|
||
|
||
4. **Start it** (the operator, as `deploy`): `docker compose up -d --build`. Train's
|
||
server is recreated with it. The first database takes an hour or more
|
||
(`docker compose logs -f photon-data` says when it is ready); Photon starts on it
|
||
within a minute, and until then Train's planner offers no search.
|
||
5. **Check it**: in Train, signed in, the planner's search finds a town of the region.
|
||
Not yet tried on the server (29 Oct 2026): the database's size unpacked, Photon's
|
||
memory on Europe's, and that 1.3.0 reads GraphHopper's `1.0` build.
|
||
|
||
|
||
## Todo
|
||
|
||
Halkyon Todo's server beside the instance (Halkyon Todo's ADR 0033, step 5), at its own
|
||
domain, a member of the register Learn's server holds (ADR 0016), its database
|
||
`halkyon_todo` in the instance's PostgreSQL. It reads no task: every row is sealed on
|
||
the person's devices (ADR 0033, D5), so it needs no sidecar of its own. `redeploy.sh`
|
||
builds, dumps, migrates and waits for it as for Learn's server once `.env` names its
|
||
compose file, and `backup.sh` backs its database up beside Learn's.
|
||
|
||
Steps, in order:
|
||
|
||
1. **Its database** (the operator, on the instance's server, as `deploy`, in
|
||
`~/halkyon/deploy`): a role of Todo's own, with the password that goes into
|
||
`.env` as `TODO_POSTGRES_PASSWORD` (`openssl rand -hex 32`), and its database, as
|
||
Train's (*Train*, step 2, says what each line does):
|
||
|
||
```sh
|
||
docker compose exec -T postgres psql -U halkyon_learn -d postgres -v ON_ERROR_STOP=1 \
|
||
-v password="<TODO_POSTGRES_PASSWORD>" <<'SQL'
|
||
CREATE ROLE halkyon_todo LOGIN PASSWORD :'password';
|
||
CREATE DATABASE halkyon_todo OWNER halkyon_todo;
|
||
REVOKE CONNECT ON DATABASE halkyon_todo FROM PUBLIC;
|
||
SQL
|
||
```
|
||
|
||
The `REVOKE`s of Learn's and Train's databases were made with Train's, so
|
||
`halkyon_todo` connects to neither; on an instance without Train, also
|
||
`REVOKE CONNECT ON DATABASE halkyon_learn FROM PUBLIC;`.
|
||
2. **DNS** (the operator, at the domain's DNS): Todo's domain, `todo.halkyon.eu`,
|
||
pointing at the instance's server (`A`, and `AAAA` if it has an IPv6 address).
|
||
3. **Switch it on** (the operator, as `deploy`): in `deploy/.env`, `compose.todo.yaml`
|
||
added to `COMPOSE_FILE`, and Todo's settings (`.env.example`, *With Halkyon Todo*):
|
||
|
||
```sh
|
||
COMPOSE_FILE=compose.learn.yaml:compose.languagetool.yaml:compose.train.yaml:compose.todo.yaml
|
||
TODO_DOMAIN=todo.halkyon.eu
|
||
TODO_POSTGRES_PASSWORD=<as in step 1>
|
||
TODO_SESSION_KEY_HASH_PEPPER=<openssl rand -hex 32>
|
||
```
|
||
|
||
`INSTANCE_SECRET` must be set. The compose file names Todo to Learn's register
|
||
(`INSTANCE_MEMBER_TODO`).
|
||
4. **Start it** (the operator, as `deploy`): `docker compose up -d --build`. Caddy and
|
||
Learn's server are recreated with it (Todo's site, and the register's member); the
|
||
instance is away for seconds. Todo's server applies its migrations as it starts.
|
||
5. **Check it**: `https://todo.halkyon.eu/` answers; in Todo, signed in with a Learn
|
||
account, a task added on one device shows on another.
|
||
6. **Place search** (optional; ADR 0033, D32): Todo's locations find places through the
|
||
Photon of *Train's search*, set up first. In `deploy/.env`,
|
||
`compose.todo-search.yaml` added to `COMPOSE_FILE` after `compose.todo.yaml` and
|
||
`compose.search.yaml`, then `docker compose up -d`, which recreates Todo's server
|
||
alone. Without it Todo starts as it did, and its app offers no search. Nothing
|
||
searched is kept or logged, and a task's location stays sealed on the person's
|
||
devices.
|
||
|
||
## Meditate
|
||
|
||
Halkyon Meditate's server beside the instance (Halkyon Meditate's ADR 0038, step 7;
|
||
ADR 0039, D14), as Todo's: at its own domain, a member of the register Learn's server
|
||
holds (ADR 0016), its database `halkyon_meditate` in the instance's PostgreSQL. It
|
||
reads no sitting and no dream: every row is sealed on the person's devices (ADR 0038,
|
||
D7). `redeploy.sh` builds, dumps, migrates and waits for it once `.env` names its
|
||
compose file, and `backup.sh` backs its database up beside the others'.
|
||
|
||
Steps, in order:
|
||
|
||
1. **Its database** (the operator, on the instance's server, as `deploy`, in
|
||
`~/halkyon/deploy`): a role of Meditate's own, with the password that goes into
|
||
`.env` as `MEDITATE_POSTGRES_PASSWORD` (`openssl rand -hex 32`), and its database, as
|
||
Todo's:
|
||
|
||
```sh
|
||
docker compose exec -T postgres psql -U halkyon_learn -d postgres -v ON_ERROR_STOP=1 \
|
||
-v password="<MEDITATE_POSTGRES_PASSWORD>" <<'SQL'
|
||
CREATE ROLE halkyon_meditate LOGIN PASSWORD :'password';
|
||
CREATE DATABASE halkyon_meditate OWNER halkyon_meditate;
|
||
REVOKE CONNECT ON DATABASE halkyon_meditate FROM PUBLIC;
|
||
SQL
|
||
```
|
||
|
||
The role owns its database alone and connects to no other: the `REVOKE`s of
|
||
Learn's, Train's and Todo's databases were made with theirs (on an instance without
|
||
them, also `REVOKE CONNECT ON DATABASE halkyon_learn FROM PUBLIC;`).
|
||
2. **DNS** (the operator, at the domain's DNS): Meditate's domain pointing at the
|
||
instance's server (`A`, and `AAAA` if it has an IPv6 address); for halkyon.li, at
|
||
ramiel (ADR 0039, D7).
|
||
3. **Switch it on** (the operator, as `deploy`): in `deploy/.env`,
|
||
`compose.meditate.yaml` added to `COMPOSE_FILE`, and Meditate's settings
|
||
(`.env.example`, *With Halkyon Meditate*):
|
||
|
||
```sh
|
||
COMPOSE_FILE=compose.learn.yaml:compose.train.yaml:compose.todo.yaml:compose.meditate.yaml
|
||
MEDITATE_DOMAIN=meditate.halkyon.li
|
||
MEDITATE_POSTGRES_PASSWORD=<as in step 1>
|
||
MEDITATE_SESSION_KEY_HASH_PEPPER=<openssl rand -hex 32>
|
||
```
|
||
|
||
`INSTANCE_SECRET` must be set. The compose file names Meditate to Learn's register
|
||
(`INSTANCE_MEMBER_MEDITATE`).
|
||
4. **Start it** (the operator, as `deploy`): `docker compose up -d --build`. Caddy and
|
||
Learn's server are recreated with it (Meditate's site, and the register's member);
|
||
the instance is away for seconds. Meditate's server applies its migrations as it
|
||
starts.
|
||
5. **Check it**: `https://meditate.halkyon.li/` answers; in Meditate, signed in with the
|
||
instance's account, a sitting kept on one device shows on another.
|
||
6. **The library** (optional; ADR 0038, D16): recordings, pictures and `library.yaml`
|
||
(the server's `library.example.yaml` says how) in a folder on the host, readable by
|
||
all (the server runs as an unprivileged user), such as `/data/meditate-library`.
|
||
Check it with the server's own image, which reads it as the server will and exits 1
|
||
on anything it would refuse:
|
||
|
||
```sh
|
||
docker compose run --rm --no-deps -v /data/meditate-library:/check:ro \
|
||
--entrypoint ./bin/check_library meditate-server /check
|
||
```
|
||
|
||
Then, in `deploy/.env`, `compose.meditate-library.yaml` added to `COMPOSE_FILE` after
|
||
`compose.meditate.yaml`, and `MEDITATE_LIBRARY=/data/meditate-library`, then
|
||
`docker compose up -d`, which recreates Meditate's server alone. It rereads the
|
||
folder every ten minutes, so later changes need no restart; check each before it is
|
||
copied in. Without the file, Meditate starts as before, and its app shows no library.
|