source: Klonkt/scripts/klonkt-migrate-data.sh@ 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 100755
File size: 6.9 KB
RevLine 
[2dd1dc4]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 is-enabled --quiet "$OLD_UNIT" 2>/dev/null; then
132 run "systemctl disable --now $OLD_UNIT"
133 say "disabled $OLD_UNIT (file kept, so you can roll back)"
134fi
135run "systemctl enable --now 'klonkt@$SLUG'"
136
[5462bab]137step "Rewriting klonkt-update for the new layout"
138# The installer generated an updater that restarts klonkt.service — which we
139# just retired. Left alone it would keep updating the code while never
140# restarting the real process: half old, half new, and a 500 with no obvious
141# cause. Rewrite it so it restarts every klonkt@<slug> instead.
142if [ -f "$KLONKT_DIR/scripts/klonkt-refresh-updater.sh" ]; then
143 run "KLONKT_DIR='$KLONKT_DIR' KLONKT_USER='$KLONKT_USER' KLONKT_DATA_ROOT='$DATA_ROOT' bash '$KLONKT_DIR/scripts/klonkt-refresh-updater.sh'"
144else
145 say "WARNING: scripts/klonkt-refresh-updater.sh missing in this checkout."
146 say " Update the code and run it once by hand, or every klonkt-update"
147 say " from now on will update code WITHOUT restarting the process."
148fi
149
[2dd1dc4]150step "Verifying"
151if [ "$DRY" = 1 ]; then
152 say "dry run: skipping verification"
153 exit 0
154fi
155sleep 3
156systemctl is-active --quiet "klonkt@$SLUG" || {
157 echo
158 journalctl -u "klonkt@$SLUG" -n 30 --no-pager || true
159 die "klonkt@$SLUG did not start. Roll back with: systemctl enable --now $OLD_UNIT"
160}
161say "klonkt@$SLUG is running"
162
163PORT="$(grep -m1 '^PORT=' "$ENV_NEW" | cut -d= -f2- | tr -d '\r')"
164if [ -n "$PORT" ]; then
165 if curl -fsS --max-time 8 -o /dev/null "http://127.0.0.1:${PORT}/"; then
166 say "responding on 127.0.0.1:${PORT}"
167 else
168 say "WARNING: no answer on 127.0.0.1:${PORT} yet; check: journalctl -u klonkt@$SLUG -f"
169 fi
170fi
171
172if [ -e "$KLONKT_DIR/storage" ]; then
173 say "WARNING: $KLONKT_DIR/storage came back. That means this build still writes"
174 say " next to its code. Report it; do not delete the directory."
175else
176 say "the checkout no longer holds user data"
177fi
178
179cat <<EOF
180
181Done. This instance now looks like:
182
183 code $KLONKT_DIR shared, replaceable, no user data
184 data $DATA_DIR database, uploads and .env
185 unit klonkt@$SLUG
186
187Back up $DATA_DIR and you have the whole instance.
188Add another instance with: klonkt-add-instance.sh <slug> <domain> <port>
189EOF
Note: See TracBrowser for help on using the repository browser.