My-Weekly-ToDo-List/scripts/boot-update.sh
mARTin-B78 9d4e40e4e3
Some checks are pending
Playwright Tests / test (push) Waiting to run
fix: make the boot update actually fail loudly and survive long builds
The "update on reboot" unit silently did nothing useful. boot-update.sh piped
every command into tee, so `set -e` only saw tee's exit status: a failed
npm install or build left a half-built tree and still logged "Boot update
finished". It also used npm install (rewrites package-lock.json, so the next
git pull aborts), had no GIT_TERMINAL_PROMPT=0, and was not wrapped in a
function even though git pull rewrites it mid-run.

The unit's TimeoutStartSec=300 was the other half: npm ci + prisma generate +
next build takes far longer than 5 minutes in the container, so systemd
SIGTERMed the oneshot mid-install and left a node_modules tree npm could no
longer remove (ENOTEMPTY on rmdir node_modules/date-fns/isEqual).

Also resyncs package-lock.json with package.json, missed in the 1.115.0 commit.

v1.115.1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:27:11 +02:00

93 lines
3.6 KiB
Bash
Executable File

#!/bin/bash
#
# Boot Update Script: pull latest main, install, build, and prepare for PM2.
# Runs on LXC container boot BEFORE PM2 starts (my-weekly-todo-update.service).
#
# The whole body is wrapped in a function so bash parses it in one go: this script lives
# inside the very checkout it updates, and `git pull` rewriting it mid-run would otherwise
# make bash resume at a stale file offset.
main() {
# pipefail matters here: every command used to be piped into `tee`, so the shell saw
# tee's exit status (always 0). A failed npm install or build did not stop the script
# and did not fail the unit — it just logged "finished" and left a half-built tree.
set -euo pipefail
local APP_DIR="/root/My-Weekly-ToDo-List"
local LOG_FILE="/var/log/app-boot-update.log"
# Send everything to the log once, instead of piping each command through tee.
exec > >(tee -a "$LOG_FILE") 2>&1
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
trap 'log "!!! Boot update FAILED at line $LINENO (exit $?) — not restarting the app"' ERR
# Fail fast instead of hanging on a credential prompt when run without a terminal
# (systemd has no tty). The host authenticates via a read-only SSH deploy key.
export GIT_TERMINAL_PROMPT=0
log "=== Boot update started ==="
cd "$APP_DIR"
log "Ensuring main branch..."
git checkout main
log "Fetching from origin..."
git fetch origin main
local LOCAL REMOTE NEXT_DIR
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/main)
NEXT_DIR="$APP_DIR/.next"
if [ "$LOCAL" = "$REMOTE" ] && [ -d "$NEXT_DIR" ]; then
log "Already up-to-date and .next exists. No rebuild needed."
log "=== Boot update finished (no changes) ==="
return 0
fi
if [ "$LOCAL" != "$REMOTE" ]; then
log "Changes detected. Updating from $LOCAL to $REMOTE"
git pull origin main
# npm ci, not npm install: it installs exactly what package-lock.json specifies and
# never rewrites it. npm install rewrites the lockfile, which leaves the working
# tree dirty and makes the *next* git pull abort. npm ci also clears node_modules
# itself; if that ever fails with ENOTEMPTY, delete the directory by hand and rerun.
log "Installing dependencies..."
npm ci
fi
if [ ! -d "$NEXT_DIR" ] || [ "$LOCAL" != "$REMOTE" ]; then
log "Updating database schema (safe migrate)..."
npx prisma migrate deploy || {
log "migrate deploy failed, falling back to db push..."
npx prisma db push --accept-data-loss
}
log "Regenerating Prisma client..."
npx prisma generate
log "Building application..."
npm run build
# next.config.js sets output: 'standalone', and Next.js does NOT copy public/ or
# .next/static/ into .next/standalone — that is a documented manual step. Skipping
# it ships new server code whose HTML references new chunk hashes while the old
# chunks sit in the standalone tree: they 404, React never hydrates, and every page
# hangs on "Loading session..." while the API still answers 200.
log "Preparing standalone directory (static assets and env)..."
cp -r public .next/standalone/
mkdir -p .next/standalone/.next/static
cp -r .next/static .next/standalone/.next/
cp .env .next/standalone/ 2>/dev/null || true
fi
log "Deployed version: $(node -p "require('$APP_DIR/package.json').version")"
log "=== Boot update finished (verified/rebuilt) ==="
}
main "$@"