Database verification now allows missing database files for new installations. The app will create the database automatically via Prisma migrations on first start.
1032 lines
30 KiB
Bash
Executable File
1032 lines
30 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
#21.10.2025 - @michelroegl-brunner
|
|
# Enhanced update script for ProxmoxVE-Local
|
|
# Fetches latest release from GitHub and backs up data directory
|
|
|
|
set -euo pipefail # Exit on error, undefined vars, pipe failures
|
|
|
|
# Add error trap for debugging
|
|
trap 'echo "Error occurred at line $LINENO, command: $BASH_COMMAND"' ERR
|
|
|
|
# Configuration
|
|
REPO_OWNER="community-scripts"
|
|
REPO_NAME="ProxmoxVE-Local"
|
|
GITHUB_API="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}"
|
|
BACKUP_DIR="/tmp/pve-scripts-backup-$(date +%Y%m%d-%H%M%S)"
|
|
DATA_DIR="./data"
|
|
LOG_FILE="/tmp/update.log"
|
|
|
|
# GitHub Personal Access Token for higher rate limits (optional)
|
|
# Set GITHUB_TOKEN environment variable or create .github_token file
|
|
GITHUB_TOKEN=""
|
|
|
|
# Global variable to track if service was running before update
|
|
SERVICE_WAS_RUNNING=false
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Load GitHub token
|
|
load_github_token() {
|
|
# Try environment variable first
|
|
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
|
log "Using GitHub token from environment variable"
|
|
return 0
|
|
fi
|
|
|
|
# Try .env file
|
|
if [ -f ".env" ]; then
|
|
local env_token
|
|
env_token=$(grep "^GITHUB_TOKEN=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d "'" | tr -d '\n\r')
|
|
if [ -n "$env_token" ]; then
|
|
GITHUB_TOKEN="$env_token"
|
|
log "Using GitHub token from .env file"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Try .github_token file
|
|
if [ -f ".github_token" ]; then
|
|
GITHUB_TOKEN=$(cat .github_token | tr -d '\n\r')
|
|
log "Using GitHub token from .github_token file"
|
|
return 0
|
|
fi
|
|
|
|
# Try ~/.github_token file
|
|
if [ -f "$HOME/.github_token" ]; then
|
|
GITHUB_TOKEN=$(cat "$HOME/.github_token" | tr -d '\n\r')
|
|
log "Using GitHub token from ~/.github_token file"
|
|
return 0
|
|
fi
|
|
|
|
log_warning "No GitHub token found. Using unauthenticated requests (lower rate limits)"
|
|
log_warning "To use a token, add GITHUB_TOKEN=your_token to .env file or set GITHUB_TOKEN environment variable"
|
|
return 1
|
|
}
|
|
|
|
# Initialize log file
|
|
init_log() {
|
|
# Clear/create log file
|
|
>"$LOG_FILE"
|
|
log "Starting ProxmoxVE-Local update process..."
|
|
log "Log file: $LOG_FILE"
|
|
}
|
|
|
|
# Logging function
|
|
log() {
|
|
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" >&2
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" >&2
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" >&2
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" >&2
|
|
}
|
|
|
|
# Check if required tools are available
|
|
check_dependencies() {
|
|
log "Checking dependencies..."
|
|
|
|
local missing_deps=()
|
|
|
|
if ! command -v curl &>/dev/null; then
|
|
missing_deps+=("curl")
|
|
fi
|
|
|
|
if ! command -v jq &>/dev/null; then
|
|
missing_deps+=("jq")
|
|
fi
|
|
|
|
if ! command -v npm &>/dev/null; then
|
|
missing_deps+=("npm")
|
|
fi
|
|
|
|
if ! command -v node &>/dev/null; then
|
|
missing_deps+=("node")
|
|
fi
|
|
|
|
if [ ${#missing_deps[@]} -ne 0 ]; then
|
|
log_error "Missing dependencies: ${missing_deps[*]}"
|
|
log_error "Please install the missing dependencies and try again."
|
|
exit 1
|
|
fi
|
|
|
|
log_success "All dependencies are available"
|
|
}
|
|
|
|
# Get latest release info from GitHub API
|
|
get_latest_release() {
|
|
log "Fetching latest release information from GitHub..."
|
|
|
|
local curl_opts="-s --connect-timeout 15 --max-time 60 --retry 2 --retry-delay 3"
|
|
|
|
# Add authentication header if token is available
|
|
if [ -n "$GITHUB_TOKEN" ]; then
|
|
curl_opts="$curl_opts -H \"Authorization: token $GITHUB_TOKEN\""
|
|
log "Using authenticated GitHub API request"
|
|
else
|
|
log "Using unauthenticated GitHub API request (lower rate limits)"
|
|
fi
|
|
|
|
local release_info
|
|
if ! release_info=$(eval "curl $curl_opts \"$GITHUB_API/releases/latest\""); then
|
|
log_error "Failed to fetch release information from GitHub API (timeout or network error)"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if response is valid JSON
|
|
if ! echo "$release_info" | jq empty 2>/dev/null; then
|
|
log_error "Invalid JSON response from GitHub API"
|
|
log "Response: $release_info"
|
|
exit 1
|
|
fi
|
|
|
|
local tag_name
|
|
local download_url
|
|
local published_at
|
|
|
|
tag_name=$(echo "$release_info" | jq -r '.tag_name')
|
|
download_url=$(echo "$release_info" | jq -r '.tarball_url')
|
|
published_at=$(echo "$release_info" | jq -r '.published_at')
|
|
|
|
if [ "$tag_name" = "null" ] || [ "$download_url" = "null" ] || [ -z "$tag_name" ] || [ -z "$download_url" ]; then
|
|
log_error "Failed to parse release information from API response"
|
|
log "Tag name: $tag_name"
|
|
log "Download URL: $download_url"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Latest release: $tag_name (published: $published_at)"
|
|
echo "$tag_name|$download_url"
|
|
}
|
|
|
|
# Backup data directory, .env file, and scripts directories
|
|
backup_data() {
|
|
log "Creating backup directory at $BACKUP_DIR..."
|
|
|
|
if ! mkdir -p "$BACKUP_DIR"; then
|
|
log_error "Failed to create backup directory"
|
|
exit 1
|
|
fi
|
|
|
|
# Backup data directory
|
|
if [ -d "$DATA_DIR" ]; then
|
|
log "Backing up data directory..."
|
|
|
|
if ! cp -r "$DATA_DIR" "$BACKUP_DIR/data"; then
|
|
log_error "Failed to backup data directory"
|
|
exit 1
|
|
else
|
|
log_success "Data directory backed up successfully"
|
|
fi
|
|
else
|
|
log_warning "Data directory not found, skipping backup"
|
|
fi
|
|
|
|
# Backup .env file
|
|
if [ -f ".env" ]; then
|
|
log "Backing up .env file..."
|
|
if ! cp ".env" "$BACKUP_DIR/.env"; then
|
|
log_error "Failed to backup .env file"
|
|
exit 1
|
|
else
|
|
log_success ".env file backed up successfully"
|
|
fi
|
|
else
|
|
log_warning ".env file not found, skipping backup"
|
|
fi
|
|
|
|
# Backup scripts directories
|
|
local scripts_dirs=("scripts/ct" "scripts/install" "scripts/tools" "scripts/vm")
|
|
for scripts_dir in "${scripts_dirs[@]}"; do
|
|
if [ -d "$scripts_dir" ]; then
|
|
log "Backing up $scripts_dir directory..."
|
|
local backup_name=$(basename "$scripts_dir")
|
|
if ! cp -r "$scripts_dir" "$BACKUP_DIR/$backup_name"; then
|
|
log_error "Failed to backup $scripts_dir directory"
|
|
exit 1
|
|
else
|
|
log_success "$scripts_dir directory backed up successfully"
|
|
fi
|
|
else
|
|
log_warning "$scripts_dir directory not found, skipping backup"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Download and extract latest release
|
|
download_release() {
|
|
local release_info="$1"
|
|
local tag_name="${release_info%|*}"
|
|
local download_url="${release_info#*|}"
|
|
|
|
log "Downloading release $tag_name..."
|
|
|
|
local temp_dir="/tmp/pve-update-$$"
|
|
local archive_file="$temp_dir/release.tar.gz"
|
|
|
|
# Create temporary directory
|
|
if ! mkdir -p "$temp_dir"; then
|
|
log_error "Failed to create temporary directory"
|
|
exit 1
|
|
fi
|
|
|
|
# Download release with timeout and progress
|
|
if ! curl -L --connect-timeout 30 --max-time 300 --retry 3 --retry-delay 5 -o "$archive_file" "$download_url" 2>/dev/null; then
|
|
log_error "Failed to download release from GitHub"
|
|
rm -rf "$temp_dir"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify download
|
|
if [ ! -f "$archive_file" ] || [ ! -s "$archive_file" ]; then
|
|
log_error "Downloaded file is empty or missing"
|
|
rm -rf "$temp_dir"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Downloaded release"
|
|
|
|
# Extract release
|
|
if ! tar -xzf "$archive_file" -C "$temp_dir" 2>/dev/null; then
|
|
log_error "Failed to extract release"
|
|
rm -rf "$temp_dir"
|
|
exit 1
|
|
fi
|
|
|
|
# Find the extracted directory (GitHub tarballs have a root directory)
|
|
local extracted_dir
|
|
extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "community-scripts-ProxmoxVE-Local-*" 2>/dev/null | head -1)
|
|
|
|
# Try alternative patterns if not found
|
|
if [ -z "$extracted_dir" ]; then
|
|
extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "${REPO_NAME}-*" 2>/dev/null | head -1)
|
|
fi
|
|
|
|
if [ -z "$extracted_dir" ]; then
|
|
extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d ! -name "$temp_dir" 2>/dev/null | head -1)
|
|
fi
|
|
|
|
if [ -z "$extracted_dir" ]; then
|
|
log_error "Could not find extracted directory"
|
|
rm -rf "$temp_dir"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Release extracted successfully"
|
|
echo "$extracted_dir"
|
|
}
|
|
|
|
# Clear the original directory before updating
|
|
clear_original_directory() {
|
|
log "Clearing original directory..."
|
|
|
|
# Remove old lock files and node_modules before update
|
|
rm -f package-lock.json 2>/dev/null
|
|
rm -rf node_modules 2>/dev/null
|
|
|
|
# List of files/directories to preserve (already backed up)
|
|
local preserve_patterns=(
|
|
"data"
|
|
"data/*"
|
|
".env"
|
|
"*.log"
|
|
"update.log"
|
|
"*.backup"
|
|
"*.bak"
|
|
".git"
|
|
"scripts"
|
|
)
|
|
|
|
# Remove all files except preserved ones
|
|
while IFS= read -r file; do
|
|
local should_preserve=false
|
|
local filename=$(basename "$file")
|
|
|
|
for pattern in "${preserve_patterns[@]}"; do
|
|
if [[ "$filename" == $pattern ]]; then
|
|
should_preserve=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$should_preserve" = false ]; then
|
|
rm -f "$file"
|
|
fi
|
|
done < <(find . -maxdepth 1 -type f ! -name ".*")
|
|
|
|
# Remove all directories except preserved ones
|
|
while IFS= read -r dir; do
|
|
local should_preserve=false
|
|
local dirname=$(basename "$dir")
|
|
|
|
for pattern in "${preserve_patterns[@]}"; do
|
|
if [[ "$dirname" == $pattern ]]; then
|
|
should_preserve=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$should_preserve" = false ]; then
|
|
rm -rf "$dir"
|
|
fi
|
|
done < <(find . -maxdepth 1 -type d ! -name "." ! -name "..")
|
|
|
|
log_success "Original directory cleared"
|
|
}
|
|
|
|
# Restore backup files before building
|
|
restore_backup_files() {
|
|
log "Restoring .env, data directory, and scripts directories from backup..."
|
|
|
|
if [ -d "$BACKUP_DIR" ]; then
|
|
# Restore .env file
|
|
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
if [ -f ".env" ]; then
|
|
rm -f ".env"
|
|
fi
|
|
if cp "$BACKUP_DIR/.env" ".env"; then
|
|
log_success ".env file restored from backup"
|
|
else
|
|
log_error "Failed to restore .env file"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warning "No .env file backup found"
|
|
fi
|
|
|
|
# Restore data directory
|
|
if [ -d "$BACKUP_DIR/data" ]; then
|
|
if [ -d "data" ]; then
|
|
rm -rf "data"
|
|
fi
|
|
if cp -r "$BACKUP_DIR/data" "data"; then
|
|
log_success "Data directory restored from backup"
|
|
else
|
|
log_error "Failed to restore data directory"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warning "No data directory backup found"
|
|
fi
|
|
|
|
# Restore scripts directories
|
|
local scripts_dirs=("ct" "install" "tools" "vm")
|
|
for backup_name in "${scripts_dirs[@]}"; do
|
|
if [ -d "$BACKUP_DIR/$backup_name" ]; then
|
|
local target_dir="scripts/$backup_name"
|
|
log "Restoring $target_dir directory from backup..."
|
|
|
|
# Ensure scripts directory exists
|
|
if [ ! -d "scripts" ]; then
|
|
mkdir -p "scripts"
|
|
fi
|
|
|
|
# Remove existing directory if it exists
|
|
if [ -d "$target_dir" ]; then
|
|
rm -rf "$target_dir"
|
|
fi
|
|
|
|
if cp -r "$BACKUP_DIR/$backup_name" "$target_dir"; then
|
|
log_success "$target_dir directory restored from backup"
|
|
else
|
|
log_error "Failed to restore $target_dir directory"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warning "No $backup_name directory backup found"
|
|
fi
|
|
done
|
|
else
|
|
log_error "No backup directory found for restoration"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Verify database was restored correctly
|
|
verify_database_restored() {
|
|
log "Verifying database was restored correctly..."
|
|
|
|
# Ensure data directory exists (will be auto-created by app if needed)
|
|
if [ ! -d "data" ]; then
|
|
log "Creating data directory..."
|
|
mkdir -p data
|
|
fi
|
|
|
|
# Check for both possible database filenames
|
|
local db_file=""
|
|
if [ -f "data/database.sqlite" ]; then
|
|
db_file="data/database.sqlite"
|
|
elif [ -f "data/settings.db" ]; then
|
|
db_file="data/settings.db"
|
|
else
|
|
# Database doesn't exist yet - this is OK for new installations
|
|
# The app will create it automatically via Prisma migrations
|
|
log_warning "No existing database file found - will be created automatically on first start"
|
|
return 0
|
|
fi
|
|
|
|
local db_size=$(stat -f%z "$db_file" 2>/dev/null || stat -c%s "$db_file" 2>/dev/null)
|
|
if [ "$db_size" -eq 0 ]; then
|
|
log_warning "Database file is empty - will be recreated by Prisma migrations"
|
|
return 0 # Don't fail the update, let Prisma recreate the database
|
|
fi
|
|
|
|
log_success "Database verified (file: $db_file, size: $db_size bytes)"
|
|
}
|
|
|
|
# Ensure DATABASE_URL is set in .env file for Prisma
|
|
ensure_database_url() {
|
|
log "Ensuring DATABASE_URL is set in .env file..."
|
|
|
|
# Check if .env file exists
|
|
if [ ! -f ".env" ]; then
|
|
log_warning ".env file not found, creating from .env.example..."
|
|
if [ -f ".env.example" ]; then
|
|
cp ".env.example" ".env"
|
|
else
|
|
log_error ".env.example not found, cannot create .env file"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Check if DATABASE_URL is already set
|
|
if grep -q "^DATABASE_URL=" .env; then
|
|
log "DATABASE_URL already exists in .env file"
|
|
return 0
|
|
fi
|
|
|
|
# Add DATABASE_URL to .env file
|
|
log "Adding DATABASE_URL to .env file..."
|
|
echo "" >>.env
|
|
echo "# Database" >>.env
|
|
echo "DATABASE_URL=\"file:./data/settings.db\"" >>.env
|
|
|
|
log_success "DATABASE_URL added to .env file"
|
|
}
|
|
|
|
# Check if systemd service exists
|
|
check_service() {
|
|
# systemctl status returns 0-3 if service exists (running, exited, failed, etc.)
|
|
# and returns 4 if service unit is not found
|
|
systemctl status pvescriptslocal.service &>/dev/null
|
|
local exit_code=$?
|
|
if [ $exit_code -le 3 ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Stop the application before updating
|
|
stop_application() {
|
|
|
|
# Change to the application directory if we're not already there
|
|
local app_dir
|
|
if [ -f "package.json" ] && [ -f "server.js" ]; then
|
|
app_dir="$(pwd)"
|
|
else
|
|
# Change to production application directory
|
|
app_dir="/opt/ProxmoxVE-Local"
|
|
if [ -d "$app_dir" ] && [ -f "$app_dir/server.js" ]; then
|
|
cd "$app_dir" || {
|
|
log_error "Failed to change to application directory: $app_dir"
|
|
return 1
|
|
}
|
|
else
|
|
log_error "Production application directory not found: $app_dir"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
log "Working from application directory: $(pwd)"
|
|
|
|
# Check if systemd service is running and disable it temporarily
|
|
if check_service && systemctl is-active --quiet pvescriptslocal.service; then
|
|
log "Disabling systemd service temporarily to prevent auto-restart..."
|
|
if systemctl disable pvescriptslocal.service; then
|
|
log_success "Service disabled successfully"
|
|
else
|
|
log_error "Failed to disable service"
|
|
return 1
|
|
fi
|
|
else
|
|
log "No running systemd service found"
|
|
fi
|
|
|
|
# Kill any remaining npm/node processes
|
|
log "Killing any remaining npm/node processes..."
|
|
local pids
|
|
pids=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true)
|
|
if [ -n "$pids" ]; then
|
|
log "Found running processes: $pids"
|
|
pkill -9 -f "node server.js" 2>/dev/null || true
|
|
pkill -9 -f "npm start" 2>/dev/null || true
|
|
sleep 2
|
|
log_success "Processes killed"
|
|
else
|
|
log "No running processes found"
|
|
fi
|
|
}
|
|
|
|
# Update application files
|
|
update_files() {
|
|
local source_dir="$1"
|
|
|
|
log "Updating application files..."
|
|
|
|
# List of files/directories to exclude from update
|
|
local exclude_patterns=(
|
|
"data"
|
|
"node_modules"
|
|
".git"
|
|
".env"
|
|
"*.log"
|
|
"update.log"
|
|
"*.backup"
|
|
"*.bak"
|
|
"scripts/ct"
|
|
"scripts/install"
|
|
"scripts/tools"
|
|
"scripts/vm"
|
|
)
|
|
|
|
# Find the actual source directory (strip the top-level directory)
|
|
local actual_source_dir
|
|
actual_source_dir=$(find "$source_dir" -maxdepth 1 -type d -name "community-scripts-ProxmoxVE-Local-*" | head -1)
|
|
|
|
if [ -z "$actual_source_dir" ]; then
|
|
log_error "Could not find the actual source directory in $source_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Verify critical files exist in source
|
|
if [ ! -f "$actual_source_dir/package.json" ]; then
|
|
log_error "package.json not found in source directory!"
|
|
return 1
|
|
fi
|
|
|
|
# Use process substitution instead of pipe to avoid subshell issues
|
|
local files_copied=0
|
|
local files_excluded=0
|
|
|
|
# Create a temporary file list to avoid process substitution issues
|
|
local file_list="/tmp/file_list_$$.txt"
|
|
find "$actual_source_dir" -type f >"$file_list"
|
|
|
|
while IFS= read -r file; do
|
|
local rel_path="${file#$actual_source_dir/}"
|
|
local should_exclude=false
|
|
|
|
for pattern in "${exclude_patterns[@]}"; do
|
|
if [[ "$rel_path" == $pattern ]] || [[ "$rel_path" == $pattern/* ]]; then
|
|
should_exclude=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$should_exclude" = false ]; then
|
|
local target_dir
|
|
target_dir=$(dirname "$rel_path")
|
|
if [ "$target_dir" != "." ]; then
|
|
mkdir -p "$target_dir"
|
|
fi
|
|
|
|
if ! cp "$file" "$rel_path"; then
|
|
log_error "Failed to copy $rel_path"
|
|
rm -f "$file_list"
|
|
return 1
|
|
fi
|
|
files_copied=$((files_copied + 1))
|
|
else
|
|
files_excluded=$((files_excluded + 1))
|
|
fi
|
|
done <"$file_list"
|
|
|
|
# Clean up temporary file
|
|
rm -f "$file_list"
|
|
|
|
# Verify critical files were copied
|
|
if [ ! -f "package.json" ]; then
|
|
log_error "package.json was not copied to target directory!"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "package-lock.json" ]; then
|
|
log_warning "package-lock.json was not copied!"
|
|
fi
|
|
|
|
log_success "Application files updated successfully ($files_copied files)"
|
|
}
|
|
|
|
# Install dependencies and build
|
|
install_and_build() {
|
|
log "Installing dependencies..."
|
|
|
|
# Verify package.json exists
|
|
if [ ! -f "package.json" ]; then
|
|
log_error "package.json not found! Cannot install dependencies."
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "package-lock.json" ]; then
|
|
log_warning "No package-lock.json found, npm will generate one"
|
|
fi
|
|
|
|
# Create temporary file for npm output
|
|
local npm_log="/tmp/npm_install_$$.log"
|
|
|
|
# Ensure NODE_ENV is not set to production during install (we need devDependencies for build)
|
|
local old_node_env="${NODE_ENV:-}"
|
|
export NODE_ENV=development
|
|
|
|
# Run npm install to get ALL dependencies including devDependencies
|
|
if ! npm install --include=dev >"$npm_log" 2>&1; then
|
|
log_error "Failed to install dependencies"
|
|
log_error "npm install output (last 30 lines):"
|
|
tail -30 "$npm_log" | while read -r line; do
|
|
log_error "NPM: $line"
|
|
done
|
|
rm -f "$npm_log"
|
|
return 1
|
|
fi
|
|
|
|
# Restore NODE_ENV
|
|
if [ -n "$old_node_env" ]; then
|
|
export NODE_ENV="$old_node_env"
|
|
else
|
|
unset NODE_ENV
|
|
fi
|
|
|
|
log_success "Dependencies installed successfully"
|
|
rm -f "$npm_log"
|
|
|
|
# Generate Prisma client
|
|
log "Generating Prisma client..."
|
|
if ! npx prisma generate >"$npm_log" 2>&1; then
|
|
log_error "Failed to generate Prisma client"
|
|
log_error "Prisma generate output:"
|
|
cat "$npm_log" | while read -r line; do
|
|
log_error "PRISMA: $line"
|
|
done
|
|
rm -f "$npm_log"
|
|
return 1
|
|
fi
|
|
log_success "Prisma client generated successfully"
|
|
|
|
# Check if Prisma migrations exist and are compatible
|
|
if [ -d "prisma/migrations" ]; then
|
|
log "Existing migration history detected"
|
|
local migration_count=$(find prisma/migrations -type d -mindepth 1 | wc -l)
|
|
log "Found $migration_count existing migrations"
|
|
else
|
|
log_warning "No existing migration history found - this may be a fresh install"
|
|
fi
|
|
|
|
# Run Prisma migrations
|
|
log "Running Prisma migrations..."
|
|
if ! npx prisma migrate deploy >"$npm_log" 2>&1; then
|
|
log_warning "Prisma migrations failed or no migrations to run"
|
|
log "Prisma migrate output:"
|
|
cat "$npm_log" | while read -r line; do
|
|
log "PRISMA: $line"
|
|
done
|
|
else
|
|
log_success "Prisma migrations completed successfully"
|
|
fi
|
|
rm -f "$npm_log"
|
|
|
|
log "Building application..."
|
|
# Set NODE_ENV to production for build
|
|
export NODE_ENV=production
|
|
|
|
# Create temporary file for npm build output
|
|
local build_log="/tmp/npm_build_$$.log"
|
|
|
|
if ! npm run build >"$build_log" 2>&1; then
|
|
log_error "Failed to build application"
|
|
log_error "npm run build output:"
|
|
cat "$build_log" | while read -r line; do
|
|
log_error "BUILD: $line"
|
|
done
|
|
rm -f "$build_log"
|
|
return 1
|
|
fi
|
|
|
|
# Log success and clean up
|
|
log_success "Application built successfully"
|
|
rm -f "$build_log"
|
|
|
|
log_success "Dependencies installed and application built successfully"
|
|
}
|
|
|
|
# Start the application after updating
|
|
start_application() {
|
|
log "Starting application..."
|
|
|
|
# Use the global variable to determine how to start
|
|
if [ "$SERVICE_WAS_RUNNING" = true ] && check_service; then
|
|
log "Service was running before update, re-enabling and starting systemd service..."
|
|
if systemctl enable --now pvescriptslocal.service; then
|
|
systemctl restart pvescriptslocal.service
|
|
log_success "Service enabled and started successfully"
|
|
# Wait a moment and check if it's running
|
|
sleep 2
|
|
if systemctl is-active --quiet pvescriptslocal.service; then
|
|
log_success "Service is running"
|
|
else
|
|
log_warning "Service started but may not be running properly"
|
|
fi
|
|
else
|
|
log_error "Failed to enable/start service, falling back to npm start"
|
|
if ! start_with_npm; then
|
|
log_error "Failed to start application with npm"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
log "Service was not running before update or no service exists, starting with npm..."
|
|
if ! start_with_npm; then
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Start application with npm
|
|
start_with_npm() {
|
|
log "Starting application with npm start..."
|
|
|
|
# Start in background
|
|
nohup npm start >server.log 2>&1 &
|
|
local npm_pid=$!
|
|
|
|
# Wait a moment and check if it started
|
|
sleep 3
|
|
if kill -0 $npm_pid 2>/dev/null; then
|
|
log_success "Application started with PID: $npm_pid"
|
|
else
|
|
log_error "Failed to start application with npm"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Rollback function
|
|
rollback() {
|
|
log_warning "Rolling back to previous version..."
|
|
|
|
if [ -d "$BACKUP_DIR" ]; then
|
|
log "Restoring from backup directory: $BACKUP_DIR"
|
|
|
|
# Restore data directory
|
|
if [ -d "$BACKUP_DIR/data" ]; then
|
|
log "Restoring data directory..."
|
|
if [ -d "$DATA_DIR" ]; then
|
|
rm -rf "$DATA_DIR"
|
|
fi
|
|
if mv "$BACKUP_DIR/data" "$DATA_DIR"; then
|
|
log_success "Data directory restored from backup"
|
|
else
|
|
log_error "Failed to restore data directory"
|
|
fi
|
|
else
|
|
log_warning "No data directory backup found"
|
|
fi
|
|
|
|
# Restore .env file
|
|
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
log "Restoring .env file..."
|
|
if [ -f ".env" ]; then
|
|
rm -f ".env"
|
|
fi
|
|
if mv "$BACKUP_DIR/.env" ".env"; then
|
|
log_success ".env file restored from backup"
|
|
else
|
|
log_error "Failed to restore .env file"
|
|
fi
|
|
else
|
|
log_warning "No .env file backup found"
|
|
fi
|
|
|
|
# Restore scripts directories
|
|
local scripts_dirs=("ct" "install" "tools" "vm")
|
|
for backup_name in "${scripts_dirs[@]}"; do
|
|
if [ -d "$BACKUP_DIR/$backup_name" ]; then
|
|
local target_dir="scripts/$backup_name"
|
|
log "Restoring $target_dir directory from backup..."
|
|
|
|
# Ensure scripts directory exists
|
|
if [ ! -d "scripts" ]; then
|
|
mkdir -p "scripts"
|
|
fi
|
|
|
|
# Remove existing directory if it exists
|
|
if [ -d "$target_dir" ]; then
|
|
rm -rf "$target_dir"
|
|
fi
|
|
|
|
if mv "$BACKUP_DIR/$backup_name" "$target_dir"; then
|
|
log_success "$target_dir directory restored from backup"
|
|
else
|
|
log_error "Failed to restore $target_dir directory"
|
|
fi
|
|
else
|
|
log_warning "No $backup_name directory backup found"
|
|
fi
|
|
done
|
|
|
|
# Clean up backup directory
|
|
log "Cleaning up backup directory..."
|
|
rm -rf "$BACKUP_DIR"
|
|
else
|
|
log_error "No backup directory found for rollback"
|
|
fi
|
|
|
|
log_error "Update failed. Please check the logs and try again."
|
|
exit 1
|
|
}
|
|
|
|
# Check installed Node.js version and upgrade if needed
|
|
check_node_version() {
|
|
if ! command -v node &>/dev/null; then
|
|
log_error "Node.js is not installed"
|
|
exit 1
|
|
fi
|
|
|
|
local current major_version
|
|
|
|
current=$(node -v 2>/dev/null | tr -d 'v')
|
|
major_version=${current%%.*}
|
|
|
|
log "Detected Node.js version: $current"
|
|
|
|
if ((major_version < 24)); then
|
|
log_warning "Node.js < 24 detected → upgrading to Node.js 24 LTS..."
|
|
upgrade_node_to_24
|
|
elif ((major_version > 24)); then
|
|
log_warning "Node.js > 24 detected → script tested only up to Node 24"
|
|
log "Continuing anyway…"
|
|
else
|
|
log_success "Node.js 24 already installed"
|
|
fi
|
|
}
|
|
|
|
# Upgrade Node.js to version 24
|
|
upgrade_node_to_24() {
|
|
log "Preparing Node.js 24 upgrade…"
|
|
|
|
# Remove old nodesource repo if it exists
|
|
if [ -f /etc/apt/sources.list.d/nodesource.list ]; then
|
|
rm -f /etc/apt/sources.list.d/nodesource.list
|
|
fi
|
|
|
|
# Install NodeSource repo for Node.js 24
|
|
curl -fsSL https://deb.nodesource.com/setup_24.x -o /tmp/node24_setup.sh
|
|
if ! bash /tmp/node24_setup.sh >/tmp/node24_setup.log 2>&1; then
|
|
log_error "Failed to configure Node.js 24 repository"
|
|
tail -20 /tmp/node24_setup.log | while read -r line; do log_error "$line"; done
|
|
exit 1
|
|
fi
|
|
|
|
log "Installing Node.js 24…"
|
|
if ! apt-get install -y nodejs >>"$LOG_FILE" 2>&1; then
|
|
log_error "Failed to install Node.js 24"
|
|
exit 1
|
|
fi
|
|
|
|
local new_ver
|
|
new_ver=$(node -v 2>/dev/null || true)
|
|
log_success "Node.js successfully upgraded to $new_ver"
|
|
}
|
|
|
|
# Main update process
|
|
main() {
|
|
# Check if this is the relocated/detached version first
|
|
if [ "${1:-}" = "--relocated" ]; then
|
|
export PVE_UPDATE_RELOCATED=1
|
|
init_log
|
|
log "Running as detached process"
|
|
sleep 3
|
|
|
|
else
|
|
init_log
|
|
fi
|
|
|
|
# Check if we're running from the application directory and not already relocated
|
|
if [ -z "${PVE_UPDATE_RELOCATED:-}" ] && [ -f "package.json" ] && [ -f "server.js" ]; then
|
|
log "Detected running from application directory"
|
|
bash "$0" --relocated
|
|
exit $?
|
|
fi
|
|
|
|
# Ensure we're in the application directory
|
|
local app_dir
|
|
|
|
# First check if we're already in the right directory
|
|
if [ -f "package.json" ] && [ -f "server.js" ]; then
|
|
app_dir="$(pwd)"
|
|
else
|
|
# Use production application directory
|
|
app_dir="/opt/ProxmoxVE-Local"
|
|
if [ -d "$app_dir" ] && [ -f "$app_dir/server.js" ]; then
|
|
cd "$app_dir" || {
|
|
log_error "Failed to change to application directory: $app_dir"
|
|
exit 1
|
|
}
|
|
else
|
|
log_error "Production application directory not found: $app_dir"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Check dependencies
|
|
check_dependencies
|
|
|
|
# Load GitHub token for higher rate limits
|
|
load_github_token
|
|
|
|
# Check if service was running before update
|
|
if check_service && systemctl is-active --quiet pvescriptslocal.service; then
|
|
SERVICE_WAS_RUNNING=true
|
|
else
|
|
SERVICE_WAS_RUNNING=false
|
|
fi
|
|
|
|
# Get latest release info
|
|
local release_info
|
|
release_info=$(get_latest_release)
|
|
|
|
# Backup data directory
|
|
backup_data
|
|
|
|
# Stop the application before updating
|
|
stop_application
|
|
|
|
# Check Node.js version
|
|
check_node_version
|
|
|
|
#Update Node.js to 24
|
|
upgrade_node_to_24
|
|
|
|
# Download and extract release
|
|
local source_dir
|
|
source_dir=$(download_release "$release_info")
|
|
|
|
# Clear the original directory before updating
|
|
clear_original_directory
|
|
|
|
# Update files
|
|
if ! update_files "$source_dir"; then
|
|
log_error "File update failed, rolling back..."
|
|
rollback
|
|
fi
|
|
|
|
# Restore .env and data directory before building
|
|
restore_backup_files
|
|
|
|
# Verify database was restored correctly
|
|
if ! verify_database_restored; then
|
|
log_error "Database verification failed, rolling back..."
|
|
rollback
|
|
fi
|
|
|
|
# Ensure DATABASE_URL is set for Prisma
|
|
ensure_database_url
|
|
|
|
# Install dependencies and build
|
|
if ! install_and_build; then
|
|
log_error "Install and build failed, rolling back..."
|
|
rollback
|
|
fi
|
|
|
|
# Start the application
|
|
if ! start_application; then
|
|
log_error "Failed to start application after update"
|
|
rollback
|
|
fi
|
|
|
|
# Cleanup only after successful start
|
|
rm -rf "$source_dir"
|
|
rm -rf "/tmp/pve-update-$$"
|
|
rm -rf "$BACKUP_DIR"
|
|
log "Backup directory cleaned up"
|
|
|
|
log_success "Update completed successfully!"
|
|
}
|
|
|
|
# Run main function with error handling
|
|
if ! main "$@"; then
|
|
log_error "Update script failed with exit code $?"
|
|
exit 1
|
|
fi
|