source: Klonkt/deploy/MULTI-INSTANCE.md@ 2d38b22

main
Last change on this file since 2d38b22 was 2d38b22, checked in by roboburr <roboburr@…>, 5 weeks ago

Draaiboek voor wie Klonkt voor anderen draait (deploy/HOSTING.md)

DEPLOY.md en MULTI-INSTANCE.md leggen uit hoe je het aan de praat krijgt. Geen van
beide gaat over wat er verandert zodra de gegevens op je schijf niet meer van jou
zijn -- en met een Klonkt per persoon is dat vanaf de tweede instance het geval.

Bewust geen derde bron van waarheid voor de installatie: dit verwijst voor de
mechaniek naar de twee bestaande documenten en voegt toe wat nergens staat.

dag een drie dingen voordat je iemand vertelt dat het klaar is, waarvan

de belangrijkste: pak je back-up echt een keer uit

wekelijks wat je nakijkt, en waarom federatie-waarschuwingen het lezen waard

zijn (een gebruiker kan een week onzichtbaar zijn zonder fout)

bijwerken een commando herstart ALLE instances; hoe je terugrolt, en dat de

terugrol niet atomair is

kapot de vier gevallen die je echt tegenkomt
wat je houdt de private actor-sleutels. Wie die heeft kan namens die persoon

posten, en de rest van de fediverse ziet het verschil niet. Plus
de verwerkersrol onder de AVG.

weggaan exporteren voordat je verwijdert, en pas verwijderen als het

archief bij hen opent

niet doen onder meer: nooit beveiligingsupdates achter iets zetten

Elk commando is getoetst op de echte host in plaats van uit het hoofd
opgeschreven. Dat scheelde twee fouten: journalctl -u 'klonkt@*' kon ik niet
verifieren (geen rechten op het systeemjournaal), dus die staat er niet in; en de
systemctl-glob matcht alleen units die al geladen zijn, wat bij een terugrol
precies de gestopte instance overslaat -- dat staat er nu bij.

MULTI-INSTANCE.md verwijst ernaar; een document waar niets naar linkt wordt niet
gelezen.

  • Property mode set to 100644
File size: 7.2 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 and masked, not deleted.
87Masked because `disable` alone does not stop `systemctl restart klonkt` from
88starting it again, and a resurrected unit no longer finds its `.env` (that moved
89with the data): it would fall back to the defaults and write a fresh empty
90database into the checkout. To go back, move the data into `/opt/klonkt/storage`,
91restore the relative paths in `.env`, then `systemctl unmask klonkt` and
92`systemctl enable --now klonkt`.
93
94## Adding an instance
95
96```bash
97sudo bash /opt/klonkt/scripts/klonkt-add-instance.sh blog blog.example.com
98```
99
100That creates `/var/lib/klonkt/blog`, writes an `.env` with a fresh random
101`SESSION_SECRET` and a free port, starts `klonkt@blog`, and adds a Caddy block
102for the domain. Pass a port as a third argument to choose it yourself, or
103`--no-caddy` if you run your own proxy.
104
105Point the DNS at the server first, otherwise the certificate cannot be issued.
106
107Doing it by hand comes down to the same four things:
108
109```bash
110sudo mkdir -p /var/lib/klonkt/blog
111sudo tee /var/lib/klonkt/blog/.env >/dev/null <<'ENV'
112NODE_ENV=production
113PORT=3001
114HOST=127.0.0.1
115SESSION_SECRET=<openssl rand -hex 32>
116PUBLIC_BASE_URL=https://blog.example.com
117DATABASE_PATH=/var/lib/klonkt/blog/database.sqlite
118MEDIA_PATH=/var/lib/klonkt/blog/media
119AUDIO_PATH=/var/lib/klonkt/blog/audio
120ENV
121sudo chown -R klonkt:klonkt /var/lib/klonkt/blog
122sudo chmod 750 /var/lib/klonkt/blog && sudo chmod 600 /var/lib/klonkt/blog/.env
123sudo systemctl enable --now klonkt@blog
124```
125
126Every instance needs its own `PORT` and its own `PUBLIC_BASE_URL`. Give each one
127its own `SESSION_SECRET` as well: sharing it would make sessions from one site
128valid on another.
129
130The three data paths are the only ones you need. Media subdirectories such as
131`media/avatars` and `media/post-images` follow `MEDIA_PATH` on their own.
132
133## Updating them all at once
134
135```bash
136sudo klonkt-update
137```
138
139**Migrated before August 2026?** Then your `klonkt-update` still restarts the
140retired `klonkt.service`: the code updates but the running process never
141follows, and after the next update the site can 500 on pages whose template
142and route no longer match. Repair it once:
143
144```bash
145sudo systemctl restart klonkt@<slug> # load the current code now
146sudo bash /opt/klonkt/scripts/klonkt-refresh-updater.sh # fix the updater for good
147```
148
149The migration script does this by itself nowadays.
150
151That pulls the code once into `/opt/klonkt`, reinstalls dependencies only when
152`package-lock.json` changed, and restarts every instance it finds under
153`/var/lib/klonkt`. There is one copy of the code, so no instance can lag behind.
154
155Watch one instance while it comes back:
156
157```bash
158journalctl -u klonkt@blog -f
159```
160
161## Backups
162
163One directory per instance:
164
165```bash
166systemctl stop klonkt@blog
167tar czf blog-$(date +%F).tar.gz -C /var/lib/klonkt blog
168systemctl start klonkt@blog
169```
170
171Stopping first keeps the SQLite file consistent. To back up without downtime,
172use `sqlite3 database.sqlite ".backup snapshot.db"` and archive the snapshot
173together with `media/` and `audio/`.
174
175There is nothing to back up in `/opt/klonkt`: it is a checkout of a public
176repository and `klonkt-update` recreates it.
177
178## Removing an instance
179
180```bash
181sudo systemctl disable --now klonkt@blog
182sudo rm -rf /var/lib/klonkt/blog # this deletes the site's data
183```
184
185Then remove its block from the web server config and reload it.
186
187## Hosting for other people
188
189Everything here is about the machine. The moment an instance belongs to someone
190who is not you, there is a second set of questions — what you are holding, what
191you owe them, and how they leave. See [HOSTING.md](HOSTING.md).
192
193## Everyday commands
194
195| | |
196|---|---|
197| `systemctl status klonkt@<slug>` | is it running |
198| `journalctl -u klonkt@<slug> -f` | follow the log |
199| `systemctl restart klonkt@<slug>` | restart one instance |
200| `klonkt-update` | update the code, restart all instances |
201| `ls /var/lib/klonkt` | which instances exist |
Note: See TracBrowser for help on using the repository browser.