#!/bin/bash set -euo pipefail # ----------------------------- # CONFIG # ----------------------------- NODE_VERSION="v22.14.0" NVM_DIR="/root/.nvm" APP_NAME="erpfrontend" # PM2 process name used in ecosystem.cjs MEDIASERVER_NAME="mediaserver" # PM2 process name for media server SRC_DIR="/root/test/workspace/erp-frontend-pipeline-production" STAGE_DIR="/opt/erp-nuxt-frontend-temp" LIVE_DIR="/opt/erp-nuxt-frontend" ROLLBACK_DIR="/opt/erp-nuxt-frontend-rollback" MEDIASERVER_DIR="/opt/mediaserver" DB_FILE="push-subscriptions.db" # Every SQLite DB under data/ that must survive a deploy. data/*.db is gitignored, # so it is NOT in the build artifact — anything missing here is recreated empty # on the next deploy (this is what silently wiped chat.db before). DB_FILES=("push-subscriptions.db" "chat.db") DB_LIVE_DIR="$LIVE_DIR/data" DB_BACKUP_DIR="/opt/erp-nuxt-frontend-db-backups" ENV_SRC="$SRC_DIR/.env-prod" ENV_DST="$STAGE_DIR/.env" # ----------------------------- # NVM + NODE 22 # ----------------------------- export NVM_DIR="$NVM_DIR" # shellcheck disable=SC1090 [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm install "$NODE_VERSION" nvm alias default "$NODE_VERSION" nvm use "$NODE_VERSION" # Ensure pm2 is installed for THIS Node version npm install -g pm2 NODE_BIN="$NVM_DIR/versions/node/$NODE_VERSION/bin/node" NPM_BIN="$NVM_DIR/versions/node/$NODE_VERSION/bin/npm" PM2_BIN="$NVM_DIR/versions/node/$NODE_VERSION/bin/pm2" echo "Using Node: $("$NODE_BIN" -v)" echo "ABI: $("$NODE_BIN" -p 'process.versions.modules')" echo "PM2: $("$PM2_BIN" -v)" # ----------------------------- # STAGE WORKSPACE # ----------------------------- mkdir -p "$STAGE_DIR" rm -rf "$STAGE_DIR"/* cp -r "$SRC_DIR"/* "$STAGE_DIR"/ cp "$ENV_SRC" "$ENV_DST" # Inject build metadata cd "$SRC_DIR" git_hash=$(git rev-parse --short HEAD || echo "unknown") current_datetime=$(date '+%d.%m.%Y %H:%M') cd "$STAGE_DIR" sed -i "s//$git_hash/g" "$ENV_DST" sed -i "s//$current_datetime/g" "$ENV_DST" export NODE_OPTIONS="--max-old-space-size=12288" # Clean install + build under Node 22. # npm ci installs EXACTLY the committed package-lock.json — no re-resolution # against the live registry (a lockfile-less `npm install` here let a fresh # @vitejs/devtools publish crash npm's resolver on 2026-07-25). rm -rf node_modules .nuxt .output $NPM_BIN ci $NPM_BIN run build # ----------------------------- # VERSION SERVICE WORKER CACHE # ----------------------------- echo "Versioning service worker cache with git hash: $git_hash" if [ -f "$STAGE_DIR/.output/public/sw-notifications.js" ]; then sed -i "s/const CACHE_NAME = 'logship-erp-v1'/const CACHE_NAME = 'logship-erp-$git_hash'/" \ "$STAGE_DIR/.output/public/sw-notifications.js" echo "Service worker cache versioned: logship-erp-$git_hash" else echo "Warning: Service worker file not found at expected location" fi # ----------------------------- # BACKUP SQLITE DBs # ----------------------------- echo "Backing up SQLite databases..." mkdir -p "$DB_BACKUP_DIR" backup_ts="$(date +%Y%m%d-%H%M%S)" for db in "${DB_FILES[@]}"; do if [ -f "$LIVE_DIR/data/$db" ]; then base="${db%.db}" cp "$LIVE_DIR/data/$db" "$DB_BACKUP_DIR/${base}-${backup_ts}.db" echo "Database backed up: $DB_BACKUP_DIR/${base}-${backup_ts}.db" # keep last 10 backups per database (cd "$DB_BACKUP_DIR" && ls -t "${base}-"*.db 2>/dev/null | tail -n +11 | xargs -r rm) else echo "No existing $db found - fresh deployment for it" fi done # ----------------------------- # DEPLOY (with rollback) # ----------------------------- rm -rf "$ROLLBACK_DIR" if [ -d "$LIVE_DIR" ]; then cp -rf "$LIVE_DIR" "$ROLLBACK_DIR" fi rm -rf "$LIVE_DIR" mkdir -p "$LIVE_DIR" cp -r "$STAGE_DIR"/. "$LIVE_DIR"/ # ----------------------------- # RESTORE SQLITE DBs # ----------------------------- echo "Restoring SQLite databases..." mkdir -p "$DB_LIVE_DIR" for db in "${DB_FILES[@]}"; do if [ -f "$ROLLBACK_DIR/data/$db" ]; then cp "$ROLLBACK_DIR/data/$db" "$DB_LIVE_DIR"/ # Carry over WAL sidecars too: chat.db runs in WAL mode, so the most recent # messages may still live in -wal and not yet be checkpointed into the .db. [ -f "$ROLLBACK_DIR/data/$db-wal" ] && cp "$ROLLBACK_DIR/data/$db-wal" "$DB_LIVE_DIR"/ || true [ -f "$ROLLBACK_DIR/data/$db-shm" ] && cp "$ROLLBACK_DIR/data/$db-shm" "$DB_LIVE_DIR"/ || true echo "Database $db restored from rollback" else echo "No previous $db found - will be created on first run" fi done chown -R root:root "$DB_LIVE_DIR" chmod 755 "$DB_LIVE_DIR" for db in "${DB_FILES[@]}"; do for f in "$db" "$db-wal" "$db-shm"; do [ -f "$DB_LIVE_DIR/$f" ] && chmod 644 "$DB_LIVE_DIR/$f" || true done done # ----------------------------- # REBUILD NATIVE MODULES FOR NODE 22 # ----------------------------- cd "$LIVE_DIR/.output/server" echo "Rebuilding better-sqlite3 for Node $NODE_VERSION..." $NPM_BIN rebuild better-sqlite3 # ----------------------------- # START WITH PM2 (force Node 20) # ----------------------------- cd "$LIVE_DIR" # Stop old ERP frontend process if exists $PM2_BIN delete "$APP_NAME" || true # Start ERP frontend using Node 22 interpreter $PM2_BIN start ecosystem.config.cjs \ --only "$APP_NAME" \ --update-env \ --interpreter "$NODE_BIN" # Start or restart mediaserver (ensure it's running with correct config) cd "$MEDIASERVER_DIR" if $PM2_BIN describe "$MEDIASERVER_NAME" > /dev/null 2>&1; then echo "Restarting existing mediaserver..." $PM2_BIN restart "$MEDIASERVER_NAME" --update-env --interpreter "$NODE_BIN" else echo "Starting mediaserver for the first time..." $PM2_BIN start ecosystem.config.js --update-env --interpreter "$NODE_BIN" fi # Save PM2 state (both apps) $PM2_BIN save --force # Ensure PM2 resurrects at boot WITH the right PATH/NVM $PM2_BIN startup systemd -u root --hp /root # Create a systemd drop-in so PM2 finds Node on boot AND starts only after the # iDempiere REST API (8443) answers — without the wait, every reboot produces a # ~2 minute 502 storm in the frontend until the API is up. # Keep this block in sync with deploy/pm2-root.service.d/override.conf. mkdir -p /etc/systemd/system/pm2-root.service.d cat >/etc/systemd/system/pm2-root.service.d/override.conf <