Build the ERP frontend on the Jenkins controller and deploy it to ANY LogShip instance (demo clones etc.) over ssh. Parameters: TARGET_HOST (instance ip), BRANCH (git branch). Script source of truth: reference/ci/deploy-instance.sh in the repo — paste it here again after changes. Keeps the instance .env + data/, regenerates NUXT_* overrides, pm2 restart from ecosystem, rollback on failed health check. false -1 5 -1 -1 false TARGET_HOST IP or hostname of the LogShip instance (ssh as root from the jenkins user; its key must be in authorized_keys — true for every clone of the golden template) 192.168.12.68 true BRANCH Git branch to build (dev, main, feature/…) dev true 2 https://github.com/younex1/erp-nuxt-frontend.git younex1 */${BRANCH} false built-in false false false false __BUILD_TOKEN__ false #!/bin/bash # ===================================================================== # deploy-instance.sh — build the ERP frontend on the Jenkins controller and deploy it to ANY # LogShip instance (demo clones, blut24-style instances, …) over ssh. # # This is the shell step of the Jenkins job "erp-frontend-deploy-instance" (parameters # TARGET_HOST, BRANCH; job config: reference/ci/jenkins-job-deploy-instance.xml). The job's # Git SCM checks out BRANCH into the workspace; this script then: # 1. builds with the Node version the deploy pins (nvm, jenkins user), heap 10 GB # 2. compiles sqlite3 from source for the controller's glibc (= Debian 12 like the targets) and # copies the binary + the OCR/PDF runtime assets into .output/server (same as deploy-production.sh) # 3. rsyncs the build to TARGET_HOST:/opt/erp-nuxt-frontend-incoming/ # 4. runs the target-side swap: keeps the instance's .env, data/ and SQLite DBs, moves the old tree to # /opt/erp-nuxt-frontend-rollback, regenerates the NUXT_* overrides (05-apply-env-overrides.sh — # runtimeConfig is baked at build time, see .claude/rules/ops/runtimeconfig-env-reload.md), restarts # pm2 from the ecosystem file, load-tests the native modules and checks /signin. # Unlike production, the build does NOT run on the target (16 GB demo VMs cannot hold iDempiere + a # 12 GB Nuxt build), so targets need no Node toolchain for deploys — only pm2/Node for running. # # Manual use (from the controller as user jenkins, inside a checkout): # TARGET_HOST=192.168.12.68 bash reference/ci/deploy-instance.sh # ===================================================================== set -euo pipefail TARGET_HOST="${TARGET_HOST:?TARGET_HOST (ip or hostname of the instance) required}" TARGET_USER="${TARGET_USER:-root}" NODE_VERSION="${NODE_VERSION:-v22.22.3}" BUILD_HEAP_MB="${BUILD_HEAP_MB:-10240}" LIVE_DIR="/opt/erp-nuxt-frontend" INCOMING_DIR="/opt/erp-nuxt-frontend-incoming" ROLLBACK_DIR="/opt/erp-nuxt-frontend-rollback" SSH="ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new $TARGET_USER@$TARGET_HOST" export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" # shellcheck disable=SC1090 [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm install "$NODE_VERSION" >/dev/null nvm use "$NODE_VERSION" >/dev/null echo "== node $(node -v) / npm $(npm -v) on $(hostname), target $TARGET_HOST, branch ${GIT_BRANCH:-?} ${GIT_COMMIT:-}" $SSH "hostname && test -d $LIVE_DIR && test -f $LIVE_DIR/.env" || { echo "FATAL: $TARGET_HOST has no $LIVE_DIR/.env (not a LogShip instance?)"; exit 1; } # --- 1. build ------------------------------------------------------------------------------ git_hash="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" build_time="$(date '+%d.%m.%Y %H:%M')" echo "== npm ci" npm ci --no-audit --no-fund 2>&1 | tail -3 echo "== build (heap ${BUILD_HEAP_MB} MB)" export NODE_OPTIONS="--max-old-space-size=${BUILD_HEAP_MB}" # GITHASH/BUILDTIME are read by nuxt.config.ts at build time (public.githash / buildtime) GITHASH="$git_hash" BUILDTIME="$build_time" npm run build 2>&1 | tail -15 unset NODE_OPTIONS [ -f .output/server/index.mjs ] || { echo "FATAL: build produced no .output/server/index.mjs"; exit 1; } # service-worker cache version if [ -f .output/public/sw-notifications.js ]; then sed -i "s/const CACHE_NAME = '[^']*'/const CACHE_NAME = 'logship-erp-$git_hash'/" .output/public/sw-notifications.js fi # --- 2. native modules + runtime assets (same rules as deploy-production.sh) -------------------- echo "== native modules" npm rebuild better-sqlite3 >/dev/null 2>&1 || true npm rebuild sqlite3 --build-from-source 2>&1 | tail -2 SQLITE3_BIN="node_modules/sqlite3/build/Release/node_sqlite3.node" [ -f "$SQLITE3_BIN" ] || { echo "FATAL: sqlite3 source build produced no binary"; exit 1; } mkdir -p .output/server/node_modules/sqlite3/build/Release && cp -f "$SQLITE3_BIN" .output/server/node_modules/sqlite3/build/Release/ [ -d node_modules/tesseract.js-core ] && { mkdir -p .output/server/node_modules/tesseract.js-core; cp -rf node_modules/tesseract.js-core/. .output/server/node_modules/tesseract.js-core/; } [ -d node_modules/pdfjs-dist/legacy/build ] && { mkdir -p .output/server/node_modules/pdfjs-dist/legacy/build; cp -f node_modules/pdfjs-dist/legacy/build/pdf*.mjs .output/server/node_modules/pdfjs-dist/legacy/build/ 2>/dev/null || true; } ( cd .output/server && node -e 'require("./node_modules/sqlite3"); require("./node_modules/better-sqlite3"); console.log(" verified: sqlite3 + better-sqlite3 load OK (controller)")' ) # --- 3. ship ------------------------------------------------------------------------------------- echo "== rsync to $TARGET_HOST:$INCOMING_DIR" $SSH "mkdir -p $INCOMING_DIR" # anchored excludes (leading /): the top-level node_modules stays home, .output/server/node_modules must travel rsync -a --delete --exclude /node_modules --exclude /.git --exclude /.nuxt --exclude /.env --exclude '/data/' --exclude /android \ --exclude '/reference/' -e "ssh -o BatchMode=yes" ./ "$TARGET_USER@$TARGET_HOST:$INCOMING_DIR/" # GITHASH/BUILDTIME into the instance .env happen on the target (its .env is kept) # --- 4. swap on the target ---------------------------------------------------------------------- echo "== deploy on target" $SSH "GIT_HASH='$git_hash' BUILD_TIME='$build_time' NODE_VERSION='$NODE_VERSION' bash -s" <<'TARGET' set -euo pipefail; export LANG=C.UTF-8 LIVE=/opt/erp-nuxt-frontend; IN=/opt/erp-nuxt-frontend-incoming; RB=/opt/erp-nuxt-frontend-rollback export NVM_DIR=/root/.nvm; . "$NVM_DIR/nvm.sh" >/dev/null NODE_BIN="$NVM_DIR/versions/node/$NODE_VERSION/bin/node"; [ -x "$NODE_BIN" ] || { nvm install "$NODE_VERSION" >/dev/null; } export PATH="$NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH" [ -f "$IN/.output/server/index.mjs" ] || { echo "FATAL: incoming build missing"; exit 1; } # native modules must load on THIS host before anything is touched ( cd "$IN/.output/server" && "$NODE_BIN" -e 'require("./node_modules/sqlite3"); require("./node_modules/better-sqlite3"); console.log(" verified: native modules load on target")' ) # keep instance state: .env + data/ (SQLite DBs, uploads, signing packages, …) cp -p "$LIVE/.env" "$IN/.env" mkdir -p "$IN/data"; [ -d "$LIVE/data" ] && cp -a "$LIVE/data/." "$IN/data/" sed -i -E "s|^GITHASH=.*|GITHASH=$GIT_HASH|; s|^BUILDTIME=.*|BUILDTIME=$BUILD_TIME|" "$IN/.env" # runtimeConfig overrides for THIS .env (values are baked into the bundle with the controller's env) if [ -x /root/template-cleanup/05-apply-env-overrides.sh ]; then /root/template-cleanup/05-apply-env-overrides.sh "$IN/.env" "$IN/nuxt.config.ts" | tail -1 else echo " (05-apply-env-overrides.sh not on this host — NUXT_* overrides unchanged)" fi # swap pm2 delete erpfrontend >/dev/null 2>&1 || true rm -rf "$RB"; mv "$LIVE" "$RB"; mv "$IN" "$LIVE" # ecosystem interpreter path follows the pinned Node sed -i -E "s|/root/.nvm/versions/node/v[0-9.]+/bin/node|$NODE_BIN|" "$LIVE/ecosystem.config.cjs" cd "$LIVE" && pm2 start ecosystem.config.cjs --only erpfrontend --update-env --interpreter "$NODE_BIN" >/dev/null && pm2 save --force >/dev/null # (|| true: a refused connection makes curl exit 7, which would abort the script under set -e) for i in $(seq 1 40); do code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3001/signin || true); [ "${code:-000}" = 200 ] && break; sleep 3; done echo " /signin http ${code:-000} (build $GIT_HASH, $BUILD_TIME)" [ "${code:-000}" = 200 ] || { echo "FATAL: frontend not answering — rolling back"; pm2 delete erpfrontend >/dev/null 2>&1 || true; rm -rf "$LIVE"; mv "$RB" "$LIVE"; cd "$LIVE" && pm2 start ecosystem.config.cjs --only erpfrontend --update-env >/dev/null; pm2 save --force >/dev/null; exit 1; } echo "DEPLOY_OK" TARGET echo "== done: $TARGET_HOST runs $git_hash ($build_time)"