177 lines
11 KiB
Markdown
177 lines
11 KiB
Markdown
|
|
# ADR 0040: The Instance console: accounts, sign-up, assistants, regions and state in the apps
|
|||
|
|
|
|||
|
|
- **Status:** decided 8 Nov 2026 by the developer: D1 to D4 and D17 to D18 are their
|
|||
|
|
own choices, D5 to D16 the agent's recommendations, which they confirmed. Nothing is
|
|||
|
|
built yet.
|
|||
|
|
- **Builds on:** Halkyon Learn's ADR 0021 (operators, approval, the assistants and their
|
|||
|
|
grants: `InstanceOperatorEndpoint`, `AssistantOperatorEndpoint`, the operator's card,
|
|||
|
|
badge and screen in `halkyon_app`), ADR 0016 (the register and `INSTANCE_SECRET`), ADR
|
|||
|
|
0030, D44 and D50 (BRouter, the tiles and Photon for Train), ADR 0033, D32 (Todo's
|
|||
|
|
search on the same Photon), ADR 0039 (halkyon.li, whose regions come from here, D5,
|
|||
|
|
D19).
|
|||
|
|
|
|||
|
|
## Context
|
|||
|
|
|
|||
|
|
An instance's operator already approves, refuses and suspends accounts, names operators
|
|||
|
|
and grants assistants with budgets, in every app (ADR 0021). Everything else about the
|
|||
|
|
instance is in files on its host, changed over SSH, each change followed by a restart:
|
|||
|
|
|
|||
|
|
- who may sign up (`SIGN_UP` in `deploy/.env`);
|
|||
|
|
- which assistants and models are offered, with their keys (`deploy/assistants.env`);
|
|||
|
|
- the regions of Train's map, routes and search: `MAP_REGION` and `BROUTER_REGION` (boxes
|
|||
|
|
in degrees) and `PHOTON_REGION` (a country of GraphHopper's dumps), Europe by default.
|
|||
|
|
|
|||
|
|
Nothing in the apps shows whether the sidecars run or when the last backup was made.
|
|||
|
|
|
|||
|
|
halkyon.li (ADR 0039) runs on a home machine with 7.2 GB of RAM and 150 GB of free disk,
|
|||
|
|
where Europe's tiles (45 GB, twice while cut) and Europe's Photon (8 GB of memory) don't
|
|||
|
|
fit. Its family needs a few areas, smaller than a country: *Upper Bavaria, South*, for
|
|||
|
|
instance. CoMaps (Organic Maps' fork) cuts the world into 1,159 such areas for its own
|
|||
|
|
downloads, each a polygon in `data/borders/*.poly`, named by its place in the hierarchy
|
|||
|
|
(`Germany_Free State of Bavaria_Upper Bavaria_South`, 500 KB). The repository is under
|
|||
|
|
Apache 2.0; the borders come from OpenStreetMap's boundaries, so they carry
|
|||
|
|
"© OpenStreetMap contributors", as the tiles do.
|
|||
|
|
|
|||
|
|
How each sidecar can be cut:
|
|||
|
|
|
|||
|
|
- **Tiles**: `pmtiles extract` takes a GeoJSON polygon (`--region`) instead of a box.
|
|||
|
|
- **BRouter**: its segments are 5° by 5° and can't be cut. A region takes every segment
|
|||
|
|
its polygon touches.
|
|||
|
|
- **Photon**: GraphHopper's dumps stop at countries. Anything smaller is an index of our
|
|||
|
|
own: an extract of OpenStreetMap cut to the polygon (`osmium extract -p`), imported
|
|||
|
|
into a Nominatim database, which Photon then imports from (ADR 0039, D19).
|
|||
|
|
|
|||
|
|
## Decisions
|
|||
|
|
|
|||
|
|
### The developer's (8 Nov 2026)
|
|||
|
|
|
|||
|
|
- **D1 One row in Settings, a route of its own**: operators alone see a single row,
|
|||
|
|
*Instance*, in every app's Settings, with the count of accounts waiting as its subtitle
|
|||
|
|
and badge (replacing today's *This instance* card). It opens the *Instance* route, with
|
|||
|
|
*Accounts*, *Sign-up*, *Assistants*, *Regions* and *State*. On a phone each opens a
|
|||
|
|
screen of its own; on a wide window the route shows the list and the chosen part side by
|
|||
|
|
side. The badge on the Settings cog stays. Not in Settings itself (too much there), not a
|
|||
|
|
web console, not Django: the module's endpoints and `halkyon_app`'s screens, as for
|
|||
|
|
everything an operator does today.
|
|||
|
|
- **D2 The instance's settings are not backed up; what belongs to accounts is**: sign-up,
|
|||
|
|
the assistants offered and the regions are kept in a file of their own (D5), outside
|
|||
|
|
the database and every backup. Approvals, operators, grants, budgets and figures stay in
|
|||
|
|
the database and so in the backups, since a restore without them would leave everyone
|
|||
|
|
waiting.
|
|||
|
|
- **D3 The assistants' keys stay in `assistants.env`**: the console shows whether each
|
|||
|
|
assistant's key is set, never the key.
|
|||
|
|
- **D4 Regions are added and removed in the console**, from CoMaps' polygons. halkyon.li
|
|||
|
|
starts with none, and Train's and Todo's map, routes and search with them.
|
|||
|
|
|
|||
|
|
### Recommended by the agent, confirmed by the developer
|
|||
|
|
|
|||
|
|
- **D5 The settings file**: `settings.json` in a volume of its own, `instance_settings`,
|
|||
|
|
mounted into the register's server alone (Learn's, ADR 0016). It is written whole into
|
|||
|
|
a new file and moved over the old, so it is never read half written. `backup.sh` leaves
|
|||
|
|
the volume out. It holds no secret. Each setting comes from the file once the console
|
|||
|
|
has set it, otherwise from `.env`, as today. The console shows which applies (*Set here*
|
|||
|
|
or *From the server's configuration*) and can go back to `.env`'s. A lost file thus
|
|||
|
|
returns the instance to its `.env`, and the regions to none.
|
|||
|
|
- **D6 The members ask the register**: Todo's, Train's and Meditate's servers read the
|
|||
|
|
settings that concern them (sign-up, which every server enforces at sign-in, and the
|
|||
|
|
assistants, which Learn's server alone forwards today) from the register over the
|
|||
|
|
instance's own network, with `INSTANCE_SECRET`, as they ask it about accounts now. They
|
|||
|
|
cache an answer for a minute. A change thus takes at most a minute, with no restart.
|
|||
|
|
- **D7 Sign-up**: *Open*, *By approval* or *Closed*, a choice on the screen, applied
|
|||
|
|
within a minute. Closing it leaves waiting accounts waiting; they are approved or
|
|||
|
|
refused under *Accounts* as before.
|
|||
|
|
- **D8 Assistants**: each assistant's name, provider (protocol and host), model, efforts
|
|||
|
|
allowed, order and the ceilings of a request are kept in the file; its key stays
|
|||
|
|
`ASSISTANT_<ID>_KEY` in `assistants.env`. Adding an assistant in the console makes it
|
|||
|
|
*Key missing* until its key is in the file and the server restarted: the one restart
|
|||
|
|
left, as keys never pass through the app. Renaming an ID stays impossible (its grants
|
|||
|
|
hang on it); the console makes a new one instead. The grants and budgets move under
|
|||
|
|
*Assistants* as a second part, *Granted*.
|
|||
|
|
- **D9 Regions are CoMaps' polygons, named and fetched at a pinned commit**: the
|
|||
|
|
register's image carries the list of the 1,159 names (some 60 KB) of one CoMaps commit
|
|||
|
|
(`55846720`, 8 Nov 2026; moved by hand). The console shows them as a tree to search
|
|||
|
|
(*Germany › Free State of Bavaria › Upper Bavaria › South*), from the names' parts. An
|
|||
|
|
added region's polygon is downloaded from that commit on Codeberg once, converted to
|
|||
|
|
GeoJSON and kept beside `settings.json`. Nothing of CoMaps' is in the repository but
|
|||
|
|
the list.
|
|||
|
|
- **D10 One list of regions for all three**: a region added is covered by the tiles,
|
|||
|
|
the routes and the search together, as a family needs each where it goes; the console
|
|||
|
|
shows each service's state per region (confirmed by D17).
|
|||
|
|
- **D11 The sidecars bring themselves to what the register wants**: no server ever gets
|
|||
|
|
Docker's socket. Each sidecar's refresher (`map-tiles-refresh`, `brouter-segments`,
|
|||
|
|
and Photon's builder, D12) asks the register every 10 minutes for the wanted regions,
|
|||
|
|
their union as one GeoJSON MultiPolygon and its hash, with `INSTANCE_SECRET`, over a
|
|||
|
|
network it shares with the register's server alone. When the hash differs from what it
|
|||
|
|
last built, or a month has passed, it builds anew beside what it serves and moves it
|
|||
|
|
over, as today. It reports its state back: *building* (with what), *ready* (with size
|
|||
|
|
and date), *failed* (with the reason in a line), and the free space of its volume. The
|
|||
|
|
`.env` variables stay for an instance without the console's regions: with none set
|
|||
|
|
there, nothing is built.
|
|||
|
|
- tiles: `pmtiles extract --region=<the union>`;
|
|||
|
|
- routes: every segment whose 5° square the union's polygons touch (`E10_N45.rd5` for
|
|||
|
|
Upper Bavaria);
|
|||
|
|
- search: D12.
|
|||
|
|
- **D12 Photon's builder**: one container, `photon-build`, replaces `photon-data`. It
|
|||
|
|
carries osmium, a PostgreSQL with Nominatim (`mediagis/nominatim`, pinned by digest)
|
|||
|
|
and Photon's jar. When the regions change or a month has passed:
|
|||
|
|
1. it picks the smallest of Geofabrik's extracts that covers each polygon, from
|
|||
|
|
Geofabrik's index (`index-v1.json`, each extract's own polygon);
|
|||
|
|
2. it downloads them and cuts them to the union;
|
|||
|
|
3. it imports the cut into its Nominatim, then that into a new Photon database;
|
|||
|
|
4. it stops its PostgreSQL, deletes Nominatim's database and marks the new Photon
|
|||
|
|
database ready for `serve.sh`, as `data.sh` does now.
|
|||
|
|
|
|||
|
|
It is held to 3 GB of memory. While nothing is to be built, it holds nothing but its
|
|||
|
|
script. Photon's server then needs about 1 GB of heap for a few regions
|
|||
|
|
(`PHOTON_HEAP`), rather than 6.
|
|||
|
|
- **D13 Builds wait for the day**: refreshers and the builder are among the sidecars
|
|||
|
|
ADR 0039, D6, stops during the CI window from 02:00 to 05:00. A build already running
|
|||
|
|
is interrupted there and starts again afterwards.
|
|||
|
|
- **D14 Room before a region**: the console shows each volume's free space as reported.
|
|||
|
|
It warns, without refusing, when it is less than twice what the services take now,
|
|||
|
|
since a new cut is built beside the old.
|
|||
|
|
- **D15 State**: *State* shows what the instance can know without Docker:
|
|||
|
|
- each sidecar's last report (D11) and whether it is within the nightly window;
|
|||
|
|
- the last backup, from a line `backup.sh` writes (`state/backup.json` under
|
|||
|
|
`deploy/`, mounted read-only into the register's server: when, how large, whether it
|
|||
|
|
succeeded);
|
|||
|
|
- the version each app's server runs.
|
|||
|
|
|
|||
|
|
A sidecar that hasn't reported for two of its rounds shows as *Silent*.
|
|||
|
|
- **D16 Access**: every new endpoint of the console is the operator's alone, with tests
|
|||
|
|
first asserting that a person who isn't an operator, someone signed out and a member
|
|||
|
|
service are refused. The sidecars' two endpoints (the wanted regions, the reports) take
|
|||
|
|
`INSTANCE_SECRET` alone, with tests asserting that a person's session, operator or not,
|
|||
|
|
and a wrong or missing secret are refused. Reports are kept in memory only (the
|
|||
|
|
register's server forgets them when it restarts, and the next round reports again), so
|
|||
|
|
nothing of them reaches the database.
|
|||
|
|
|
|||
|
|
### Order of building
|
|||
|
|
|
|||
|
|
1. **Regions** (D9 to D14), which halkyon.li waits for.
|
|||
|
|
2. **Sign-up** (D5 to D7), with the settings file and the members asking for it.
|
|||
|
|
3. **Assistants** (D8).
|
|||
|
|
4. **State** (D15).
|
|||
|
|
5. The row in Settings and the route (D1), from step 1 on: *Accounts* is today's
|
|||
|
|
operator's screen.
|
|||
|
|
|
|||
|
|
### Decided after the proposal (8 Nov 2026)
|
|||
|
|
|
|||
|
|
- **D17 One list of regions** (the developer's choice), as D10 has it, over a choice per
|
|||
|
|
service, which would have let a region have routes without its search.
|
|||
|
|
- **D18 A region is built whenever it is added or removed** (the developer's choice),
|
|||
|
|
at any hour outside the CI window (D13), with no hours of its own.
|
|||
|
|
|
|||
|
|
## Consequences
|
|||
|
|
|
|||
|
|
- The settings' file joins `.env` as where an instance's configuration lives; after a
|
|||
|
|
restore the operator sets again what was set in the console. Approvals, operators,
|
|||
|
|
grants and budgets come back with the database.
|
|||
|
|
- A region costs disk and memory on its sidecars, which the console shows but does not
|
|||
|
|
refuse.
|
|||
|
|
- The register's server gains an instance network shared with the refreshers, which have
|
|||
|
|
a way out to the internet (`downloads`). Only its two secret-bound endpoints are of use
|
|||
|
|
to them, as with the members today.
|
|||
|
|
- Photon's builder brings Nominatim in, as an import step that leaves nothing running.
|
|||
|
|
- `sidecars/README.md` and `.env.example` describe the console as the way to set
|
|||
|
|
regions, sign-up and assistants, `.env` as their defaults.
|