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

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

Zet de oude klonkt.service opzij; maskeren werkte niet

Correctie op f434294. Daar maskeerde ik de oude unit om te voorkomen dat een
systemctl restart klonkt hem uit de dood liet opstaan. Getest met een
user-unit blijkt dat niet te werken:

Failed to mask unit: File '.../masktest.service' already exists

systemctl mask legt een symlink naar /dev/null op het pad van de unit, maar
install.sh schrijft klonkt.service in /etc/systemd/system, de map met de
hoogste prioriteit. Er is dan geen plek meer om de mask neer te zetten en
systemd weigert. Na de mislukte mask start restart de unit gewoon nog
(exitcode 0), dus de fix deed niets.

Erger: die mask stond zonder vangnet onder set -euo pipefail. In het
migratiescript zou hij de migratie afbreken NADAT de data al verplaatst was.
De fix was dus niet alleen nutteloos maar ook gevaarlijk.

Wat wel werkt, en nu getest is in beide richtingen: het unit-bestand opzij
zetten met een tijdstempel en daemon-reload. systemd kent de unit dan niet
meer, en een restart faalt hard met "Unit klonkt.service not found"
(exitcode 5) in plaats van stilletjes een tweede proces te starten dat een
lege database in de checkout schrijft. Terugzetten plus daemon-reload maakt
hem weer gewoon startbaar, dus de rollback blijft intact.

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

  • eigen stap 'Retiring klonkt.service': stop, disable, bestand naar klonkt.service.retired-<tijdstempel>, daemon-reload
  • stop en disable krijgen een vangnet zodat een al gestopte unit de migratie niet afbreekt
  • waarom maskeren hier niet kan, staat erbij; dat is niet vanzelfsprekend

scripts/klonkt-refresh-updater.sh

  • zelfde aanpak voor al gemigreerde servers, met een expliciete waarschuwing als het verplaatsen niet lukt

deploy/MULTI-INSTANCE.md

  • rollback bijgewerkt naar het terugzetten van het bestand

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

  • Property mode set to 100755
File size: 8.0 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 "Retiring $OLD_UNIT"
131# Stopping and disabling is NOT enough: `systemctl restart klonkt` starts a
132# disabled unit anyway, and that is exactly what an updater generated before
133# the split does. A resurrected klonkt.service no longer finds its .env (that
134# moved with the data), falls back to the built-in defaults, and writes a
135# FRESH EMPTY database into the checkout.
136#
137# Masking does not help either: the unit file lives in /etc/systemd/system,
138# the highest-priority directory, and `systemctl mask` refuses when a real
139# file is already there ("File ... already exists"). Verified, not assumed.
140#
141# So the file is moved aside. systemd then no longer knows the unit at all and
142# any restart fails loudly with "Unit klonkt.service not found". The file is
143# kept next to its old place, timestamped, so a rollback is a move back.
144if [ -f "/etc/systemd/system/$OLD_UNIT" ]; then
145 run "systemctl stop $OLD_UNIT 2>/dev/null || true"
146 run "systemctl disable $OLD_UNIT 2>/dev/null || true"
147 RETIRED="/etc/systemd/system/${OLD_UNIT}.retired-$(date +%Y%m%d%H%M%S)"
148 run "mv '/etc/systemd/system/$OLD_UNIT' '$RETIRED'"
149 run "systemctl daemon-reload"
150 say "stopped, disabled and moved aside → $RETIRED"
151 say "roll back by moving that file back and running: systemctl daemon-reload"
152else
153 say "no $OLD_UNIT unit file to retire"
154fi
155
156step "Switching to klonkt@$SLUG"
157run "systemctl enable --now 'klonkt@$SLUG'"
158
159step "Rewriting klonkt-update for the new layout"
160# The installer generated an updater that restarts klonkt.service — which we
161# just retired. Left alone it would keep updating the code while never
162# restarting the real process: half old, half new, and a 500 with no obvious
163# cause. Rewrite it so it restarts every klonkt@<slug> instead.
164if [ -f "$KLONKT_DIR/scripts/klonkt-refresh-updater.sh" ]; then
165 run "KLONKT_DIR='$KLONKT_DIR' KLONKT_USER='$KLONKT_USER' KLONKT_DATA_ROOT='$DATA_ROOT' bash '$KLONKT_DIR/scripts/klonkt-refresh-updater.sh'"
166else
167 say "WARNING: scripts/klonkt-refresh-updater.sh missing in this checkout."
168 say " Update the code and run it once by hand, or every klonkt-update"
169 say " from now on will update code WITHOUT restarting the process."
170fi
171
172step "Verifying"
173if [ "$DRY" = 1 ]; then
174 say "dry run: skipping verification"
175 exit 0
176fi
177sleep 3
178systemctl is-active --quiet "klonkt@$SLUG" || {
179 echo
180 journalctl -u "klonkt@$SLUG" -n 30 --no-pager || true
181 die "klonkt@$SLUG did not start. Roll back with: systemctl enable --now $OLD_UNIT"
182}
183say "klonkt@$SLUG is running"
184
185PORT="$(grep -m1 '^PORT=' "$ENV_NEW" | cut -d= -f2- | tr -d '\r')"
186if [ -n "$PORT" ]; then
187 if curl -fsS --max-time 8 -o /dev/null "http://127.0.0.1:${PORT}/"; then
188 say "responding on 127.0.0.1:${PORT}"
189 else
190 say "WARNING: no answer on 127.0.0.1:${PORT} yet; check: journalctl -u klonkt@$SLUG -f"
191 fi
192fi
193
194if [ -e "$KLONKT_DIR/storage" ]; then
195 say "WARNING: $KLONKT_DIR/storage came back. That means this build still writes"
196 say " next to its code. Report it; do not delete the directory."
197else
198 say "the checkout no longer holds user data"
199fi
200
201cat <<EOF
202
203Done. This instance now looks like:
204
205 code $KLONKT_DIR shared, replaceable, no user data
206 data $DATA_DIR database, uploads and .env
207 unit klonkt@$SLUG
208
209Back up $DATA_DIR and you have the whole instance.
210Add another instance with: klonkt-add-instance.sh <slug> <domain> <port>
211EOF
Note: See TracBrowser for help on using the repository browser.