fix: make the boot update actually fail loudly and survive long builds
Some checks are pending
Playwright Tests / test (push) Waiting to run
Some checks are pending
Playwright Tests / test (push) Waiting to run
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>
This commit is contained in:
parent
224fdba5c6
commit
9d4e40e4e3
22
CHANGELOG.md
22
CHANGELOG.md
@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
All notable changes to this project are documented in this file.
|
All notable changes to this project are documented in this file.
|
||||||
|
|
||||||
|
## [1.115.1] - 2026-09-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `scripts/boot-update.sh` (the "update on reboot" unit): every command was piped into `tee`,
|
||||||
|
so `set -e` only ever saw tee's exit status. A failed `npm install` or `npm run build` did
|
||||||
|
not stop the script and did not fail the systemd unit — it logged "Boot update finished"
|
||||||
|
and left a half-built tree. Now uses `set -euo pipefail`, logs through a single `exec`
|
||||||
|
redirect, and traps ERR.
|
||||||
|
- `scripts/boot-update.sh`: switched from `npm install` to `npm ci`. Same fix update-app.sh
|
||||||
|
got in 1.114.1 — `npm install` rewrites package-lock.json, leaving the tree dirty so the
|
||||||
|
*next* `git pull` aborts.
|
||||||
|
- `scripts/boot-update.sh`: body wrapped in a function so bash parses it in one go. The
|
||||||
|
script lives inside the checkout it pulls, so `git pull` rewriting it mid-run made bash
|
||||||
|
resume at a stale file offset.
|
||||||
|
- `scripts/boot-update.sh`: set `GIT_TERMINAL_PROMPT=0` so a git credential prompt fails
|
||||||
|
fast instead of hanging the unit at boot, where there is no tty.
|
||||||
|
- `scripts/my-weekly-todo-update.service`: raised `TimeoutStartSec` from 300 to 1800.
|
||||||
|
`npm ci` + `prisma generate` + `next build` takes far longer than 5 minutes in the
|
||||||
|
container; systemd SIGTERMed the oneshot unit mid-install, which left a `node_modules`
|
||||||
|
tree npm could no longer remove (`ENOTEMPTY: rmdir node_modules/date-fns/isEqual`).
|
||||||
|
- `package-lock.json`: version resynced with `package.json` (missed in the 1.115.0 commit).
|
||||||
|
|
||||||
## [1.115.0] - 2026-09-03
|
## [1.115.0] - 2026-09-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.114.2",
|
"version": "1.115.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.114.2",
|
"version": "1.115.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^2.11.1",
|
"@auth/prisma-adapter": "^2.11.1",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.115.0",
|
"version": "1.115.1",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,66 +1,92 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Boot Update Script: Automatically pull latest main, build, and prepare for PM2
|
#
|
||||||
# This script runs on LXC container boot BEFORE PM2 starts.
|
# Boot Update Script: pull latest main, install, build, and prepare for PM2.
|
||||||
# It ensures the app is always up-to-date with the main branch.
|
# 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.
|
||||||
|
|
||||||
set -e
|
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
|
||||||
|
|
||||||
APP_DIR="/root/My-Weekly-ToDo-List"
|
local APP_DIR="/root/My-Weekly-ToDo-List"
|
||||||
LOG_FILE="/var/log/app-boot-update.log"
|
local LOG_FILE="/var/log/app-boot-update.log"
|
||||||
|
|
||||||
log() {
|
# Send everything to the log once, instead of piping each command through tee.
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
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 ==="
|
log "=== Boot update started ==="
|
||||||
|
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
|
|
||||||
# Check and ensure we are on main branch
|
|
||||||
log "Ensuring main branch..."
|
log "Ensuring main branch..."
|
||||||
git checkout main 2>&1 | tee -a "$LOG_FILE"
|
git checkout main
|
||||||
|
|
||||||
# Check if there are remote changes
|
|
||||||
log "Fetching from origin..."
|
log "Fetching from origin..."
|
||||||
git fetch origin main 2>&1 | tee -a "$LOG_FILE"
|
git fetch origin main
|
||||||
|
|
||||||
|
local LOCAL REMOTE NEXT_DIR
|
||||||
LOCAL=$(git rev-parse HEAD)
|
LOCAL=$(git rev-parse HEAD)
|
||||||
REMOTE=$(git rev-parse origin/main)
|
REMOTE=$(git rev-parse origin/main)
|
||||||
NEXT_DIR="$APP_DIR/.next"
|
NEXT_DIR="$APP_DIR/.next"
|
||||||
|
|
||||||
if [ "$LOCAL" = "$REMOTE" ] && [ -d "$NEXT_DIR" ]; then
|
if [ "$LOCAL" = "$REMOTE" ] && [ -d "$NEXT_DIR" ]; then
|
||||||
log "Already up-to-date and .next directory exists. No rebuild needed."
|
log "Already up-to-date and .next exists. No rebuild needed."
|
||||||
log "=== Boot update finished (no changes) ==="
|
log "=== Boot update finished (no changes) ==="
|
||||||
exit 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$LOCAL" != "$REMOTE" ]; then
|
if [ "$LOCAL" != "$REMOTE" ]; then
|
||||||
log "Changes detected. Updating from $LOCAL to "$REMOTE""
|
log "Changes detected. Updating from $LOCAL to $REMOTE"
|
||||||
log "Pulling latest changes..."
|
git pull origin main
|
||||||
git pull origin main 2>&1 | tee -a "$LOG_FILE"
|
|
||||||
|
|
||||||
|
# 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..."
|
log "Installing dependencies..."
|
||||||
npm install 2>&1 | tee -a "$LOG_FILE"
|
npm ci
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "$NEXT_DIR" ] || [ "$LOCAL" != "$REMOTE" ]; then
|
if [ ! -d "$NEXT_DIR" ] || [ "$LOCAL" != "$REMOTE" ]; then
|
||||||
log "Updating database schema (Safe Migrate)..."
|
log "Updating database schema (safe migrate)..."
|
||||||
npx prisma migrate deploy 2>&1 | tee -a "$LOG_FILE" || {
|
npx prisma migrate deploy || {
|
||||||
log "migrate deploy failed, falling back to db push..."
|
log "migrate deploy failed, falling back to db push..."
|
||||||
npx prisma db push --accept-data-loss 2>&1 | tee -a "$LOG_FILE"
|
npx prisma db push --accept-data-loss
|
||||||
}
|
}
|
||||||
|
|
||||||
log "Regenerating Prisma client..."
|
log "Regenerating Prisma client..."
|
||||||
npx prisma generate 2>&1 | tee -a "$LOG_FILE"
|
npx prisma generate
|
||||||
|
|
||||||
log "Building application..."
|
log "Building application..."
|
||||||
npm run build 2>&1 | tee -a "$LOG_FILE"
|
npm run build
|
||||||
|
|
||||||
log "Preparing standalone directory (copying static assets and env)..."
|
# next.config.js sets output: 'standalone', and Next.js does NOT copy public/ or
|
||||||
cp -r public .next/standalone/ 2>&1 | tee -a "$LOG_FILE"
|
# .next/static/ into .next/standalone — that is a documented manual step. Skipping
|
||||||
mkdir -p .next/standalone/.next/static 2>&1 | tee -a "$LOG_FILE"
|
# it ships new server code whose HTML references new chunk hashes while the old
|
||||||
cp -r .next/static .next/standalone/.next/ 2>&1 | tee -a "$LOG_FILE"
|
# chunks sit in the standalone tree: they 404, React never hydrates, and every page
|
||||||
cp .env .next/standalone/ 2>/dev/null || true 2>&1 | tee -a "$LOG_FILE"
|
# 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
|
fi
|
||||||
|
|
||||||
|
log "Deployed version: $(node -p "require('$APP_DIR/package.json').version")"
|
||||||
log "=== Boot update finished (verified/rebuilt) ==="
|
log "=== Boot update finished (verified/rebuilt) ==="
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
@ -12,7 +12,10 @@ Environment=HOME=/root
|
|||||||
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
StandardOutput=journal+console
|
StandardOutput=journal+console
|
||||||
StandardError=journal+console
|
StandardError=journal+console
|
||||||
TimeoutStartSec=300
|
# npm ci + prisma generate + next build in this container takes well over the old 300s.
|
||||||
|
# systemd SIGTERMs a Type=oneshot unit at the timeout, which used to kill npm mid-install
|
||||||
|
# and leave a node_modules tree that npm could no longer clean up (ENOTEMPTY on rmdir).
|
||||||
|
TimeoutStartSec=1800
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user