#!/usr/bin/env bash
set -Eeuo pipefail
INSTALLER_VERSION="3.0.0"
APP_VERSION="2026.08.30.1"
NODE_BIN="${NODE_BIN:-/opt/cpanel/ea-nodejs22/bin/node}"
NPM_BIN="${NPM_BIN:-/opt/cpanel/ea-nodejs22/bin/npm}"
ROOT="$(pwd)"; LOG="$ROOT/lifebook-install.log"; BACKUP_ROOT="$ROOT/lifebook-backups"
mkdir -p "$BACKUP_ROOT"; exec > >(tee -a "$LOG") 2>&1
fail(){ echo; echo "INSTALL FAILED: $*"; echo "See: $LOG"; exit 1; }; trap 'fail "Command failed at line $LINENO."' ERR
line(){ printf '%s\n' '------------------------------------------------------------'; }
read_default(){ local __var=$1 __label=$2 __default=$3 __value; read -r -p "$__label [$__default] (Enter to accept): " __value; printf -v "$__var" '%s' "${__value:-$__default}"; }
read_required(){ local __var=$1 __label=$2 __value=""; while [[ -z "$__value" ]]; do read -r -p "$__label: " __value; done; printf -v "$__var" '%s' "$__value"; }
read_secret(){ local __var=$1 __label=$2 __value=""; while [[ -z "$__value" ]]; do read -r -s -p "$__label: " __value; echo; done; printf -v "$__var" '%s' "$__value"; }
secret32(){ if command -v openssl >/dev/null 2>&1; then openssl rand -hex 32; else "$NODE_BIN" -e 'console.log(require("crypto").randomBytes(32).toString("hex"))'; fi; }
urlencode(){ "$NODE_BIN" -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$1"; }
clear 2>/dev/null || true
printf '\nLifeBook Family Production Installer %s\n' "$INSTALLER_VERSION"; printf 'Application build: %s\n' "$APP_VERSION"; printf 'Install folder: %s\n' "$ROOT"; line
[[ -f "$ROOT/payload.zip" ]] || fail "payload.zip is missing."; command -v unzip >/dev/null 2>&1 || fail "unzip is not available."; [[ -x "$NODE_BIN" ]] || fail "Node.js 22 was not found at $NODE_BIN."; [[ -x "$NPM_BIN" ]] || fail "npm was not found at $NPM_BIN."
echo "✓ Node: $($NODE_BIN -v)"; echo "✓ npm:  $($NPM_BIN -v)"; line
read_default APP_URL "Website URL" "https://lifebook.family"
read_default DB_HOST "MySQL host" "localhost"; read_default DB_PORT "MySQL port" "3306"; read_required DB_NAME "MySQL database name"; read_required DB_USER "MySQL database user"; read_secret DB_PASS "MySQL database password"
read_default ADMIN_EMAIL "Administrator email" "admin@lifebook.family"; read_default ADMIN_NAME "Administrator name" "Platform Administrator"
while true; do read_secret ADMIN_PASS "Administrator password (minimum 8 characters)"; [[ ${#ADMIN_PASS} -ge 8 ]] && break; echo "Password must be at least 8 characters."; done
line; printf 'Ready to install:\n  URL: %s\n  Database: %s@%s/%s\n  Admin: %s\n' "$APP_URL" "$DB_USER" "$DB_HOST" "$DB_NAME" "$ADMIN_EMAIL"; echo "Email, Cloudflare R2 and Turnstile will be configured later in Admin > Settings."; read -r -p "Continue? [Y/n]: " CONFIRM; [[ "${CONFIRM,,}" == "n" || "${CONFIRM,,}" == "no" ]] && exit 0
STAMP="$(date '+%Y%m%d-%H%M%S')"; BACKUP="$BACKUP_ROOT/preinstall-$STAMP"; mkdir -p "$BACKUP"
echo "1/7 Backing up existing website files..."; shopt -s dotglob nullglob; for item in "$ROOT"/*; do base="$(basename "$item")"; case "$base" in payload.zip|install.sh|update.sh|rollback.sh|FINAL-INSTALLATION-GUIDE.md|README-FIRST.txt|lifebook-install.log|lifebook-backups|.well-known) continue;; esac; cp -a "$item" "$BACKUP/" 2>/dev/null || true; done; shopt -u dotglob nullglob
echo "2/7 Installing application files..."; TMP="$(mktemp -d "$ROOT/.ys-install.XXXXXX")"; unzip -q "$ROOT/payload.zip" -d "$TMP"; [[ -d "$TMP/payload" ]] || fail "payload directory not found."; shopt -s dotglob nullglob; for item in "$TMP/payload"/*; do base="$(basename "$item")"; case "$base" in .htaccess|.user.ini|.well-known) continue;; esac; rm -rf "$ROOT/$base"; cp -a "$item" "$ROOT/$base"; done; shopt -u dotglob nullglob; rm -rf "$TMP"
DB_USER_ENC="$(urlencode "$DB_USER")"; DB_PASS_ENC="$(urlencode "$DB_PASS")"; DATABASE_URL="mysql://${DB_USER_ENC}:${DB_PASS_ENC}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
cat > "$ROOT/.env" <<EOF
NODE_ENV=production
APP_URL=$APP_URL
APP_HOST=0.0.0.0
ALLOWED_ORIGINS=$APP_URL
APP_VERSION=$APP_VERSION
DATABASE_URL=$DATABASE_URL
AUTH_SECRET=$(secret32)
OTP_SECRET=$(secret32)
QR_ACTIVATION_SECRET=$(secret32)
IP_HASH_SECRET=$(secret32)
SESSION_DAYS=30
OTP_MINUTES=10
CLAIM_SESSION_MINUTES=45
CLAIM_RESERVATION_MINUTES=30
ALLOW_CONSOLE_OTP=false
SMTP_HOST=
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=
NEXT_PUBLIC_TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
TURNSTILE_REQUIRED=false
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_PRIVATE_BUCKET=
R2_PUBLIC_BUCKET=
R2_PUBLIC_BASE_URL=
ADMIN_EMAIL=$ADMIN_EMAIL
ADMIN_NAME=$ADMIN_NAME
ADMIN_PASSWORD=$ADMIN_PASS
EOF
chmod 600 "$ROOT/.env"; mkdir -p "$ROOT/tmp" "$ROOT/logs"
echo "3/7 Installing Node dependencies..."; if [[ -f "$ROOT/package-lock.json" ]]; then "$NPM_BIN" ci --no-audit --no-fund; else "$NPM_BIN" install --no-audit --no-fund; fi
echo "4/7 Validating and building..."; "$NODE_BIN" scripts/check-env.mjs; "$NPM_BIN" run build
echo "5/7 Creating/updating MySQL tables..."; "$NODE_BIN" scripts/migrate.mjs
echo "6/7 Creating/updating administrator..."; "$NODE_BIN" scripts/seed-admin.mjs
echo "7/7 Preparing Passenger restart..."; touch "$ROOT/tmp/restart.txt"
cat > "$ROOT/INSTALL-COMPLETE.txt" <<EOF
LifeBook Family $APP_VERSION
Installed: $(date -u '+%Y-%m-%d %H:%M:%S UTC')
URL: $APP_URL
Administrator: $ADMIN_EMAIL
Admin login: $APP_URL/admin-login
Application folder: $ROOT
Backup: $BACKUP
EOF
line; echo "INSTALLATION COMPLETE"; echo "Website: $APP_URL"; echo "Admin login: $APP_URL/admin-login"; echo "After login: Admin > Settings for Email, R2 and Turnstile"; echo "QR inventory: $APP_URL/admin/qr"; echo "Health: $APP_URL/api/health"; echo; echo "If not already registered, add this folder once in cPanel Application Manager:"; echo "  Domain: lifebook.family"; echo "  Application path: ${ROOT#$HOME/}"; echo "  Base URL: /"; echo "  Startup file: app.js"; echo "  Node.js: 22 / ea-nodejs22"
