source: Klonkt/scripts/klonkt-migrate-data.sh@ f434294

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

Maskeer de oude klonkt.service bij de split, hij stond op te staan uit de dood

Op boiert.eu verscheen na de update opnieuw een /opt/klonkt/storage. Oorzaak:
de migratie deed alleen systemctl disable op klonkt.service, maar disable
haalt uitsluitend de autostart weg. Een systemctl restart klonkt start zo'n
unit alsnog, en precies dat staat in elke updater die voor de split is
gegenereerd.

De opgestane klonkt.service heeft WorkingDirectory=/opt/klonkt en geen
EnvironmentFile, terwijl zijn .env met de data mee is verhuisd naar
/var/lib/klonkt/<slug>/. Hij start dus zonder enige config, valt terug op de
ingebouwde standaardpaden en schrijft een verse lege database in de checkout.
Afhankelijk van wie de poort pakt levert dat een lege site op, of een
crashende unit die bij elke herstart de map opnieuw aanmaakt.

Disable is dus niet genoeg: de unit wordt nu ook gemaskeerd, zodat elke
aanroep hard faalt in plaats van stilletjes een tweede proces te starten.
Omkeerbaar met systemctl unmask.

Changed files:
scripts/klonkt-migrate-data.sh

  • oude unit wordt gestopt, uitgezet en gemaskeerd
  • test op list-unit-files in plaats van is-enabled, zodat een al uitgezette unit niet wordt overgeslagen

scripts/klonkt-refresh-updater.sh

  • maskeert de oude unit alsnog op een gesplitste installatie, zodat al gemigreerde servers zichzelf repareren
  • waarschuwt als er een storage/ in de checkout staat, met het commando om te controleren of er iets in zit; verwijdert nooit zelf

deploy/MULTI-INSTANCE.md

  • rollback bijgewerkt: unmask hoort er nu bij, met de reden

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

  • Property mode set to 100755
File size: 7.4 KB
Line 
1#!/usr/bin/env bash
2#
3# Move an existing Klonkt install to the split layout:
4#
5# /opt/klonkt/ shared code, read-only at runtime
6# /var/lib/klonkt/<slug>/ this instance's data and .env
7#
8# Before, an instance kept its database, uploads and .env inside the checkout.
9# That made the code directory undeletable (it held live user data), made
10# backups awkward, and meant a second instance needed a second copy of the code.
11#
12# Run as root on the server. Safe to re-run: it stops at the first step that is
13# already done rather than moving anything twice.
14#
15# sudo bash scripts/klonkt-migrate-data.sh <slug>
16# sudo bash scripts/klonkt-migrate-data.sh <slug> --dry-run
17#
18# The slug names the instance and nothing else: it is the directory under
19# /var/lib/klonkt and the systemd instance name (klonkt@<slug>).
20
21set -euo pipefail
22
23KLONKT_DIR="${KLONKT_DIR:-/opt/klonkt}"
24KLONKT_USER="${KLONKT_USER:-klonkt}"
25DATA_ROOT="${KLONKT_DATA_ROOT:-/var/lib/klonkt}"
26OLD_UNIT="klonkt.service"
27
28SLUG=""
29DRY=0
30for arg in "$@"; do
31 case "$arg" in
32 --dry-run) DRY=1 ;;
33 -*) echo "unknown option: $arg" >&2; exit 2 ;;
34 *) SLUG="$arg" ;;
35 esac
36done
37
38say() { printf ' %s\n' "$*"; }
39step() { printf '\n== %s\n' "$*"; }
40die() { printf '\nERROR: %s\n' "$*" >&2; exit 1; }
41run() { if [ "$DRY" = 1 ]; then printf ' [dry-run] %s\n' "$*"; else eval "$@"; fi; }
42
43[ "$(id -u)" = 0 ] || die "run this as root (sudo)."
44[ -n "$SLUG" ] || die "usage: $0 <slug> [--dry-run] e.g. $0 boiert"
45[[ "$SLUG" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || die "slug must be lowercase letters, digits, dot, dash or underscore."
46
47DATA_DIR="$DATA_ROOT/$SLUG"
48ENV_OLD="$KLONKT_DIR/.env"
49ENV_NEW="$DATA_DIR/.env"
50
51step "Preflight"
52[ -d "$KLONKT_DIR" ] || die "no install at $KLONKT_DIR"
53[ -f "$ENV_OLD" ] || [ -f "$ENV_NEW" ] || die "no .env at $ENV_OLD (already migrated elsewhere?)"
54id -u "$KLONKT_USER" >/dev/null 2>&1 || die "user $KLONKT_USER does not exist"
55
56# The split only works on code where every media subdirectory derives from
57# MEDIA_PATH. On older code the subdirectories fall back into the checkout, so
58# the app would quietly recreate storage/ next to the code and uploads would
59# land there.
60[ -f "$KLONKT_DIR/src/config/paths.js" ] || die \
61 "this build is too old for the split layout: src/config/paths.js is missing.
62 Update first (git pull in $KLONKT_DIR), then run this again."
63say "code at $KLONKT_DIR supports MEDIA_PATH-derived subdirectories"
64
65if [ -d "$DATA_DIR" ] && [ -n "$(ls -A "$DATA_DIR" 2>/dev/null)" ]; then
66 die "$DATA_DIR already exists and is not empty. Remove it or pick another slug."
67fi
68say "target $DATA_DIR is free"
69[ "$DRY" = 1 ] && say "DRY RUN: nothing will be changed"
70
71step "Stopping the service"
72if systemctl is-active --quiet "$OLD_UNIT"; then
73 run "systemctl stop $OLD_UNIT"
74 say "stopped $OLD_UNIT (SQLite checkpoints its write-ahead log on shutdown)"
75else
76 say "$OLD_UNIT was not running"
77fi
78
79step "Creating the data directory"
80run "mkdir -p '$DATA_DIR'"
81
82step "Moving data out of the checkout"
83if [ -d "$KLONKT_DIR/storage" ]; then
84 say "found $(find "$KLONKT_DIR/storage" -type f 2>/dev/null | wc -l) files in storage/ ($(du -sh "$KLONKT_DIR/storage" 2>/dev/null | cut -f1))"
85 # Everything, including database.sqlite plus its -wal and -shm siblings.
86 run "shopt -s dotglob nullglob; for f in '$KLONKT_DIR/storage/'*; do mv \"\$f\" '$DATA_DIR/'; done"
87 run "rmdir '$KLONKT_DIR/storage' 2>/dev/null || true"
88 say "moved to $DATA_DIR"
89else
90 say "no storage/ directory (already moved?)"
91fi
92
93if [ -f "$ENV_OLD" ]; then
94 run "mv '$ENV_OLD' '$ENV_NEW'"
95 say "moved .env to $ENV_NEW"
96fi
97
98step "Pointing the data paths at the new location"
99# Replace when present, append when absent, so this works regardless of which
100# variables the original install wrote.
101set_env() {
102 local key="$1" val="$2"
103 if [ "$DRY" = 1 ]; then printf ' [dry-run] %s=%s\n' "$key" "$val"; return; fi
104 if grep -q "^${key}=" "$ENV_NEW" 2>/dev/null; then
105 sed -i "s#^${key}=.*#${key}=${val}#" "$ENV_NEW"
106 else
107 printf '%s=%s\n' "$key" "$val" >> "$ENV_NEW"
108 fi
109 printf ' %s=%s\n' "$key" "$val"
110}
111set_env DATABASE_PATH "$DATA_DIR/database.sqlite"
112set_env MEDIA_PATH "$DATA_DIR/media"
113set_env AUDIO_PATH "$DATA_DIR/audio"
114
115step "Ownership and permissions"
116run "chown -R '$KLONKT_USER:$KLONKT_USER' '$DATA_DIR'"
117run "chmod 750 '$DATA_DIR'"
118run "chmod 600 '$ENV_NEW'"
119say "data owned by $KLONKT_USER, .env readable only by that user"
120
121step "Installing the systemd template"
122if [ -f "$KLONKT_DIR/deploy/klonkt@.service" ]; then
123 run "install -m 0644 '$KLONKT_DIR/deploy/klonkt@.service' /etc/systemd/system/klonkt@.service"
124 say "installed /etc/systemd/system/klonkt@.service"
125else
126 die "template not found at $KLONKT_DIR/deploy/klonkt@.service"
127fi
128run "systemctl daemon-reload"
129
130step "Switching to klonkt@$SLUG"
131if systemctl list-unit-files "$OLD_UNIT" >/dev/null 2>&1; then
132 run "systemctl disable --now $OLD_UNIT"
133 # Disable only removes the autostart link: `systemctl restart klonkt` would
134 # still START it. That is not theoretical — an updater generated before the
135 # split does exactly that, and the resurrected unit finds no .env (it moved
136 # with the data), falls back to the built-in defaults and creates a FRESH
137 # EMPTY database in the checkout. Masking makes any such call fail loudly.
138 # Reversible: systemctl unmask klonkt.
139 run "systemctl mask $OLD_UNIT"
140 say "disabled and masked $OLD_UNIT (unmask to roll back)"
141fi
142run "systemctl enable --now 'klonkt@$SLUG'"
143
144step "Rewriting klonkt-update for the new layout"
145# The installer generated an updater that restarts klonkt.service — which we
146# just retired. Left alone it would keep updating the code while never
147# restarting the real process: half old, half new, and a 500 with no obvious
148# cause. Rewrite it so it restarts every klonkt@<slug> instead.
149if [ -f "$KLONKT_DIR/scripts/klonkt-refresh-updater.sh" ]; then
150 run "KLONKT_DIR='$KLONKT_DIR' KLONKT_USER='$KLONKT_USER' KLONKT_DATA_ROOT='$DATA_ROOT' bash '$KLONKT_DIR/scripts/klonkt-refresh-updater.sh'"
151else
152 say "WARNING: scripts/klonkt-refresh-updater.sh missing in this checkout."
153 say " Update the code and run it once by hand, or every klonkt-update"
154 say " from now on will update code WITHOUT restarting the process."
155fi
156
157step "Verifying"
158if [ "$DRY" = 1 ]; then
159 say "dry run: skipping verification"
160 exit 0
161fi
162sleep 3
163systemctl is-active --quiet "klonkt@$SLUG" || {
164 echo
165 journalctl -u "klonkt@$SLUG" -n 30 --no-pager || true
166 die "klonkt@$SLUG did not start. Roll back with: systemctl enable --now $OLD_UNIT"
167}
168say "klonkt@$SLUG is running"
169
170PORT="$(grep -m1 '^PORT=' "$ENV_NEW" | cut -d= -f2- | tr -d '\r')"
171if [ -n "$PORT" ]; then
172 if curl -fsS --max-time 8 -o /dev/null "http://127.0.0.1:${PORT}/"; then
173 say "responding on 127.0.0.1:${PORT}"
174 else
175 say "WARNING: no answer on 127.0.0.1:${PORT} yet; check: journalctl -u klonkt@$SLUG -f"
176 fi
177fi
178
179if [ -e "$KLONKT_DIR/storage" ]; then
180 say "WARNING: $KLONKT_DIR/storage came back. That means this build still writes"
181 say " next to its code. Report it; do not delete the directory."
182else
183 say "the checkout no longer holds user data"
184fi
185
186cat <<EOF
187
188Done. This instance now looks like:
189
190 code $KLONKT_DIR shared, replaceable, no user data
191 data $DATA_DIR database, uploads and .env
192 unit klonkt@$SLUG
193
194Back up $DATA_DIR and you have the whole instance.
195Add another instance with: klonkt-add-instance.sh <slug> <domain> <port>
196EOF
Note: See TracBrowser for help on using the repository browser.