feat(meditate,deploy): Meditate beside the instance, and every server's public address from .env

Meditate's deploy step (ADR 0038, step 7; ADR 0039, D14), as Todo's: the
server's Dockerfile with the library's check compiled beside it, its
production config on its own database and role, compose.meditate.yaml as a
member of the register, its site sending /library/* to the web server and
the rest to the API server, the operator's library read-only in a compose
file of its own, and redeploy.sh, backup.sh and the image replay taking it
in.

Every server's public address now comes from .env (Serverpod's
SERVERPOD_API_SERVER_PUBLIC_HOST and _WEB_SERVER_, which win over the
config file): Train, Todo and Meditate from their domains, Learn from
LEARN_PUBLIC_HOST, unset learn.halkyon.eu as before. Without it a server on
halkyon.li would name halkyon.eu in its SimpleLogin callback, profile pages
and privacy page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-08 19:12:31 +01:00
commit fd9c2dfe76
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -25,6 +25,8 @@ site changes.
| 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`.
@ -527,3 +529,70 @@ Steps, in order:
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.