source: Klonkt/deploy/MULTI-INSTANCE.md@ 5462bab

main
Last change on this file since 5462bab was 5462bab, checked in by Robin <roboburr@…>, 6 weeks ago

Fix 500 op de Uiterlijk-pagina na een halve update (boiert.eu)

Bart kreeg een 500 op /admin/sites/<slug>/edit. Oorzaak was een gemengde
staat: zijn klonkt-update was gegenereerd voor de oude single-service-layout
en herstart nog klonkt.service, de unit die de migratie juist heeft
uitgezet. De code op schijf werd dus wel bijgewerkt, maar het draaiende
proces nooit herstart. De oude route rendert dan het nieuwe template, dat
een variabele verwacht die de oude route niet meegeeft: ReferenceError, en
Express cachet het gecompileerde template tot de volgende herstart.

Drie lagen gefixt:

  1. Het aliasveld degradeert bij een ontbrekende variabele naar leeg in plaats van de hele pagina mee te nemen (typeof-guard, zelfde patroon als auth-register). Een deploy-moment mag nooit een 500 opleveren.
  2. De nieuw-site-form gaf dezelfde ReferenceError ook met volledig nieuwe code: de /new-route rendert hetzelfde template maar gaf apAliases niet mee. Lokaal gereproduceerd en bevestigd gefixt (beide pagina's 200).
  3. De wortel: een gedeeld script dat /usr/local/bin/klonkt-update herschrijft voor de actuele layout. De migratie draait het voortaan zelf, install.sh genereert de updater er ook mee (kan nooit meer uiteenlopen), en al gemigreerde servers repareren het met een los commando.

Changed files:
src/routes/admin-sites.js

  • /new geeft apAliases mee aan het template

src/views/pages/admin-site-edit.ejs

  • typeof-guard op apAliases met uitleg waarom

scripts/klonkt-migrate-data.sh

  • herschrijft de updater na de unit-omschakeling; waarschuwt als het script in een oudere checkout ontbreekt

scripts/install.sh

  • inline updater-generatie vervangen door het gedeelde script

deploy/MULTI-INSTANCE.md

  • reparatie-instructie voor servers die voor deze fix zijn gemigreerd

New file:
scripts/klonkt-refresh-updater.sh

  • idempotent; detecteert de branch uit de checkout; herstart alle klonkt@<slug>-instances, of klonkt.service als er geen zijn

-robo
Co-Authored-By: Claude Fable 5 <noreply@…>

  • Property mode set to 100644
File size: 6.7 KB
Line 
1# Running several Klonkts on one server
2
3One copy of the code, one directory of data per site. Adding a site is a
4directory and an `.env` file; updating is one command for all of them.
5
6```
7/opt/klonkt/ the code. Shared. Replaceable. No user data.
8/var/lib/klonkt/<slug>/ one instance: its .env, database, uploads.
9 .env
10 database.sqlite
11 media/
12 audio/
13```
14
15A *slug* is just a short name for an instance: `boiert`, `blog`, `label`. It is
16the directory name under `/var/lib/klonkt` and the systemd instance name
17(`klonkt@boiert`). It never appears in a URL.
18
19New installs get this layout on their own; the installer derives the slug from
20the domain (`boiert.eu` becomes `boiert`) unless you set `KLONKT_SLUG`. Servers
21installed before this existed keep their old layout untouched until you convert
22them, which is the section below.
23
24## Why the split
25
26When the database and uploads sit inside the checkout, the code directory holds
27live user data. That has three consequences you feel sooner or later:
28
29- **Updates get risky.** Anything that cleans up the checkout can reach your
30 uploads. Deleting and re-cloning the code becomes impossible.
31- **Backups get vague.** You either back up the code as well or you have to pick
32 the data out from between it.
33- **A second site needs a second copy of everything**, including `node_modules`,
34 and every copy has to be updated separately.
35
36After the split the checkout is disposable: throw it away, clone it again, and
37every instance still has its data. Backing up `/var/lib/klonkt/<slug>` backs up
38the whole instance, configuration included.
39
40## Who owns what
41
42Everything runs as the unprivileged `klonkt` system user, which is created by
43the installer and cannot log in.
44
45| Path | Owner | Mode | Written by |
46|---|---|---|---|
47| `/opt/klonkt` | `klonkt` | `0755` | `klonkt-update`, never the running app |
48| `/var/lib/klonkt/<slug>` | `klonkt` | `0750` | the app |
49| `/var/lib/klonkt/<slug>/.env` | `klonkt` | `0600` | you |
50
51Root is needed to install the systemd unit, create the directory and edit the
52web server config. The application itself never runs as root.
53
54The service unit narrows this further:
55
56```ini
57ProtectSystem=strict
58ReadWritePaths=/var/lib/klonkt/%i
59```
60
61The whole filesystem is read-only to the process except its own data directory.
62An instance cannot write into the code, and it cannot reach another instance's
63data, even if something inside the application goes wrong.
64
65## Migrating an existing install
66
67For a server that was installed the single site way, with everything under
68`/opt/klonkt`. Update the code first: the split needs a build where every media
69subdirectory follows `MEDIA_PATH`, and the script refuses to run on anything
70older.
71
72```bash
73klonkt-update
74sudo bash /opt/klonkt/scripts/klonkt-migrate-data.sh <slug> --dry-run
75sudo bash /opt/klonkt/scripts/klonkt-migrate-data.sh <slug>
76```
77
78Pick a slug that describes the site, for example `boiert` for boiert.eu. The
79script stops the service (so SQLite writes out its log), moves `storage/` and
80`.env` to `/var/lib/klonkt/<slug>/`, rewrites the three data paths, installs the
81systemd template, and switches from `klonkt.service` to `klonkt@<slug>`.
82
83Your web server config does not change. The instance keeps the same port,
84because the port comes from the same `.env`.
85
86**Rolling back.** The old `klonkt.service` is disabled but not deleted. To go
87back, move the data into `/opt/klonkt/storage`, restore the relative paths in
88`.env`, and run `systemctl enable --now klonkt`.
89
90## Adding an instance
91
92```bash
93sudo bash /opt/klonkt/scripts/klonkt-add-instance.sh blog blog.example.com
94```
95
96That creates `/var/lib/klonkt/blog`, writes an `.env` with a fresh random
97`SESSION_SECRET` and a free port, starts `klonkt@blog`, and adds a Caddy block
98for the domain. Pass a port as a third argument to choose it yourself, or
99`--no-caddy` if you run your own proxy.
100
101Point the DNS at the server first, otherwise the certificate cannot be issued.
102
103Doing it by hand comes down to the same four things:
104
105```bash
106sudo mkdir -p /var/lib/klonkt/blog
107sudo tee /var/lib/klonkt/blog/.env >/dev/null <<'ENV'
108NODE_ENV=production
109PORT=3001
110HOST=127.0.0.1
111SESSION_SECRET=<openssl rand -hex 32>
112PUBLIC_BASE_URL=https://blog.example.com
113DATABASE_PATH=/var/lib/klonkt/blog/database.sqlite
114MEDIA_PATH=/var/lib/klonkt/blog/media
115AUDIO_PATH=/var/lib/klonkt/blog/audio
116ENV
117sudo chown -R klonkt:klonkt /var/lib/klonkt/blog
118sudo chmod 750 /var/lib/klonkt/blog && sudo chmod 600 /var/lib/klonkt/blog/.env
119sudo systemctl enable --now klonkt@blog
120```
121
122Every instance needs its own `PORT` and its own `PUBLIC_BASE_URL`. Give each one
123its own `SESSION_SECRET` as well: sharing it would make sessions from one site
124valid on another.
125
126The three data paths are the only ones you need. Media subdirectories such as
127`media/avatars` and `media/post-images` follow `MEDIA_PATH` on their own.
128
129## Updating them all at once
130
131```bash
132sudo klonkt-update
133```
134
135**Migrated before August 2026?** Then your `klonkt-update` still restarts the
136retired `klonkt.service`: the code updates but the running process never
137follows, and after the next update the site can 500 on pages whose template
138and route no longer match. Repair it once:
139
140```bash
141sudo systemctl restart klonkt@<slug> # load the current code now
142sudo bash /opt/klonkt/scripts/klonkt-refresh-updater.sh # fix the updater for good
143```
144
145The migration script does this by itself nowadays.
146
147That pulls the code once into `/opt/klonkt`, reinstalls dependencies only when
148`package-lock.json` changed, and restarts every instance it finds under
149`/var/lib/klonkt`. There is one copy of the code, so no instance can lag behind.
150
151Watch one instance while it comes back:
152
153```bash
154journalctl -u klonkt@blog -f
155```
156
157## Backups
158
159One directory per instance:
160
161```bash
162systemctl stop klonkt@blog
163tar czf blog-$(date +%F).tar.gz -C /var/lib/klonkt blog
164systemctl start klonkt@blog
165```
166
167Stopping first keeps the SQLite file consistent. To back up without downtime,
168use `sqlite3 database.sqlite ".backup snapshot.db"` and archive the snapshot
169together with `media/` and `audio/`.
170
171There is nothing to back up in `/opt/klonkt`: it is a checkout of a public
172repository and `klonkt-update` recreates it.
173
174## Removing an instance
175
176```bash
177sudo systemctl disable --now klonkt@blog
178sudo rm -rf /var/lib/klonkt/blog # this deletes the site's data
179```
180
181Then remove its block from the web server config and reload it.
182
183## Everyday commands
184
185| | |
186|---|---|
187| `systemctl status klonkt@<slug>` | is it running |
188| `journalctl -u klonkt@<slug> -f` | follow the log |
189| `systemctl restart klonkt@<slug>` | restart one instance |
190| `klonkt-update` | update the code, restart all instances |
191| `ls /var/lib/klonkt` | which instances exist |
Note: See TracBrowser for help on using the repository browser.