source: Klonkt/scripts/klonkt-refresh-updater.sh@ 5861849

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

De updater diept de checkout eenmalig uit, zodat terugrollen kan

Gevonden bij het doorlezen van /usr/local/bin/klonkt-update op een productiehost
met zeven instances. Terugrollen was daar niet moeilijk maar onmogelijk:

git fetch --depth 1 origin main
git checkout -qf -B main FETCH_HEAD

--depth 1 haalt alleen de nieuwste commit op en checkout -f gooit de vorige boom
weg. Na een update stond de vorige versie NERGENS meer op die machine. Een
slechte release neemt alle instances tegelijk mee, en de enige uitweg was opnieuw
van GitHub halen -- als GitHub op dat moment bereikbaar is en als je weet welke
commit je wilt.

De updater diept nu EENMALIG uit (fetch --unshallow) als de checkout ondiep is, en
haalt daarna gewoon op. Dat koopt de geschiedenis terug; elke volgende fetch houdt
hem. De check gaat via rev-parse --is-shallow-repository, want --unshallow op een
complete repo is een fout, geen no-op.

Daarnaast drukt hij na een geslaagde update de vorige commit af mét het commando
om terug te gaan. Geschiedenis alleen is geen terugrol; om drie uur 's nachts heb
je ook de regel nodig.

Getest op een echte ondiepe kloon (file:// transport, want git negeert --depth bij
een lokaal pad): 1 commit -> deepen -> 1045 commits -> checkout van HEAD~3 werkt.
Tweede run slaat het deepenen over. Het gegenereerde script is met bash -n
gecontroleerd.

install.sh blijft ondiep klonen: dat houdt een verse installatie snel, en de
eerste klonkt-update diept alsnog uit. Voor de installatie zelf valt er toch
niets terug te rollen.

  • Property mode set to 100755
File size: 4.9 KB
Line 
1#!/usr/bin/env bash
2#
3# (Re)write /usr/local/bin/klonkt-update so it matches how this server runs.
4#
5# Why this exists: the updater is generated once at install time. A server
6# that later migrated to the split layout (klonkt@<slug> units) kept its old
7# updater, which still restarts the retired klonkt.service. Result: the code
8# on disk updates, the restart quietly fails, and the old process keeps
9# serving — half old routes, half new templates, which is how you get a 500
10# on one page and nothing in the logs that says why.
11#
12# Idempotent; safe to run any time:
13#
14# sudo bash /opt/klonkt/scripts/klonkt-refresh-updater.sh
15#
16set -euo pipefail
17
18KLONKT_DIR="${KLONKT_DIR:-/opt/klonkt}"
19KLONKT_USER="${KLONKT_USER:-klonkt}"
20DATA_ROOT="${KLONKT_DATA_ROOT:-/var/lib/klonkt}"
21# Follow whatever branch the checkout is on (stable for most self-hosters).
22BRANCH="${KLONKT_BRANCH:-$(git -C "$KLONKT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo stable)}"
23
24[ "$(id -u)" = 0 ] || { echo "run this as root (sudo)." >&2; exit 1; }
25[ -d "$KLONKT_DIR/.git" ] || { echo "no git checkout at $KLONKT_DIR" >&2; exit 1; }
26
27cat > /usr/local/bin/klonkt-update <<EOF
28#!/usr/bin/env bash
29set -euo pipefail
30D="${KLONKT_DIR}"
31B=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD 2>/dev/null || true)
32# Deepen a shallow checkout ONCE. \`fetch --depth 1\` keeps only the newest commit
33# and \`checkout -f\` throws away the tree that was there, so after an update the
34# previous version existed nowhere on the machine: a bad release could not be
35# undone without the network, and only if you knew which commit to ask for.
36# One deepening buys back the history; every later fetch keeps it.
37if [ "\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse --is-shallow-repository 2>/dev/null)" = "true" ]; then
38 echo "deepening the checkout once so updates can be rolled back..."
39 runuser -u ${KLONKT_USER} -- git -C "\$D" fetch --unshallow origin ${BRANCH} || true
40fi
41runuser -u ${KLONKT_USER} -- git -C "\$D" fetch origin ${BRANCH}
42runuser -u ${KLONKT_USER} -- git -C "\$D" checkout -qf -B ${BRANCH} FETCH_HEAD
43A=\$(runuser -u ${KLONKT_USER} -- git -C "\$D" rev-parse HEAD)
44if [ "\$B" = "\$A" ]; then
45 echo "Klonkt is already up to date (\$A) — nothing to do."
46 exit 0
47fi
48if ! runuser -u ${KLONKT_USER} -- git -C "\$D" diff --quiet "\$B" "\$A" -- package-lock.json 2>/dev/null; then
49 runuser -u ${KLONKT_USER} -- env HOME="\$D" bash -c "cd '\$D' && npm ci --omit=dev"
50fi
51# Restart every instance sharing this checkout: one directory with an .env
52# under the data root per instance. No instances there = the pre-split
53# single-service layout, which still runs plain klonkt.service.
54N=0
55for d in ${DATA_ROOT}/*/; do
56 [ -f "\$d/.env" ] || continue
57 s=\$(basename "\$d")
58 systemctl restart "klonkt@\$s" && N=\$((N+1))
59done
60if [ "\$N" = 0 ]; then
61 systemctl restart klonkt
62 echo "Klonkt updated (\$A) + restarted."
63else
64 echo "Klonkt updated (\$A) + restarted \$N instance(s)."
65fi
66# History alone is not a rollback; at three in the morning you also need the
67# command. Print it while the previous commit is still known.
68if [ -n "\$B" ]; then
69 echo
70 echo "Previous version: \$B"
71 echo "To go back:"
72 echo " runuser -u ${KLONKT_USER} -- git -C \$D checkout -qf -B ${BRANCH} \$B"
73 echo " then restart the instances (systemctl restart 'klonkt@*')"
74fi
75EOF
76chmod +x /usr/local/bin/klonkt-update
77echo "klonkt-update rewritten: branch ${BRANCH}, code ${KLONKT_DIR}, instances under ${DATA_ROOT}"
78
79# On a split install the old single unit must not be startable. `disable` alone
80# does not stop `systemctl restart klonkt` from starting it, and a resurrected
81# klonkt.service has no .env (it moved with the data): it falls back to the
82# defaults and writes a fresh empty database into the checkout.
83SPLIT=0
84for d in "${DATA_ROOT}"/*/; do [ -f "$d/.env" ] && SPLIT=1 && break; done
85if [ "$SPLIT" = 1 ] && systemctl list-unit-files klonkt.service >/dev/null 2>&1; then
86 if ! systemctl is-enabled klonkt.service 2>/dev/null | grep -q masked; then
87 systemctl stop klonkt.service 2>/dev/null || true
88 systemctl disable klonkt.service 2>/dev/null || true
89 systemctl mask klonkt.service
90 echo "retired klonkt.service: stopped, disabled and masked (unmask to roll back)"
91 fi
92fi
93
94# A leftover storage/ in the checkout means something ran without the instance
95# config. Report it; never delete it unattended — only its owner can tell
96# whether it holds anything.
97if [ "$SPLIT" = 1 ] && [ -e "${KLONKT_DIR}/storage" ]; then
98 echo
99 echo "WARNING: ${KLONKT_DIR}/storage exists while instance data lives in ${DATA_ROOT}."
100 echo " Something ran without the instance .env and wrote here. Check with:"
101 echo " sqlite3 ${KLONKT_DIR}/storage/database.sqlite 'select count(*) from posts;'"
102 echo " If it is empty, it is a stray from a resurrected klonkt.service and"
103 echo " can be removed. If it is NOT empty, do not delete it: ask first."
104fi
Note: See TracBrowser for help on using the repository browser.