From ea5d3e067a78f7ce9c9a1db5de974ab05087b29c Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Tue, 7 Oct 2025 16:39:24 +0200 Subject: [PATCH 01/41] fix(update): properly detach update script to survive service shutdown - Use setsid and nohup to completely detach update process from parent Node.js - Add 3-second grace period to allow parent process to respond to client - Fix issue where update script would stop when killing Node.js process - Improve systemd service detection using systemctl status with exit code check --- update.sh | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/update.sh b/update.sh index 0020c3a..940ea4c 100755 --- a/update.sh +++ b/update.sh @@ -368,7 +368,11 @@ restore_backup_files() { # Check if systemd service exists check_service() { - if systemctl list-unit-files | grep -q "^pvescriptslocal.service"; then + # 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 @@ -781,10 +785,28 @@ main() { chmod +x "$temp_script" log "Executing update from temporary location: $temp_script" + log "Detaching update process to survive service shutdown..." - # Set flag to prevent infinite loop and execute from temporary location + # Use setsid and nohup to completely detach from parent process + # This ensures the script continues even if the Node.js process is killed + nohup setsid bash "$temp_script" PVE_UPDATE_RELOCATED=1 /dev/null 2>&1 & + + log "Update process started in background (PID: $!)" + log "This process will continue even after the service stops" + exit 0 + fi + + # If we get here, we've been relocated and detached + if [ "${1:-}" = "PVE_UPDATE_RELOCATED=1" ]; then export PVE_UPDATE_RELOCATED=1 - exec "$temp_script" "$@" + # Reinitialize log since we're in a new process + init_log + log "Running as detached process, safe to kill parent Node.js process" + + # Wait a few seconds to allow the parent process to send response to client + log "Waiting 3 seconds to allow parent process to respond to client..." + sleep 3 + log "Proceeding with update process" fi # Ensure we're in the application directory From 18baeea438d0bfb7ab0bc55d188553182c233cd6 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Tue, 7 Oct 2025 16:42:00 +0200 Subject: [PATCH 02/41] fix(update): prevent infinite loop in script relocation - Check for --relocated flag at the start of main() before any other logic - Set PVE_UPDATE_RELOCATED environment variable immediately when --relocated is detected - Prevents relocated script from triggering relocation logic again --- update.sh | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/update.sh b/update.sh index 940ea4c..c8cdd7b 100755 --- a/update.sh +++ b/update.sh @@ -770,7 +770,19 @@ rollback() { # Main update process main() { - init_log + # 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, safe to kill parent Node.js process" + + # Wait a few seconds to allow the parent process to send response to client + log "Waiting 3 seconds to allow parent process to respond to client..." + sleep 3 + log "Proceeding with update process" + 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 @@ -789,26 +801,13 @@ main() { # Use setsid and nohup to completely detach from parent process # This ensures the script continues even if the Node.js process is killed - nohup setsid bash "$temp_script" PVE_UPDATE_RELOCATED=1 /dev/null 2>&1 & + nohup setsid bash "$temp_script" --relocated /dev/null 2>&1 & log "Update process started in background (PID: $!)" log "This process will continue even after the service stops" exit 0 fi - # If we get here, we've been relocated and detached - if [ "${1:-}" = "PVE_UPDATE_RELOCATED=1" ]; then - export PVE_UPDATE_RELOCATED=1 - # Reinitialize log since we're in a new process - init_log - log "Running as detached process, safe to kill parent Node.js process" - - # Wait a few seconds to allow the parent process to send response to client - log "Waiting 3 seconds to allow parent process to respond to client..." - sleep 3 - log "Proceeding with update process" - fi - # Ensure we're in the application directory local app_dir From 3848095c9281da1aefa4ff28b6f97023f69aa8ff Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Tue, 7 Oct 2025 16:43:25 +0200 Subject: [PATCH 03/41] fix(update): use systemd-run and double-fork for complete process isolation - Primary: Use systemd-run --user --scope with KillMode=none for complete isolation - Fallback: Implement double-fork daemonization technique - Ensures update script survives systemd service shutdown - Script is fully orphaned and reparented to init/systemd --- update.sh | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/update.sh b/update.sh index c8cdd7b..c924529 100755 --- a/update.sh +++ b/update.sh @@ -799,11 +799,30 @@ main() { log "Executing update from temporary location: $temp_script" log "Detaching update process to survive service shutdown..." - # Use setsid and nohup to completely detach from parent process - # This ensures the script continues even if the Node.js process is killed - nohup setsid bash "$temp_script" --relocated /dev/null 2>&1 & + # Use systemd-run if available for complete isolation from the service + if command -v systemd-run &> /dev/null; then + log "Using systemd-run for maximum isolation..." + systemd-run --user --scope --property=KillMode=none bash "$temp_script" --relocated &>/dev/null || { + # Fallback to traditional method if systemd-run fails + log_warning "systemd-run failed, using nohup/setsid fallback" + nohup setsid bash "$temp_script" --relocated >/tmp/update.log 2>&1 & + } + else + # Double-fork technique for complete daemonization + ( + # First fork - become session leader + setsid bash -c " + # Second fork - ensure we're not a session leader + ( + cd / + exec bash '$temp_script' --relocated >/tmp/update.log 2>&1 + ) & + " & + ) + fi - log "Update process started in background (PID: $!)" + sleep 1 # Give the detached process time to start + log "Update process started and fully detached" log "This process will continue even after the service stops" exit 0 fi From 24bab671930d917c5c2b2ae74d07df639a348465 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:08:34 +0200 Subject: [PATCH 04/41] Update update.sh --- update.sh | 108 +++++++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 67 deletions(-) diff --git a/update.sh b/update.sh index c924529..6318a10 100755 --- a/update.sh +++ b/update.sh @@ -16,6 +16,9 @@ BACKUP_DIR="/tmp/pve-scripts-backup-$(date +%Y%m%d-%H%M%S)" DATA_DIR="./data" LOG_FILE="/tmp/update.log" +# 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' @@ -379,6 +382,19 @@ check_service() { fi } +# Check if systemd service is running +is_service_running() { + if check_service; then + if systemctl is-active --quiet pvescriptslocal.service; then + return 0 + else + return 1 + fi + else + return 1 + fi +} + # Kill application processes directly kill_processes() { # Try to find and stop the Node.js process @@ -513,7 +529,7 @@ kill_processes() { # Stop the application before updating stop_application() { - log "Stopping application..." + log "Stopping application processes..." # Change to the application directory if we're not already there local app_dir @@ -535,22 +551,12 @@ stop_application() { log "Working from application directory: $(pwd)" - # Check if systemd service exists and is active - if check_service; then - if systemctl is-active --quiet pvescriptslocal.service; then - log "Stopping pvescriptslocal service..." - if systemctl stop pvescriptslocal.service; then - log_success "Service stopped successfully" - else - log_error "Failed to stop service, falling back to process kill" - kill_processes - fi - else - log "Service exists but is not active, checking for running processes..." - kill_processes - fi + # Check if systemd service is running but don't stop it + if is_service_running; then + log "Systemd service is running, killing npm/node processes only (keeping service running)..." + kill_processes else - log "No systemd service found, stopping processes directly..." + log "No running systemd service found, stopping processes directly..." kill_processes fi } @@ -680,24 +686,24 @@ install_and_build() { start_application() { log "Starting application..." - # Check if systemd service exists - if check_service; then - log "Starting pvescriptslocal service..." - if systemctl start pvescriptslocal.service; then - log_success "Service started successfully" + # Use the global variable to determine how to start + if [ "$SERVICE_WAS_RUNNING" = true ] && check_service; then + log "Service was running before update, restarting systemd service..." + if systemctl restart pvescriptslocal.service; then + log_success "Service restarted 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" + log_warning "Service restarted but may not be running properly" fi else - log_error "Failed to start service, falling back to npm start" + log_error "Failed to restart service, falling back to npm start" start_with_npm fi else - log "No systemd service found, starting with npm..." + log "Service was not running before update or no service exists, starting with npm..." start_with_npm fi } @@ -787,44 +793,8 @@ main() { # 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" - log "Copying update script to temporary location for safe execution..." - - local temp_script="/tmp/pve-scripts-update-$$.sh" - if ! cp "$0" "$temp_script"; then - log_error "Failed to copy update script to temporary location" - exit 1 - fi - - chmod +x "$temp_script" - log "Executing update from temporary location: $temp_script" - log "Detaching update process to survive service shutdown..." - - # Use systemd-run if available for complete isolation from the service - if command -v systemd-run &> /dev/null; then - log "Using systemd-run for maximum isolation..." - systemd-run --user --scope --property=KillMode=none bash "$temp_script" --relocated &>/dev/null || { - # Fallback to traditional method if systemd-run fails - log_warning "systemd-run failed, using nohup/setsid fallback" - nohup setsid bash "$temp_script" --relocated >/tmp/update.log 2>&1 & - } - else - # Double-fork technique for complete daemonization - ( - # First fork - become session leader - setsid bash -c " - # Second fork - ensure we're not a session leader - ( - cd / - exec bash '$temp_script' --relocated >/tmp/update.log 2>&1 - ) & - " & - ) - fi - - sleep 1 # Give the detached process time to start - log "Update process started and fully detached" - log "This process will continue even after the service stops" - exit 0 + bash "$0" --relocated + exit $? fi # Ensure we're in the application directory @@ -861,6 +831,15 @@ main() { # Check dependencies check_dependencies + # Check if service was running before update + if is_service_running; then + SERVICE_WAS_RUNNING=true + log "Service was running before update, will restart it after update" + else + SERVICE_WAS_RUNNING=false + log "Service was not running before update, will use npm start after update" + fi + # Get latest release info local release_info release_info=$(get_latest_release) @@ -917,11 +896,6 @@ main() { rm -rf "$source_dir" rm -rf "/tmp/pve-update-$$" - # Clean up temporary script if it exists - if [ -f "/tmp/pve-scripts-update-$$.sh" ]; then - rm -f "/tmp/pve-scripts-update-$$.sh" - fi - # Start the application start_application From cb59ef3603a17d83529cc15530aa9ee05a36e8e5 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:15:19 +0200 Subject: [PATCH 05/41] Update update.sh --- update.sh | 185 ++---------------------------------------------------- 1 file changed, 6 insertions(+), 179 deletions(-) diff --git a/update.sh b/update.sh index 6318a10..c926dac 100755 --- a/update.sh +++ b/update.sh @@ -382,154 +382,10 @@ check_service() { fi } -# Check if systemd service is running -is_service_running() { - if check_service; then - if systemctl is-active --quiet pvescriptslocal.service; then - return 0 - else - return 1 - fi - else - return 1 - fi -} -# Kill application processes directly -kill_processes() { - # Try to find and stop the Node.js process - local pids - pids=$(pgrep -f "node server.js" 2>/dev/null || true) - - # Also check for npm start processes - local npm_pids - npm_pids=$(pgrep -f "npm start" 2>/dev/null || true) - - # Combine all PIDs - if [ -n "$npm_pids" ]; then - pids="$pids $npm_pids" - fi - - if [ -n "$pids" ]; then - log "Stopping application processes: $pids" - - # Send TERM signal to each PID individually - for pid in $pids; do - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - log "Sending TERM signal to PID: $pid" - kill -TERM "$pid" 2>/dev/null || true - fi - done - - # Wait for graceful shutdown with timeout - log "Waiting for graceful shutdown..." - local wait_count=0 - local max_wait=10 # Maximum 10 seconds - - while [ $wait_count -lt $max_wait ]; do - local still_running - still_running=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -z "$still_running" ]; then - log_success "Processes stopped gracefully" - break - fi - sleep 1 - wait_count=$((wait_count + 1)) - log "Waiting... ($wait_count/$max_wait)" - done - - # Force kill any remaining processes - local remaining_pids - remaining_pids=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$remaining_pids" ]; then - log_warning "Force killing remaining processes: $remaining_pids" - pkill -9 -f "node server.js" 2>/dev/null || true - pkill -9 -f "npm start" 2>/dev/null || true - sleep 1 - fi - - # Final check - local final_check - final_check=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$final_check" ]; then - log_warning "Some processes may still be running: $final_check" - else - log_success "All application processes stopped" - fi - else - log "No running application processes found" - fi -} - -# Kill application processes directly -kill_processes() { - # Try to find and stop the Node.js process - local pids - pids=$(pgrep -f "node server.js" 2>/dev/null || true) - - # Also check for npm start processes - local npm_pids - npm_pids=$(pgrep -f "npm start" 2>/dev/null || true) - - # Combine all PIDs - if [ -n "$npm_pids" ]; then - pids="$pids $npm_pids" - fi - - if [ -n "$pids" ]; then - log "Stopping application processes: $pids" - - # Send TERM signal to each PID individually - for pid in $pids; do - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - log "Sending TERM signal to PID: $pid" - kill -TERM "$pid" 2>/dev/null || true - fi - done - - # Wait for graceful shutdown with timeout - log "Waiting for graceful shutdown..." - local wait_count=0 - local max_wait=10 # Maximum 10 seconds - - while [ $wait_count -lt $max_wait ]; do - local still_running - still_running=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -z "$still_running" ]; then - log_success "Processes stopped gracefully" - break - fi - sleep 1 - wait_count=$((wait_count + 1)) - log "Waiting... ($wait_count/$max_wait)" - done - - # Force kill any remaining processes - local remaining_pids - remaining_pids=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$remaining_pids" ]; then - log_warning "Force killing remaining processes: $remaining_pids" - pkill -9 -f "node server.js" 2>/dev/null || true - pkill -9 -f "npm start" 2>/dev/null || true - sleep 1 - fi - - # Final check - local final_check - final_check=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$final_check" ]; then - log_warning "Some processes may still be running: $final_check" - else - log_success "All application processes stopped" - fi - else - log "No running application processes found" - fi -} - -# Stop the application before updating -stop_application() { - log "Stopping application processes..." +# Prepare for update (no need to stop processes) +prepare_for_update() { + log "Preparing for update..." # Change to the application directory if we're not already there local app_dir @@ -550,15 +406,7 @@ stop_application() { fi log "Working from application directory: $(pwd)" - - # Check if systemd service is running but don't stop it - if is_service_running; then - log "Systemd service is running, killing npm/node processes only (keeping service running)..." - kill_processes - else - log "No running systemd service found, stopping processes directly..." - kill_processes - fi + log "No need to stop processes - Node.js can handle file updates while running" } # Update application files @@ -659,17 +507,6 @@ install_and_build() { return 1 fi - # Ensure no processes are running before build - log "Ensuring no conflicting processes are running..." - local pids - pids=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$pids" ]; then - log_warning "Found running processes, stopping them: $pids" - pkill -9 -f "node server.js" 2>/dev/null || true - pkill -9 -f "npm start" 2>/dev/null || true - sleep 2 - fi - log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production @@ -847,18 +684,8 @@ main() { # Backup data directory backup_data - # Stop the application before updating (now running from /tmp/) - stop_application - - # Double-check that no processes are running - local remaining_pids - remaining_pids=$(pgrep -f "node server.js\|npm start" 2>/dev/null || true) - if [ -n "$remaining_pids" ]; then - log_warning "Force killing remaining processes" - pkill -9 -f "node server.js" 2>/dev/null || true - pkill -9 -f "npm start" 2>/dev/null || true - sleep 2 - fi + # Prepare for update (no need to stop processes) + prepare_for_update # Download and extract release local source_dir From b3c1949c63dab11e7e749ca4d9c39960b5976f11 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:20:32 +0200 Subject: [PATCH 06/41] Update update.sh --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index c926dac..f0fe0f2 100755 --- a/update.sh +++ b/update.sh @@ -669,7 +669,7 @@ main() { check_dependencies # Check if service was running before update - if is_service_running; then + if check_service && systemctl is-active --quiet pvescriptslocal.service; then SERVICE_WAS_RUNNING=true log "Service was running before update, will restart it after update" else From 0f4cfe618bfbb9894a5c9de80b39db454a5d5270 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:23:22 +0200 Subject: [PATCH 07/41] Update update.sh --- update.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/update.sh b/update.sh index f0fe0f2..db3bdb0 100755 --- a/update.sh +++ b/update.sh @@ -617,12 +617,10 @@ main() { if [ "${1:-}" = "--relocated" ]; then export PVE_UPDATE_RELOCATED=1 init_log - log "Running as detached process, safe to kill parent Node.js process" - - # Wait a few seconds to allow the parent process to send response to client - log "Waiting 3 seconds to allow parent process to respond to client..." + log "Running as detached process" sleep 3 - log "Proceeding with update process" + systemctl stop pvescriptslocal.service + else init_log fi From 7b1608f2209569819a615c6c248ba7fba8dd162e Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:29:01 +0200 Subject: [PATCH 08/41] Update update.sh --- update.sh | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/update.sh b/update.sh index db3bdb0..f751913 100755 --- a/update.sh +++ b/update.sh @@ -383,9 +383,9 @@ check_service() { } -# Prepare for update (no need to stop processes) -prepare_for_update() { - log "Preparing for update..." +# Stop the application before updating +stop_application() { + log "Stopping application before update..." # Change to the application directory if we're not already there local app_dir @@ -406,7 +406,33 @@ prepare_for_update() { fi log "Working from application directory: $(pwd)" - log "No need to stop processes - Node.js can handle file updates while running" + + # 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 @@ -473,7 +499,7 @@ update_files() { if [ "$target_dir" != "." ]; then mkdir -p "$target_dir" fi - log "Copying: $file -> $rel_path" + if ! cp "$file" "$rel_path"; then log_error "Failed to copy $rel_path" rm -f "$file_list" @@ -525,18 +551,18 @@ start_application() { # Use the global variable to determine how to start if [ "$SERVICE_WAS_RUNNING" = true ] && check_service; then - log "Service was running before update, restarting systemd service..." - if systemctl restart pvescriptslocal.service; then - log_success "Service restarted successfully" + log "Service was running before update, re-enabling and starting systemd service..." + if systemctl enable --nowpvescriptslocal.service ; then + 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 restarted but may not be running properly" + log_warning "Service started but may not be running properly" fi else - log_error "Failed to restart service, falling back to npm start" + log_error "Failed to enable/start service, falling back to npm start" start_with_npm fi else @@ -619,8 +645,7 @@ main() { init_log log "Running as detached process" sleep 3 - systemctl stop pvescriptslocal.service - + else init_log fi @@ -682,8 +707,8 @@ main() { # Backup data directory backup_data - # Prepare for update (no need to stop processes) - prepare_for_update + # Stop the application before updating + stop_application # Download and extract release local source_dir From 74007f54836b826c318422c31d4c18f3aadcd385 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:31:24 +0200 Subject: [PATCH 09/41] Update update.sh --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index f751913..0424439 100755 --- a/update.sh +++ b/update.sh @@ -527,7 +527,7 @@ update_files() { # Install dependencies and build install_and_build() { log "Installing dependencies..." - + npm install if ! npm install; then log_error "Failed to install dependencies" return 1 From c81b0307f035f74b6124d8e25652e83b67a47c55 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:37:12 +0200 Subject: [PATCH 10/41] Update update.sh --- update.sh | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/update.sh b/update.sh index 0424439..67c5797 100755 --- a/update.sh +++ b/update.sh @@ -16,6 +16,10 @@ 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 @@ -26,6 +30,33 @@ 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 .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, set GITHUB_TOKEN environment variable or create .github_token file" + return 1 +} + # Initialize log file init_log() { # Clear/create log file @@ -86,8 +117,18 @@ check_dependencies() { 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=$(curl -s --connect-timeout 15 --max-time 60 --retry 2 --retry-delay 3 "$GITHUB_API/releases/latest"); then + 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 @@ -527,21 +568,45 @@ update_files() { # Install dependencies and build install_and_build() { log "Installing dependencies..." - npm install - if ! npm install; then + + # Create temporary file for npm output + local npm_log="/tmp/npm_install_$$.log" + + if ! npm install > "$npm_log" 2>&1; then log_error "Failed to install dependencies" + log_error "npm install output:" + cat "$npm_log" | while read -r line; do + log_error "NPM: $line" + done + rm -f "$npm_log" return 1 fi + # Log success and clean up + log_success "Dependencies installed successfully" + rm -f "$npm_log" + log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production - if ! npm run build; then + # 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" } @@ -552,7 +617,7 @@ start_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 --nowpvescriptslocal.service ; then + if systemctl enable --now pvescriptslocal.service; then log_success "Service enabled and started successfully" # Wait a moment and check if it's running sleep 2 @@ -691,6 +756,9 @@ main() { # 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 From 1412cc40167a5583a2225dac6a1610967c3499cc Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:42:00 +0200 Subject: [PATCH 11/41] Update update.sh --- .env.example | 1 + env.example | 25 +++++++++++++++++++++++++ src/env.js | 4 ++++ src/server/api/routers/version.ts | 20 ++++++++++++++++++-- update.sh | 13 ++++++++++++- 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 env.example diff --git a/.env.example b/.env.example index 175f439..22ab3db 100644 --- a/.env.example +++ b/.env.example @@ -16,3 +16,4 @@ ALLOWED_SCRIPT_PATHS="scripts/" # WebSocket Configuration WEBSOCKET_PORT="3001" +GITHUB_TOKEN=your_github_token_here \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..dadfbfc --- /dev/null +++ b/env.example @@ -0,0 +1,25 @@ +# Environment variables for ProxmoxVE-Local +# Copy this file to .env and fill in your values + +# GitHub Personal Access Token for higher API rate limits +# Get your token from: https://github.com/settings/tokens +# Required scope: public_repo (for public repositories) +# This token is used by both the update script and frontend version checking +GITHUB_TOKEN=your_github_token_here + +# Repository Configuration (optional) +# REPO_URL=https://github.com/your-username/your-scripts-repo +# REPO_BRANCH=main +# SCRIPTS_DIRECTORY=scripts +# JSON_FOLDER=json +# ALLOWED_SCRIPT_EXTENSIONS=.sh,.py,.js,.ts,.bash + +# Security Configuration +# MAX_SCRIPT_EXECUTION_TIME=300000 +# ALLOWED_SCRIPT_PATHS=scripts/ + +# WebSocket Configuration +# WEBSOCKET_PORT=3001 + +# Other Configuration +# NODE_ENV=production diff --git a/src/env.js b/src/env.js index 88dd269..66f49d3 100644 --- a/src/env.js +++ b/src/env.js @@ -23,6 +23,8 @@ export const env = createEnv({ ALLOWED_SCRIPT_PATHS: z.string().default("scripts/"), // WebSocket Configuration WEBSOCKET_PORT: z.string().default("3001"), + // GitHub Configuration + GITHUB_TOKEN: z.string().optional(), }, /** @@ -52,6 +54,8 @@ export const env = createEnv({ ALLOWED_SCRIPT_PATHS: process.env.ALLOWED_SCRIPT_PATHS, // WebSocket Configuration WEBSOCKET_PORT: process.env.WEBSOCKET_PORT, + // GitHub Configuration + GITHUB_TOKEN: process.env.GITHUB_TOKEN, // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, }, /** diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index 9aa0a70..df33c33 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -2,6 +2,7 @@ import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { readFile } from "fs/promises"; import { join } from "path"; import { spawn } from "child_process"; +import { env } from "~/env"; interface GitHubRelease { tag_name: string; @@ -10,6 +11,21 @@ interface GitHubRelease { html_url: string; } +// Helper function to fetch from GitHub API with optional authentication +async function fetchGitHubAPI(url: string) { + const headers: HeadersInit = { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'ProxmoxVE-Local' + }; + + // Add authentication header if token is available + if (env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${env.GITHUB_TOKEN}`; + } + + return fetch(url, { headers }); +} + export const versionRouter = createTRPCRouter({ // Get current local version getCurrentVersion: publicProcedure @@ -34,7 +50,7 @@ export const versionRouter = createTRPCRouter({ getLatestRelease: publicProcedure .query(async () => { try { - const response = await fetch('https://api.github.com/repos/community-scripts/ProxmoxVE-Local/releases/latest'); + const response = await fetchGitHubAPI('https://api.github.com/repos/community-scripts/ProxmoxVE-Local/releases/latest'); if (!response.ok) { throw new Error(`GitHub API error: ${response.status}`); @@ -70,7 +86,7 @@ export const versionRouter = createTRPCRouter({ const currentVersion = (await readFile(versionPath, 'utf-8')).trim(); - const response = await fetch('https://api.github.com/repos/community-scripts/ProxmoxVE-Local/releases/latest'); + const response = await fetchGitHubAPI('https://api.github.com/repos/community-scripts/ProxmoxVE-Local/releases/latest'); if (!response.ok) { throw new Error(`GitHub API error: ${response.status}`); diff --git a/update.sh b/update.sh index 67c5797..29ec7e2 100755 --- a/update.sh +++ b/update.sh @@ -38,6 +38,17 @@ load_github_token() { 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') @@ -53,7 +64,7 @@ load_github_token() { fi log_warning "No GitHub token found. Using unauthenticated requests (lower rate limits)" - log_warning "To use a token, set GITHUB_TOKEN environment variable or create .github_token file" + log_warning "To use a token, add GITHUB_TOKEN=your_token to .env file or set GITHUB_TOKEN environment variable" return 1 } From 1bb184c81ab6f21e81de8592fb189c0c470404de Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:43:30 +0200 Subject: [PATCH 12/41] Update update.sh --- env.example | 25 ------------------------- src/server/api/routers/version.ts | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 env.example diff --git a/env.example b/env.example deleted file mode 100644 index dadfbfc..0000000 --- a/env.example +++ /dev/null @@ -1,25 +0,0 @@ -# Environment variables for ProxmoxVE-Local -# Copy this file to .env and fill in your values - -# GitHub Personal Access Token for higher API rate limits -# Get your token from: https://github.com/settings/tokens -# Required scope: public_repo (for public repositories) -# This token is used by both the update script and frontend version checking -GITHUB_TOKEN=your_github_token_here - -# Repository Configuration (optional) -# REPO_URL=https://github.com/your-username/your-scripts-repo -# REPO_BRANCH=main -# SCRIPTS_DIRECTORY=scripts -# JSON_FOLDER=json -# ALLOWED_SCRIPT_EXTENSIONS=.sh,.py,.js,.ts,.bash - -# Security Configuration -# MAX_SCRIPT_EXECUTION_TIME=300000 -# ALLOWED_SCRIPT_PATHS=scripts/ - -# WebSocket Configuration -# WEBSOCKET_PORT=3001 - -# Other Configuration -# NODE_ENV=production diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index df33c33..e6090ea 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -20,7 +20,7 @@ async function fetchGitHubAPI(url: string) { // Add authentication header if token is available if (env.GITHUB_TOKEN) { - headers['Authorization'] = `token ${env.GITHUB_TOKEN}`; + headers.Authorization = `token ${env.GITHUB_TOKEN}`; } return fetch(url, { headers }); From db856e34c8705fd385778b17fa912e7d881d62e7 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:47:24 +0200 Subject: [PATCH 13/41] Update update.sh --- update.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/update.sh b/update.sh index 29ec7e2..28eb7a8 100755 --- a/update.sh +++ b/update.sh @@ -528,12 +528,6 @@ update_files() { total_files=$(wc -l < "$file_list") log "Found $total_files files to process" - # Show first few files for debugging - log "First few files to process:" - head -5 "$file_list" | while read -r f; do - log " - $f" - done - while IFS= read -r file; do local rel_path="${file#$actual_source_dir/}" local should_exclude=false @@ -558,9 +552,7 @@ update_files() { return 1 else files_copied=$((files_copied + 1)) - if [ $((files_copied % 10)) -eq 0 ]; then - log "Copied $files_copied files so far..." - fi + fi else files_excluded=$((files_excluded + 1)) @@ -580,6 +572,13 @@ update_files() { install_and_build() { log "Installing dependencies..." + # Remove old node_modules to avoid conflicts with new dependencies + if [ -d "node_modules" ]; then + log "Removing old node_modules directory for clean install..." + rm -rf node_modules + log_success "Old node_modules removed" + fi + # Create temporary file for npm output local npm_log="/tmp/npm_install_$$.log" From d37351e89334291788003a3130e8ed6b0f5d288f Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:51:47 +0200 Subject: [PATCH 14/41] Update update.sh --- update.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 28eb7a8..5ebac46 100755 --- a/update.sh +++ b/update.sh @@ -579,10 +579,27 @@ install_and_build() { log_success "Old node_modules removed" fi + # Remove package-lock.json to avoid version conflicts + if [ -f "package-lock.json" ]; then + log "Removing old package-lock.json to avoid conflicts..." + rm -f package-lock.json + log_success "Old package-lock.json removed" + fi + + # Clear npm cache to ensure fresh downloads + log "Clearing npm cache..." + if npm cache clean --force > /dev/null 2>&1; then + log_success "npm cache cleared" + else + log_warning "Failed to clear npm cache, continuing anyway..." + fi + # Create temporary file for npm output local npm_log="/tmp/npm_install_$$.log" - if ! npm install > "$npm_log" 2>&1; then + # Run npm install with verbose output for debugging + log "Running npm install (this may take a few minutes)..." + if ! npm install --loglevel=verbose > "$npm_log" 2>&1; then log_error "Failed to install dependencies" log_error "npm install output:" cat "$npm_log" | while read -r line; do From 17e4b7684ebd7580f3a0ccc0efd2c2490f8b38db Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 08:59:49 +0200 Subject: [PATCH 15/41] Update update.sh --- update.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/update.sh b/update.sh index 5ebac46..3a831d8 100755 --- a/update.sh +++ b/update.sh @@ -459,29 +459,67 @@ stop_application() { log "Working from application directory: $(pwd)" - # Check if systemd service is running and disable it temporarily + # Check if systemd service is running and stop it if check_service && systemctl is-active --quiet pvescriptslocal.service; then - log "Disabling systemd service temporarily to prevent auto-restart..." + log "Stopping and disabling systemd service..." + systemctl stop pvescriptslocal.service 2>/dev/null || true + sleep 2 if systemctl disable pvescriptslocal.service; then - log_success "Service disabled successfully" + log_success "Service stopped and disabled successfully" else - log_error "Failed to disable service" - return 1 + log_warning "Failed to disable service, but continuing..." fi else log "No running systemd service found" fi - # Kill any remaining npm/node processes + # Kill any remaining npm/node processes related to server.js 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 + + # Find all related processes + local npm_pids node_pids sh_pids + npm_pids=$(pgrep -f "npm start" 2>/dev/null || true) + node_pids=$(pgrep -f "node server.js" 2>/dev/null || true) + sh_pids=$(pgrep -f "sh -c node server.js" 2>/dev/null || true) + + if [ -n "$npm_pids" ] || [ -n "$node_pids" ] || [ -n "$sh_pids" ]; then + log "Found running processes:" + [ -n "$npm_pids" ] && log " npm: $npm_pids" + [ -n "$node_pids" ] && log " node: $node_pids" + [ -n "$sh_pids" ] && log " sh: $sh_pids" + + # Kill node processes first (the actual server) + if [ -n "$node_pids" ]; then + log "Killing node processes..." + echo "$node_pids" | xargs -r kill -9 2>/dev/null || true + fi + + # Kill shell wrapper processes + if [ -n "$sh_pids" ]; then + log "Killing shell wrapper processes..." + echo "$sh_pids" | xargs -r kill -9 2>/dev/null || true + fi + + # Kill npm processes last + if [ -n "$npm_pids" ]; then + log "Killing npm processes..." + echo "$npm_pids" | xargs -r kill -9 2>/dev/null || true + fi + + # Wait and verify they're dead sleep 2 - log_success "Processes killed" + + # Check if any processes are still running + local remaining + remaining=$(pgrep -f "npm start|node server.js|sh -c node server.js" 2>/dev/null || true) + if [ -n "$remaining" ]; then + log_warning "Some processes may still be running: $remaining" + log "Attempting forceful kill..." + echo "$remaining" | xargs -r kill -9 2>/dev/null || true + sleep 1 + fi + + log_success "All processes killed" else log "No running processes found" fi From 5527cdac9f4adf785afcf347a17ba65000678f58 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:02:01 +0200 Subject: [PATCH 16/41] Update update.sh --- update.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/update.sh b/update.sh index 3a831d8..942a79a 100755 --- a/update.sh +++ b/update.sh @@ -462,8 +462,6 @@ stop_application() { # Check if systemd service is running and stop it if check_service && systemctl is-active --quiet pvescriptslocal.service; then log "Stopping and disabling systemd service..." - systemctl stop pvescriptslocal.service 2>/dev/null || true - sleep 2 if systemctl disable pvescriptslocal.service; then log_success "Service stopped and disabled successfully" else From 9eec587b4dbe78cb9675e5a92e9a88a971edfa22 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:09:14 +0200 Subject: [PATCH 17/41] Update update.sh --- src/server/api/routers/version.ts | 7 ++++--- update.sh | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index e6090ea..17d8112 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -131,9 +131,10 @@ export const versionRouter = createTRPCRouter({ try { const updateScriptPath = join(process.cwd(), 'update.sh'); - // Spawn the update script as a detached process using nohup - // This allows it to run independently and kill the parent Node.js process - const child = spawn('nohup', ['bash', updateScriptPath], { + // Use setsid to create a completely independent process session + // This ensures the update script survives when it kills the Node.js process + // setsid creates a new session, making the process immune to parent termination + const child = spawn('setsid', ['bash', updateScriptPath], { cwd: process.cwd(), stdio: ['ignore', 'ignore', 'ignore'], shell: false, diff --git a/update.sh b/update.sh index 942a79a..05ed746 100755 --- a/update.sh +++ b/update.sh @@ -461,7 +461,7 @@ stop_application() { # Check if systemd service is running and stop it if check_service && systemctl is-active --quiet pvescriptslocal.service; then - log "Stopping and disabling systemd service..." + log "Disabling systemd service..." if systemctl disable pvescriptslocal.service; then log_success "Service stopped and disabled successfully" else @@ -780,9 +780,19 @@ main() { # 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 $? + log "Detected running from application directory, creating independent process..." + + # Use setsid if available to create a completely independent session + if command -v setsid &> /dev/null; then + log "Using setsid for true process independence" + setsid bash "$0" --relocated /dev/null 2>&1 & + else + log "setsid not available, using standard background process" + bash "$0" --relocated /dev/null 2>&1 & + fi + + log "Update process started in background, exiting current process..." + exit 0 fi # Ensure we're in the application directory From b27678f53d462293c55424a05d48f62f6fa041c7 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:14:00 +0200 Subject: [PATCH 18/41] Update update.sh --- update.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/update.sh b/update.sh index 05ed746..0b67a81 100755 --- a/update.sh +++ b/update.sh @@ -770,9 +770,11 @@ 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" + # Give the parent process time to exit sleep 3 + init_log + log "Running as detached process (PID: $$)" + log "Parent process has exited, we are now independent" else init_log @@ -782,24 +784,51 @@ main() { if [ -z "${PVE_UPDATE_RELOCATED:-}" ] && [ -f "package.json" ] && [ -f "server.js" ]; then log "Detected running from application directory, creating independent process..." + # Copy script to /tmp to ensure it's not affected by directory operations + local temp_script="/tmp/pve-update-script-$$.sh" + log "Copying update script to: $temp_script" + cp "$0" "$temp_script" + chmod +x "$temp_script" + + # Store the application directory for the relocated script + local app_dir_path="$(pwd)" + # Use setsid if available to create a completely independent session if command -v setsid &> /dev/null; then log "Using setsid for true process independence" - setsid bash "$0" --relocated /dev/null 2>&1 & + setsid bash "$temp_script" --relocated "$app_dir_path" >/tmp/update.log 2>&1 & else log "setsid not available, using standard background process" - bash "$0" --relocated /dev/null 2>&1 & + bash "$temp_script" --relocated "$app_dir_path" >/tmp/update.log 2>&1 & fi - log "Update process started in background, exiting current process..." + local child_pid=$! + log "Update process started in background with PID: $child_pid" + log "Update logs will be written to: /tmp/update.log" + log "Current process exiting to allow update to proceed..." + sleep 1 exit 0 fi # Ensure we're in the application directory local app_dir + # Check if app directory was passed as argument (from relocation) + if [ -n "${2:-}" ]; then + app_dir="$2" + log "Using application directory from argument: $app_dir" + if [ -d "$app_dir" ]; then + cd "$app_dir" || { + log_error "Failed to change to application directory: $app_dir" + exit 1 + } + log "Changed to application directory: $(pwd)" + else + log_error "Provided application directory does not exist: $app_dir" + exit 1 + fi # First check if we're already in the right directory - if [ -f "package.json" ] && [ -f "server.js" ]; then + elif [ -f "package.json" ] && [ -f "server.js" ]; then app_dir="$(pwd)" log "Already in application directory: $app_dir" else @@ -887,6 +916,16 @@ main() { rm -rf "$source_dir" rm -rf "/tmp/pve-update-$$" + # Clean up temporary script copy if it exists + if [ -n "${PVE_UPDATE_RELOCATED:-}" ]; then + local script_path="$0" + if [[ "$script_path" == /tmp/pve-update-script-* ]]; then + log "Scheduling cleanup of temporary script: $script_path" + # Schedule deletion after script exits + (sleep 5 && rm -f "$script_path" 2>/dev/null) & + fi + fi + # Start the application start_application From bf39dcd21aece4902dc832ced0420769ae8a940b Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:14:58 +0200 Subject: [PATCH 19/41] Update update.sh --- src/server/api/routers/version.ts | 7 +- update.sh | 123 +++++------------------------- 2 files changed, 22 insertions(+), 108 deletions(-) diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index 17d8112..e6090ea 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -131,10 +131,9 @@ export const versionRouter = createTRPCRouter({ try { const updateScriptPath = join(process.cwd(), 'update.sh'); - // Use setsid to create a completely independent process session - // This ensures the update script survives when it kills the Node.js process - // setsid creates a new session, making the process immune to parent termination - const child = spawn('setsid', ['bash', updateScriptPath], { + // Spawn the update script as a detached process using nohup + // This allows it to run independently and kill the parent Node.js process + const child = spawn('nohup', ['bash', updateScriptPath], { cwd: process.cwd(), stdio: ['ignore', 'ignore', 'ignore'], shell: false, diff --git a/update.sh b/update.sh index 0b67a81..5ebac46 100755 --- a/update.sh +++ b/update.sh @@ -459,65 +459,29 @@ stop_application() { log "Working from application directory: $(pwd)" - # Check if systemd service is running and stop it + # Check if systemd service is running and disable it temporarily if check_service && systemctl is-active --quiet pvescriptslocal.service; then - log "Disabling systemd service..." + log "Disabling systemd service temporarily to prevent auto-restart..." if systemctl disable pvescriptslocal.service; then - log_success "Service stopped and disabled successfully" + log_success "Service disabled successfully" else - log_warning "Failed to disable service, but continuing..." + log_error "Failed to disable service" + return 1 fi else log "No running systemd service found" fi - # Kill any remaining npm/node processes related to server.js + # Kill any remaining npm/node processes log "Killing any remaining npm/node processes..." - - # Find all related processes - local npm_pids node_pids sh_pids - npm_pids=$(pgrep -f "npm start" 2>/dev/null || true) - node_pids=$(pgrep -f "node server.js" 2>/dev/null || true) - sh_pids=$(pgrep -f "sh -c node server.js" 2>/dev/null || true) - - if [ -n "$npm_pids" ] || [ -n "$node_pids" ] || [ -n "$sh_pids" ]; then - log "Found running processes:" - [ -n "$npm_pids" ] && log " npm: $npm_pids" - [ -n "$node_pids" ] && log " node: $node_pids" - [ -n "$sh_pids" ] && log " sh: $sh_pids" - - # Kill node processes first (the actual server) - if [ -n "$node_pids" ]; then - log "Killing node processes..." - echo "$node_pids" | xargs -r kill -9 2>/dev/null || true - fi - - # Kill shell wrapper processes - if [ -n "$sh_pids" ]; then - log "Killing shell wrapper processes..." - echo "$sh_pids" | xargs -r kill -9 2>/dev/null || true - fi - - # Kill npm processes last - if [ -n "$npm_pids" ]; then - log "Killing npm processes..." - echo "$npm_pids" | xargs -r kill -9 2>/dev/null || true - fi - - # Wait and verify they're dead + 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 - - # Check if any processes are still running - local remaining - remaining=$(pgrep -f "npm start|node server.js|sh -c node server.js" 2>/dev/null || true) - if [ -n "$remaining" ]; then - log_warning "Some processes may still be running: $remaining" - log "Attempting forceful kill..." - echo "$remaining" | xargs -r kill -9 2>/dev/null || true - sleep 1 - fi - - log_success "All processes killed" + log_success "Processes killed" else log "No running processes found" fi @@ -770,11 +734,9 @@ main() { # Check if this is the relocated/detached version first if [ "${1:-}" = "--relocated" ]; then export PVE_UPDATE_RELOCATED=1 - # Give the parent process time to exit - sleep 3 init_log - log "Running as detached process (PID: $$)" - log "Parent process has exited, we are now independent" + log "Running as detached process" + sleep 3 else init_log @@ -782,53 +744,16 @@ main() { # 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, creating independent process..." - - # Copy script to /tmp to ensure it's not affected by directory operations - local temp_script="/tmp/pve-update-script-$$.sh" - log "Copying update script to: $temp_script" - cp "$0" "$temp_script" - chmod +x "$temp_script" - - # Store the application directory for the relocated script - local app_dir_path="$(pwd)" - - # Use setsid if available to create a completely independent session - if command -v setsid &> /dev/null; then - log "Using setsid for true process independence" - setsid bash "$temp_script" --relocated "$app_dir_path" >/tmp/update.log 2>&1 & - else - log "setsid not available, using standard background process" - bash "$temp_script" --relocated "$app_dir_path" >/tmp/update.log 2>&1 & - fi - - local child_pid=$! - log "Update process started in background with PID: $child_pid" - log "Update logs will be written to: /tmp/update.log" - log "Current process exiting to allow update to proceed..." - sleep 1 - exit 0 + log "Detected running from application directory" + bash "$0" --relocated + exit $? fi # Ensure we're in the application directory local app_dir - # Check if app directory was passed as argument (from relocation) - if [ -n "${2:-}" ]; then - app_dir="$2" - log "Using application directory from argument: $app_dir" - if [ -d "$app_dir" ]; then - cd "$app_dir" || { - log_error "Failed to change to application directory: $app_dir" - exit 1 - } - log "Changed to application directory: $(pwd)" - else - log_error "Provided application directory does not exist: $app_dir" - exit 1 - fi # First check if we're already in the right directory - elif [ -f "package.json" ] && [ -f "server.js" ]; then + if [ -f "package.json" ] && [ -f "server.js" ]; then app_dir="$(pwd)" log "Already in application directory: $app_dir" else @@ -916,16 +841,6 @@ main() { rm -rf "$source_dir" rm -rf "/tmp/pve-update-$$" - # Clean up temporary script copy if it exists - if [ -n "${PVE_UPDATE_RELOCATED:-}" ]; then - local script_path="$0" - if [[ "$script_path" == /tmp/pve-update-script-* ]]; then - log "Scheduling cleanup of temporary script: $script_path" - # Schedule deletion after script exits - (sleep 5 && rm -f "$script_path" 2>/dev/null) & - fi - fi - # Start the application start_application From 04aecd643f85abe2a18b163255f0c90d6fdeb60f Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:19:59 +0200 Subject: [PATCH 20/41] Update update.sh --- update.sh | 71 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/update.sh b/update.sh index 5ebac46..5d0494d 100755 --- a/update.sh +++ b/update.sh @@ -492,6 +492,23 @@ update_files() { local source_dir="$1" log "Updating application files..." + log "Source directory: $source_dir" + + # Verify source directory exists and has files + if [ ! -d "$source_dir" ]; then + log_error "Source directory does not exist: $source_dir" + return 1 + fi + + # Check if package.json exists in source + if [ ! -f "$source_dir/package.json" ]; then + log_error "package.json not found in source directory: $source_dir" + log_error "Directory contents:" + ls -la "$source_dir" | head -20 + return 1 + fi + + log_success "Found package.json in source directory" # List of files/directories to exclude from update local exclude_patterns=( @@ -505,31 +522,22 @@ update_files() { "*.bak" ) - # 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 - # Use process substitution instead of pipe to avoid subshell issues local files_copied=0 local files_excluded=0 - log "Starting file copy process from: $actual_source_dir" + log "Starting file copy process from: $source_dir" # 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" + find "$source_dir" -type f > "$file_list" local total_files total_files=$(wc -l < "$file_list") log "Found $total_files files to process" while IFS= read -r file; do - local rel_path="${file#$actual_source_dir/}" + local rel_path="${file#$source_dir/}" local should_exclude=false for pattern in "${exclude_patterns[@]}"; do @@ -556,7 +564,6 @@ update_files() { fi else files_excluded=$((files_excluded + 1)) - log "Excluded: $rel_path" fi done < "$file_list" @@ -565,7 +572,14 @@ update_files() { log "Files processed: $files_copied copied, $files_excluded excluded" + # Verify critical files were copied + if [ ! -f "package.json" ]; then + log_error "package.json was not copied to target directory!" + return 1 + fi + log_success "Application files updated successfully" + log_success "Verified package.json exists in target directory" } # Install dependencies and build @@ -613,6 +627,37 @@ install_and_build() { log_success "Dependencies installed successfully" rm -f "$npm_log" + # Verify critical dependencies are installed + log "Verifying critical dependencies..." + local missing_deps=() + + if [ ! -d "node_modules/@tailwindcss/postcss" ] && [ ! -d "node_modules/@tailwindcss" ]; then + missing_deps+=("@tailwindcss/postcss or @tailwindcss") + fi + + if [ ! -d "node_modules/next" ]; then + missing_deps+=("next") + fi + + if [ ${#missing_deps[@]} -ne 0 ]; then + log_error "Critical dependencies missing after npm install: ${missing_deps[*]}" + log_error "Attempting to install missing dependencies explicitly..." + + # Try installing missing packages explicitly + if ! npm install @tailwindcss/postcss @tailwindcss/cli tailwindcss@next --save-dev > "$npm_log" 2>&1; then + log_error "Failed to install missing Tailwind packages" + cat "$npm_log" | while read -r line; do + log_error "NPM: $line" + done + rm -f "$npm_log" + return 1 + fi + + log_success "Missing dependencies installed" + else + log_success "All critical dependencies verified" + fi + log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production From 7ebfb6b0c9b9f138c6522116f0df4ecc78a233ea Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:22:13 +0200 Subject: [PATCH 21/41] Update update.sh --- update.sh | 71 ++++++++++--------------------------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/update.sh b/update.sh index 5d0494d..5ebac46 100755 --- a/update.sh +++ b/update.sh @@ -492,23 +492,6 @@ update_files() { local source_dir="$1" log "Updating application files..." - log "Source directory: $source_dir" - - # Verify source directory exists and has files - if [ ! -d "$source_dir" ]; then - log_error "Source directory does not exist: $source_dir" - return 1 - fi - - # Check if package.json exists in source - if [ ! -f "$source_dir/package.json" ]; then - log_error "package.json not found in source directory: $source_dir" - log_error "Directory contents:" - ls -la "$source_dir" | head -20 - return 1 - fi - - log_success "Found package.json in source directory" # List of files/directories to exclude from update local exclude_patterns=( @@ -522,22 +505,31 @@ update_files() { "*.bak" ) + # 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 + # Use process substitution instead of pipe to avoid subshell issues local files_copied=0 local files_excluded=0 - log "Starting file copy process from: $source_dir" + log "Starting file copy process from: $actual_source_dir" # Create a temporary file list to avoid process substitution issues local file_list="/tmp/file_list_$$.txt" - find "$source_dir" -type f > "$file_list" + find "$actual_source_dir" -type f > "$file_list" local total_files total_files=$(wc -l < "$file_list") log "Found $total_files files to process" while IFS= read -r file; do - local rel_path="${file#$source_dir/}" + local rel_path="${file#$actual_source_dir/}" local should_exclude=false for pattern in "${exclude_patterns[@]}"; do @@ -564,6 +556,7 @@ update_files() { fi else files_excluded=$((files_excluded + 1)) + log "Excluded: $rel_path" fi done < "$file_list" @@ -572,14 +565,7 @@ update_files() { log "Files processed: $files_copied copied, $files_excluded excluded" - # Verify critical files were copied - if [ ! -f "package.json" ]; then - log_error "package.json was not copied to target directory!" - return 1 - fi - log_success "Application files updated successfully" - log_success "Verified package.json exists in target directory" } # Install dependencies and build @@ -627,37 +613,6 @@ install_and_build() { log_success "Dependencies installed successfully" rm -f "$npm_log" - # Verify critical dependencies are installed - log "Verifying critical dependencies..." - local missing_deps=() - - if [ ! -d "node_modules/@tailwindcss/postcss" ] && [ ! -d "node_modules/@tailwindcss" ]; then - missing_deps+=("@tailwindcss/postcss or @tailwindcss") - fi - - if [ ! -d "node_modules/next" ]; then - missing_deps+=("next") - fi - - if [ ${#missing_deps[@]} -ne 0 ]; then - log_error "Critical dependencies missing after npm install: ${missing_deps[*]}" - log_error "Attempting to install missing dependencies explicitly..." - - # Try installing missing packages explicitly - if ! npm install @tailwindcss/postcss @tailwindcss/cli tailwindcss@next --save-dev > "$npm_log" 2>&1; then - log_error "Failed to install missing Tailwind packages" - cat "$npm_log" | while read -r line; do - log_error "NPM: $line" - done - rm -f "$npm_log" - return 1 - fi - - log_success "Missing dependencies installed" - else - log_success "All critical dependencies verified" - fi - log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production From dbd20bb73b8f42128a498871ad54ed6a9bad9319 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:22:45 +0200 Subject: [PATCH 22/41] Update update.sh --- update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.sh b/update.sh index 5ebac46..d1988a6 100755 --- a/update.sh +++ b/update.sh @@ -596,7 +596,7 @@ install_and_build() { # Create temporary file for npm output local npm_log="/tmp/npm_install_$$.log" - + npm install --loglevel=verbose # Run npm install with verbose output for debugging log "Running npm install (this may take a few minutes)..." if ! npm install --loglevel=verbose > "$npm_log" 2>&1; then From 95bfa98a551ecb6ba9cf1279376126358c059fa4 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:24:23 +0200 Subject: [PATCH 23/41] Update update.sh --- update.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/update.sh b/update.sh index d1988a6..03ace70 100755 --- a/update.sh +++ b/update.sh @@ -596,7 +596,11 @@ install_and_build() { # Create temporary file for npm output local npm_log="/tmp/npm_install_$$.log" - npm install --loglevel=verbose + npm install --loglevel=verbose > "$npm_log" 2>&1 + cat "$npm_log" | while read -r line; do + log_error "NPM: $line" + done + rm -f "$npm_log" # Run npm install with verbose output for debugging log "Running npm install (this may take a few minutes)..." if ! npm install --loglevel=verbose > "$npm_log" 2>&1; then From 74993b5f773e5eab1022f2a3dac0313f610909f9 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:33:23 +0200 Subject: [PATCH 24/41] Update update.sh --- update.sh | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/update.sh b/update.sh index 03ace70..761073f 100755 --- a/update.sh +++ b/update.sh @@ -332,6 +332,17 @@ download_release() { clear_original_directory() { log "Clearing original directory..." + # Remove old lock files and node_modules before update + if [ -f "package-lock.json" ]; then + log "Removing old package-lock.json..." + rm -f package-lock.json + fi + + if [ -d "node_modules" ]; then + log "Removing old node_modules directory..." + rm -rf node_modules + fi + # List of files/directories to preserve (already backed up) local preserve_patterns=( "data" @@ -340,7 +351,6 @@ clear_original_directory() { "update.log" "*.backup" "*.bak" - "node_modules" ".git" ) @@ -572,41 +582,28 @@ update_files() { install_and_build() { log "Installing dependencies..." - # Remove old node_modules to avoid conflicts with new dependencies - if [ -d "node_modules" ]; then - log "Removing old node_modules directory for clean install..." - rm -rf node_modules - log_success "Old node_modules removed" + # Verify package.json exists + if [ ! -f "package.json" ]; then + log_error "package.json not found! Cannot install dependencies." + return 1 fi - # Remove package-lock.json to avoid version conflicts + # Check if package-lock.json exists (it should from the new release) if [ -f "package-lock.json" ]; then - log "Removing old package-lock.json to avoid conflicts..." - rm -f package-lock.json - log_success "Old package-lock.json removed" - fi - - # Clear npm cache to ensure fresh downloads - log "Clearing npm cache..." - if npm cache clean --force > /dev/null 2>&1; then - log_success "npm cache cleared" + log "Using package-lock.json from new release" else - log_warning "Failed to clear npm cache, continuing anyway..." + 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" - npm install --loglevel=verbose > "$npm_log" 2>&1 - cat "$npm_log" | while read -r line; do - log_error "NPM: $line" - done - rm -f "$npm_log" - # Run npm install with verbose output for debugging + + # Run npm install (using the new package.json and package-lock.json from release) log "Running npm install (this may take a few minutes)..." - if ! npm install --loglevel=verbose > "$npm_log" 2>&1; then + if ! npm install > "$npm_log" 2>&1; then log_error "Failed to install dependencies" - log_error "npm install output:" - cat "$npm_log" | while read -r line; do + log_error "npm install output (last 50 lines):" + tail -50 "$npm_log" | while read -r line; do log_error "NPM: $line" done rm -f "$npm_log" @@ -615,6 +612,10 @@ install_and_build() { # Log success and clean up log_success "Dependencies installed successfully" + + # Show package count for verification + local pkg_count=$(find node_modules -maxdepth 1 -type d 2>/dev/null | wc -l) + log "Installed packages: approximately $pkg_count top-level packages" rm -f "$npm_log" log "Building application..." From 9bbab0cc444a258b9c1b97dda44b163dd85a983d Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:35:21 +0200 Subject: [PATCH 25/41] Update update.sh --- update.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/update.sh b/update.sh index 761073f..fc7a6cb 100755 --- a/update.sh +++ b/update.sh @@ -617,6 +617,8 @@ install_and_build() { local pkg_count=$(find node_modules -maxdepth 1 -type d 2>/dev/null | wc -l) log "Installed packages: approximately $pkg_count top-level packages" rm -f "$npm_log" + + npm install log "Building application..." # Set NODE_ENV to production for build From 8a2c864605917f04c7e5e3ac5bacb31f57b2ed17 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:36:32 +0200 Subject: [PATCH 26/41] Update update.sh --- update.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index fc7a6cb..e4dc4f0 100755 --- a/update.sh +++ b/update.sh @@ -618,8 +618,13 @@ install_and_build() { log "Installed packages: approximately $pkg_count top-level packages" rm -f "$npm_log" - npm install - + npm install > "$npm_log" 2>&1 + + cat "$npm_log" | while read -r line; do + log "NPM: $line" + done + rm -f "$npm_log" + log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production From b409a4f1574fdc522c1153b09daf1241098bc262 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:38:58 +0200 Subject: [PATCH 27/41] Update update.sh --- update.sh | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/update.sh b/update.sh index e4dc4f0..73fbd89 100755 --- a/update.sh +++ b/update.sh @@ -524,6 +524,21 @@ update_files() { return 1 fi + # Verify critical files exist in source + log "Verifying source files..." + if [ ! -f "$actual_source_dir/package.json" ]; then + log_error "package.json not found in source directory!" + return 1 + fi + + if [ -f "$actual_source_dir/package-lock.json" ]; then + log "Found package-lock.json in source directory" + local source_pkg_count=$(grep -c "\"node_modules/" "$actual_source_dir/package-lock.json" 2>/dev/null || echo "0") + log "Source package-lock.json contains approximately $source_pkg_count package entries" + else + log_warning "No package-lock.json found in source directory!" + fi + # Use process substitution instead of pipe to avoid subshell issues local files_copied=0 local files_excluded=0 @@ -575,6 +590,21 @@ update_files() { log "Files processed: $files_copied copied, $files_excluded excluded" + # Verify critical files were copied + log "Verifying copied files..." + 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_success "package-lock.json copied successfully" + local target_pkg_count=$(grep -c "\"node_modules/" "package-lock.json" 2>/dev/null || echo "0") + log "Target package-lock.json contains approximately $target_pkg_count package entries" + else + log_warning "package-lock.json was not copied!" + fi + log_success "Application files updated successfully" } @@ -616,15 +646,13 @@ install_and_build() { # Show package count for verification local pkg_count=$(find node_modules -maxdepth 1 -type d 2>/dev/null | wc -l) log "Installed packages: approximately $pkg_count top-level packages" + + # Get audit count for comparison + local audit_count=$(npm ls --depth=0 2>/dev/null | grep -c "├─\|└─" || echo "0") + log "Direct dependencies installed: $audit_count" + rm -f "$npm_log" - - npm install > "$npm_log" 2>&1 - - cat "$npm_log" | while read -r line; do - log "NPM: $line" - done - rm -f "$npm_log" - + log "Building application..." # Set NODE_ENV to production for build export NODE_ENV=production From 1147fcfa77e183e8b9717e71cf7167744f6b3ea6 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:39:36 +0200 Subject: [PATCH 28/41] Update update.sh --- update.sh | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/update.sh b/update.sh index 73fbd89..c50123b 100755 --- a/update.sh +++ b/update.sh @@ -630,12 +630,18 @@ install_and_build() { # Run npm install (using the new package.json and package-lock.json from release) log "Running npm install (this may take a few minutes)..." - if ! npm install > "$npm_log" 2>&1; then - log_error "Failed to install dependencies" - log_error "npm install output (last 50 lines):" - tail -50 "$npm_log" | while read -r line; do - log_error "NPM: $line" - done + npm install > "$npm_log" 2>&1 + local npm_exit_code=$? + + # Always show npm output (last 30 lines) + log "npm install output (last 30 lines):" + tail -30 "$npm_log" | while read -r line; do + log "NPM: $line" + done + + # Check if npm install failed + if [ $npm_exit_code -ne 0 ]; then + log_error "Failed to install dependencies (exit code: $npm_exit_code)" rm -f "$npm_log" return 1 fi From 35b24776af207b932c1d574fc8ba3e7b68736de7 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 09:41:32 +0200 Subject: [PATCH 29/41] Update update.sh --- update.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/update.sh b/update.sh index c50123b..d179f07 100755 --- a/update.sh +++ b/update.sh @@ -621,6 +621,18 @@ install_and_build() { # Check if package-lock.json exists (it should from the new release) if [ -f "package-lock.json" ]; then log "Using package-lock.json from new release" + + # Check lock file version + local lockfile_version=$(grep -m1 '"lockfileVersion"' package-lock.json | grep -o '[0-9]' | head -1) + log "package-lock.json lockfileVersion: $lockfile_version" + + # Check npm version + local npm_version=$(npm --version) + log "npm version: $npm_version" + + # Show file modification time to ensure it wasn't replaced + local lock_mtime=$(stat -c '%y' package-lock.json 2>/dev/null || echo "unknown") + log "package-lock.json last modified: $lock_mtime" else log_warning "No package-lock.json found, npm will generate one" fi @@ -649,6 +661,21 @@ install_and_build() { # Log success and clean up log_success "Dependencies installed successfully" + # Check if package-lock.json was modified by npm + if [ -f "package-lock.json" ]; then + local lock_mtime_after=$(stat -c '%y' package-lock.json 2>/dev/null || echo "unknown") + log "package-lock.json after install: $lock_mtime_after" + + local pkg_entries_after=$(grep -c "\"node_modules/" "package-lock.json" 2>/dev/null || echo "0") + log "package-lock.json now contains approximately $pkg_entries_after package entries" + + # Compare with what we expected (738 from the logs) + if [ "$pkg_entries_after" -lt 500 ]; then + log_warning "Package count seems low! Only $pkg_entries_after entries found." + log_warning "npm likely regenerated the package-lock.json instead of using the one from the release!" + fi + fi + # Show package count for verification local pkg_count=$(find node_modules -maxdepth 1 -type d 2>/dev/null | wc -l) log "Installed packages: approximately $pkg_count top-level packages" From 3aae3b73ede9bc589c24db4180c011da3f688b2d Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:03:54 +0200 Subject: [PATCH 30/41] Update update.sh --- update.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/update.sh b/update.sh index d179f07..92ca7e4 100755 --- a/update.sh +++ b/update.sh @@ -617,7 +617,7 @@ install_and_build() { log_error "package.json not found! Cannot install dependencies." return 1 fi - + log "Current working directory: $(pwd)" # Check if package-lock.json exists (it should from the new release) if [ -f "package-lock.json" ]; then log "Using package-lock.json from new release" @@ -640,11 +640,22 @@ install_and_build() { # Create temporary file for npm output local npm_log="/tmp/npm_install_$$.log" - # Run npm install (using the new package.json and package-lock.json from release) - log "Running npm install (this may take a few minutes)..." - npm install > "$npm_log" 2>&1 + # 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 + log "Running npm install --include=dev (this may take a few minutes)..." + npm install --include=dev > "$npm_log" 2>&1 local npm_exit_code=$? + # Restore NODE_ENV + if [ -n "$old_node_env" ]; then + export NODE_ENV="$old_node_env" + else + unset NODE_ENV + fi + # Always show npm output (last 30 lines) log "npm install output (last 30 lines):" tail -30 "$npm_log" | while read -r line; do From b267157fc6e5b9431c95629522a635e9f363ddf7 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:05:00 +0200 Subject: [PATCH 31/41] Update update.sh --- update.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/update.sh b/update.sh index 92ca7e4..8bfa2cf 100755 --- a/update.sh +++ b/update.sh @@ -617,7 +617,10 @@ install_and_build() { log_error "package.json not found! Cannot install dependencies." return 1 fi + log "Current working directory: $(pwd)" + log "Current NODE_ENV: ${NODE_ENV:-}" + log "Current npm config production: $(npm config get production)" # Check if package-lock.json exists (it should from the new release) if [ -f "package-lock.json" ]; then log "Using package-lock.json from new release" @@ -643,6 +646,7 @@ install_and_build() { # 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 + log "Setting NODE_ENV=development to ensure devDependencies are installed" # Run npm install to get ALL dependencies including devDependencies log "Running npm install --include=dev (this may take a few minutes)..." From b7fad611fe8d117b7b15c6f6e018ad2447b77840 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:12:17 +0200 Subject: [PATCH 32/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 179 +++++++++++++++------- src/server/api/routers/version.ts | 65 +++++++- update.sh | 204 +++---------------------- 3 files changed, 208 insertions(+), 240 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 68deef7..a71111f 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -4,13 +4,26 @@ import { api } from "~/trpc/react"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { ExternalLink, Download, RefreshCw, Loader2 } from "lucide-react"; -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; + +// Loading overlay component with log streaming +function LoadingOverlay({ + isNetworkError = false, + logs = [] +}: { + isNetworkError?: boolean; + logs?: string[]; +}) { + const logsEndRef = useRef(null); + + // Auto-scroll to bottom when new logs arrive + useEffect(() => { + logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [logs]); -// Loading overlay component -function LoadingOverlay({ isNetworkError = false }: { isNetworkError?: boolean }) { return (
-
+
@@ -28,11 +41,24 @@ function LoadingOverlay({ isNetworkError = false }: { isNetworkError?: boolean }

{isNetworkError - ? 'This may take a few moments. The page will reload automatically. You may see a blank page for up to a minute!.' + ? 'This may take a few moments. The page will reload automatically.' : 'The server will restart automatically when complete.' }

+ + {/* Log output */} + {logs.length > 0 && ( +
+ {logs.map((log, index) => ( +
+ {log} +
+ ))} +
+
+ )} +
@@ -48,79 +74,118 @@ export function VersionDisplay() { const { data: versionStatus, isLoading, error } = api.version.getVersionStatus.useQuery(); const [isUpdating, setIsUpdating] = useState(false); const [updateResult, setUpdateResult] = useState<{ success: boolean; message: string } | null>(null); - const [updateStartTime, setUpdateStartTime] = useState(null); const [isNetworkError, setIsNetworkError] = useState(false); + const [updateLogs, setUpdateLogs] = useState([]); + const [shouldSubscribe, setShouldSubscribe] = useState(false); + const lastLogTimeRef = useRef(Date.now()); + const reconnectIntervalRef = useRef(null); const executeUpdate = api.version.executeUpdate.useMutation({ - onSuccess: (result: any) => { - const now = Date.now(); - const elapsed = updateStartTime ? now - updateStartTime : 0; - - + onSuccess: (result) => { setUpdateResult({ success: result.success, message: result.message }); if (result.success) { - // The script now runs independently, so we show a longer overlay - // and wait for the server to restart - setIsNetworkError(true); - setUpdateResult({ success: true, message: 'Update in progress... Server will restart automatically.' }); - - // Wait longer for the update to complete and server to restart - setTimeout(() => { - setIsUpdating(false); - setIsNetworkError(false); - // Try to reload after the update completes - setTimeout(() => { - window.location.reload(); - }, 10000); // 10 seconds to allow for update completion - }, 5000); // Show overlay for 5 seconds + // Start subscribing to update logs + setShouldSubscribe(true); + setUpdateLogs(['Update started...']); } else { - // For errors, show for at least 1 second - const remainingTime = Math.max(0, 1000 - elapsed); - setTimeout(() => { - setIsUpdating(false); - }, remainingTime); + setIsUpdating(false); } }, onError: (error) => { - const now = Date.now(); - const elapsed = updateStartTime ? now - updateStartTime : 0; + setUpdateResult({ success: false, message: error.message }); + setIsUpdating(false); + } + }); + + // Subscribe to update progress + api.version.streamUpdateProgress.useSubscription(undefined, { + enabled: shouldSubscribe, + onData: (data) => { + lastLogTimeRef.current = Date.now(); - // Check if this is a network error (expected during server restart) - const isNetworkError = error.message.includes('Failed to fetch') || - error.message.includes('NetworkError') || - error.message.includes('fetch') || - error.message.includes('network'); + if (data.type === 'log') { + setUpdateLogs(prev => [...prev, data.message]); + } else if (data.type === 'complete') { + setUpdateLogs(prev => [...prev, 'Update complete! Server restarting...']); + setIsNetworkError(true); + } else if (data.type === 'error') { + setUpdateLogs(prev => [...prev, `Error: ${data.message}`]); + } + }, + onError: (error) => { + // Connection lost - likely server restarted + console.log('Update stream connection lost, server likely restarting'); + setIsNetworkError(true); + setUpdateLogs(prev => [...prev, 'Connection lost - server restarting...']); + }, + }); + + // Monitor for server connection loss and auto-reload + useEffect(() => { + if (!shouldSubscribe) return; + + // Check if logs have stopped coming for a while + const checkInterval = setInterval(() => { + const timeSinceLastLog = Date.now() - lastLogTimeRef.current; - if (isNetworkError && elapsed < 60000) { // If it's a network error within 30 seconds, treat as success + // If no logs for 3 seconds and we're updating, assume server is restarting + if (timeSinceLastLog > 3000 && isUpdating) { setIsNetworkError(true); - setUpdateResult({ success: true, message: 'Update in progress... Server is restarting.' }); + setUpdateLogs(prev => [...prev, 'Server restarting... waiting for reconnection...']); - // Wait longer for server to come back up - setTimeout(() => { - setIsUpdating(false); - setIsNetworkError(false); - // Try to reload after a longer delay + // Start trying to reconnect + startReconnectAttempts(); + } + }, 1000); + + return () => clearInterval(checkInterval); + }, [shouldSubscribe, isUpdating]); + + // Attempt to reconnect and reload page when server is back + const startReconnectAttempts = () => { + if (reconnectIntervalRef.current) return; + + setUpdateLogs(prev => [...prev, 'Attempting to reconnect...']); + + reconnectIntervalRef.current = setInterval(async () => { + try { + // Try to fetch the root path to check if server is back + const response = await fetch('/', { method: 'HEAD' }); + if (response.ok || response.status === 200) { + setUpdateLogs(prev => [...prev, 'Server is back online! Reloading...']); + + // Clear interval and reload + if (reconnectIntervalRef.current) { + clearInterval(reconnectIntervalRef.current); + } + setTimeout(() => { window.location.reload(); - }, 5000); - }, 3000); - } else { - // For real errors, show for at least 1 second - setUpdateResult({ success: false, message: error.message }); - const remainingTime = Math.max(0, 1000 - elapsed); - setTimeout(() => { - setIsUpdating(false); - }, remainingTime); + }, 1000); + } + } catch { + // Server still down, keep trying } - } - }); + }, 2000); + }; + + // Cleanup reconnect interval on unmount + useEffect(() => { + return () => { + if (reconnectIntervalRef.current) { + clearInterval(reconnectIntervalRef.current); + } + }; + }, []); const handleUpdate = () => { setIsUpdating(true); setUpdateResult(null); setIsNetworkError(false); - setUpdateStartTime(Date.now()); + setUpdateLogs([]); + setShouldSubscribe(false); + lastLogTimeRef.current = Date.now(); executeUpdate.mutate(); }; @@ -152,7 +217,7 @@ export function VersionDisplay() { return ( <> {/* Loading overlay */} - {isUpdating && } + {isUpdating && }
diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index e6090ea..00867e3 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -1,8 +1,10 @@ import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { readFile } from "fs/promises"; +import { readFile, writeFile } from "fs/promises"; import { join } from "path"; import { spawn } from "child_process"; import { env } from "~/env"; +import { observable } from '@trpc/server/observable'; +import { existsSync, statSync } from "fs"; interface GitHubRelease { tag_name: string; @@ -125,21 +127,78 @@ export const versionRouter = createTRPCRouter({ } }), + // Stream update progress by monitoring the log file + streamUpdateProgress: publicProcedure + .subscription(() => { + return observable<{ type: 'log' | 'complete' | 'error'; message: string }>((emit) => { + const logPath = join(process.cwd(), 'update.log'); + let lastSize = 0; + let checkInterval: NodeJS.Timeout; + + const checkLogFile = async () => { + try { + if (!existsSync(logPath)) { + return; + } + + const stats = statSync(logPath); + const currentSize = stats.size; + + if (currentSize > lastSize) { + // Read only the new content + const fileHandle = await readFile(logPath, 'utf-8'); + const newContent = fileHandle.slice(lastSize); + lastSize = currentSize; + + // Emit new log lines + const lines = newContent.split('\n').filter(line => line.trim()); + for (const line of lines) { + emit.next({ type: 'log', message: line }); + } + } + } catch (error) { + // File might be being written to, ignore errors + } + }; + + // Start monitoring the log file + checkInterval = setInterval(checkLogFile, 500); + + // Also check immediately + checkLogFile().catch(console.error); + + // Cleanup function + return () => { + clearInterval(checkInterval); + }; + }); + }), + // Execute update script executeUpdate: publicProcedure .mutation(async () => { try { const updateScriptPath = join(process.cwd(), 'update.sh'); + const logPath = join(process.cwd(), 'update.log'); + + // Clear/create the log file + await writeFile(logPath, '', 'utf-8'); // Spawn the update script as a detached process using nohup // This allows it to run independently and kill the parent Node.js process - const child = spawn('nohup', ['bash', updateScriptPath], { + // Redirect output to log file + const child = spawn('bash', [updateScriptPath], { cwd: process.cwd(), - stdio: ['ignore', 'ignore', 'ignore'], + stdio: ['ignore', 'pipe', 'pipe'], shell: false, detached: true }); + // Capture stdout and stderr to log file + const logStream = require('fs').createWriteStream(logPath, { flags: 'a' }); + child.stdout?.pipe(logStream); + child.stderr?.pipe(logStream); + // Unref the child process so it doesn't keep the parent alive child.unref(); diff --git a/update.sh b/update.sh index 8bfa2cf..b89606e 100755 --- a/update.sh +++ b/update.sh @@ -225,53 +225,12 @@ download_release() { fi # Download release with timeout and progress - log "Downloading from: $download_url" - log "Target file: $archive_file" - log "Starting curl download..." - - # Test if curl is working - log "Testing curl availability..." - if ! command -v curl >/dev/null 2>&1; then - log_error "curl command not found" + 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 - # Test basic connectivity - log "Testing basic connectivity..." - if ! curl -s --connect-timeout 10 --max-time 30 "https://api.github.com" >/dev/null 2>&1; then - log_error "Cannot reach GitHub API" - rm -rf "$temp_dir" - exit 1 - fi - log_success "Connectivity test passed" - - # Create a temporary file for curl output - local curl_log="/tmp/curl_log_$$.txt" - - # Run curl with verbose output - if curl -L --connect-timeout 30 --max-time 300 --retry 3 --retry-delay 5 -v -o "$archive_file" "$download_url" > "$curl_log" 2>&1; then - log_success "Curl command completed successfully" - # Show some of the curl output for debugging - log "Curl output (first 10 lines):" - head -10 "$curl_log" | while read -r line; do - log "CURL: $line" - done - else - local curl_exit_code=$? - log_error "Curl command failed with exit code: $curl_exit_code" - log_error "Curl output:" - cat "$curl_log" | while read -r line; do - log_error "CURL: $line" - done - rm -f "$curl_log" - rm -rf "$temp_dir" - exit 1 - fi - - # Clean up curl log - rm -f "$curl_log" - # Verify download if [ ! -f "$archive_file" ] || [ ! -s "$archive_file" ]; then log_error "Downloaded file is empty or missing" @@ -279,52 +238,35 @@ download_release() { exit 1 fi - local file_size - file_size=$(stat -c%s "$archive_file" 2>/dev/null || echo "0") - log_success "Downloaded release ($file_size bytes)" + log_success "Downloaded release" # Extract release - log "Extracting release..." - if ! tar -xzf "$archive_file" -C "$temp_dir"; then + 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 - # Debug: List contents after extraction - log "Contents after extraction:" - ls -la "$temp_dir" >&2 || true - # Find the extracted directory (GitHub tarballs have a root directory) - log "Looking for extracted directory with pattern: ${REPO_NAME}-*" local extracted_dir - extracted_dir=$(timeout 10 find "$temp_dir" -maxdepth 1 -type d -name "${REPO_NAME}-*" 2>/dev/null | head -1) - - # If not found with repo name, try alternative patterns - if [ -z "$extracted_dir" ]; then - log "Trying pattern: community-scripts-ProxmoxVE-Local-*" - extracted_dir=$(timeout 10 find "$temp_dir" -maxdepth 1 -type d -name "community-scripts-ProxmoxVE-Local-*" 2>/dev/null | head -1) - fi + 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 - log "Trying pattern: ProxmoxVE-Local-*" - extracted_dir=$(timeout 10 find "$temp_dir" -maxdepth 1 -type d -name "ProxmoxVE-Local-*" 2>/dev/null | head -1) + extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "${REPO_NAME}-*" 2>/dev/null | head -1) fi if [ -z "$extracted_dir" ]; then - log "Trying any directory in temp folder" - extracted_dir=$(timeout 10 find "$temp_dir" -maxdepth 1 -type d ! -name "$temp_dir" 2>/dev/null | head -1) + extracted_dir=$(find "$temp_dir" -maxdepth 1 -type d ! -name "$temp_dir" 2>/dev/null | head -1) fi - # If still not found, error out if [ -z "$extracted_dir" ]; then log_error "Could not find extracted directory" rm -rf "$temp_dir" exit 1 fi - log_success "Found extracted directory: $extracted_dir" - log_success "Release downloaded and extracted successfully" + log_success "Release extracted successfully" echo "$extracted_dir" } @@ -333,15 +275,8 @@ clear_original_directory() { log "Clearing original directory..." # Remove old lock files and node_modules before update - if [ -f "package-lock.json" ]; then - log "Removing old package-lock.json..." - rm -f package-lock.json - fi - - if [ -d "node_modules" ]; then - log "Removing old node_modules directory..." - rm -rf node_modules - fi + 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=( @@ -525,34 +460,19 @@ update_files() { fi # Verify critical files exist in source - log "Verifying source files..." if [ ! -f "$actual_source_dir/package.json" ]; then log_error "package.json not found in source directory!" return 1 fi - if [ -f "$actual_source_dir/package-lock.json" ]; then - log "Found package-lock.json in source directory" - local source_pkg_count=$(grep -c "\"node_modules/" "$actual_source_dir/package-lock.json" 2>/dev/null || echo "0") - log "Source package-lock.json contains approximately $source_pkg_count package entries" - else - log_warning "No package-lock.json found in source directory!" - fi - # Use process substitution instead of pipe to avoid subshell issues local files_copied=0 local files_excluded=0 - log "Starting file copy process from: $actual_source_dir" - # 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" - local total_files - total_files=$(wc -l < "$file_list") - log "Found $total_files files to process" - while IFS= read -r file; do local rel_path="${file#$actual_source_dir/}" local should_exclude=false @@ -575,37 +495,27 @@ update_files() { log_error "Failed to copy $rel_path" rm -f "$file_list" return 1 - else - files_copied=$((files_copied + 1)) - fi + files_copied=$((files_copied + 1)) else files_excluded=$((files_excluded + 1)) - log "Excluded: $rel_path" fi done < "$file_list" # Clean up temporary file rm -f "$file_list" - log "Files processed: $files_copied copied, $files_excluded excluded" - # Verify critical files were copied - log "Verifying copied files..." 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_success "package-lock.json copied successfully" - local target_pkg_count=$(grep -c "\"node_modules/" "package-lock.json" 2>/dev/null || echo "0") - log "Target package-lock.json contains approximately $target_pkg_count package entries" - else + if [ ! -f "package-lock.json" ]; then log_warning "package-lock.json was not copied!" fi - log_success "Application files updated successfully" + log_success "Application files updated successfully ($files_copied files)" } # Install dependencies and build @@ -618,25 +528,7 @@ install_and_build() { return 1 fi - log "Current working directory: $(pwd)" - log "Current NODE_ENV: ${NODE_ENV:-}" - log "Current npm config production: $(npm config get production)" - # Check if package-lock.json exists (it should from the new release) - if [ -f "package-lock.json" ]; then - log "Using package-lock.json from new release" - - # Check lock file version - local lockfile_version=$(grep -m1 '"lockfileVersion"' package-lock.json | grep -o '[0-9]' | head -1) - log "package-lock.json lockfileVersion: $lockfile_version" - - # Check npm version - local npm_version=$(npm --version) - log "npm version: $npm_version" - - # Show file modification time to ensure it wasn't replaced - local lock_mtime=$(stat -c '%y' package-lock.json 2>/dev/null || echo "unknown") - log "package-lock.json last modified: $lock_mtime" - else + if [ ! -f "package-lock.json" ]; then log_warning "No package-lock.json found, npm will generate one" fi @@ -646,12 +538,17 @@ install_and_build() { # 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 - log "Setting NODE_ENV=development to ensure devDependencies are installed" # Run npm install to get ALL dependencies including devDependencies - log "Running npm install --include=dev (this may take a few minutes)..." - npm install --include=dev > "$npm_log" 2>&1 - local npm_exit_code=$? + 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 @@ -660,45 +557,7 @@ install_and_build() { unset NODE_ENV fi - # Always show npm output (last 30 lines) - log "npm install output (last 30 lines):" - tail -30 "$npm_log" | while read -r line; do - log "NPM: $line" - done - - # Check if npm install failed - if [ $npm_exit_code -ne 0 ]; then - log_error "Failed to install dependencies (exit code: $npm_exit_code)" - rm -f "$npm_log" - return 1 - fi - - # Log success and clean up log_success "Dependencies installed successfully" - - # Check if package-lock.json was modified by npm - if [ -f "package-lock.json" ]; then - local lock_mtime_after=$(stat -c '%y' package-lock.json 2>/dev/null || echo "unknown") - log "package-lock.json after install: $lock_mtime_after" - - local pkg_entries_after=$(grep -c "\"node_modules/" "package-lock.json" 2>/dev/null || echo "0") - log "package-lock.json now contains approximately $pkg_entries_after package entries" - - # Compare with what we expected (738 from the logs) - if [ "$pkg_entries_after" -lt 500 ]; then - log_warning "Package count seems low! Only $pkg_entries_after entries found." - log_warning "npm likely regenerated the package-lock.json instead of using the one from the release!" - fi - fi - - # Show package count for verification - local pkg_count=$(find node_modules -maxdepth 1 -type d 2>/dev/null | wc -l) - log "Installed packages: approximately $pkg_count top-level packages" - - # Get audit count for comparison - local audit_count=$(npm ls --depth=0 2>/dev/null | grep -c "├─\|└─" || echo "0") - log "Direct dependencies installed: $audit_count" - rm -f "$npm_log" log "Building application..." @@ -843,7 +702,6 @@ main() { # First check if we're already in the right directory if [ -f "package.json" ] && [ -f "server.js" ]; then app_dir="$(pwd)" - log "Already in application directory: $app_dir" else # Try multiple common locations for search_path in /opt /root /home /usr/local; do @@ -860,10 +718,8 @@ main() { log_error "Failed to change to application directory: $app_dir" exit 1 } - log "Changed to application directory: $(pwd)" else log_error "Could not find application directory" - log "Searched in: /opt, /root, /home, /usr/local" exit 1 fi fi @@ -877,10 +733,8 @@ main() { # Check if service was running before update if check_service && systemctl is-active --quiet pvescriptslocal.service; then SERVICE_WAS_RUNNING=true - log "Service was running before update, will restart it after update" else SERVICE_WAS_RUNNING=false - log "Service was not running before update, will use npm start after update" fi # Get latest release info @@ -896,36 +750,26 @@ main() { # Download and extract release local source_dir source_dir=$(download_release "$release_info") - log "Download completed, source_dir: $source_dir" # Clear the original directory before updating - log "Clearing original directory..." clear_original_directory - log "Original directory cleared successfully" # Update files - log "Starting file update process..." if ! update_files "$source_dir"; then log_error "File update failed, rolling back..." rollback fi - log "File update completed successfully" # Restore .env and data directory before building - log "Restoring backup files..." restore_backup_files - log "Backup files restored successfully" # Install dependencies and build - log "Starting install and build process..." if ! install_and_build; then log_error "Install and build failed, rolling back..." rollback fi - log "Install and build completed successfully" # Cleanup - log "Cleaning up temporary files..." rm -rf "$source_dir" rm -rf "/tmp/pve-update-$$" From ef7afbed80d752b07fba942cdf09d7689ad53aa5 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:14:09 +0200 Subject: [PATCH 33/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 38 ++++++++++++++------------ src/server/api/routers/version.ts | 13 +++++---- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index a71111f..9cb4f40 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -113,7 +113,7 @@ export function VersionDisplay() { setUpdateLogs(prev => [...prev, `Error: ${data.message}`]); } }, - onError: (error) => { + onError: () => { // Connection lost - likely server restarted console.log('Update stream connection lost, server likely restarting'); setIsNetworkError(true); @@ -148,25 +148,27 @@ export function VersionDisplay() { setUpdateLogs(prev => [...prev, 'Attempting to reconnect...']); - reconnectIntervalRef.current = setInterval(async () => { - try { - // Try to fetch the root path to check if server is back - const response = await fetch('/', { method: 'HEAD' }); - if (response.ok || response.status === 200) { - setUpdateLogs(prev => [...prev, 'Server is back online! Reloading...']); - - // Clear interval and reload - if (reconnectIntervalRef.current) { - clearInterval(reconnectIntervalRef.current); + reconnectIntervalRef.current = setInterval(() => { + void (async () => { + try { + // Try to fetch the root path to check if server is back + const response = await fetch('/', { method: 'HEAD' }); + if (response.ok || response.status === 200) { + setUpdateLogs(prev => [...prev, 'Server is back online! Reloading...']); + + // Clear interval and reload + if (reconnectIntervalRef.current) { + clearInterval(reconnectIntervalRef.current); + } + + setTimeout(() => { + window.location.reload(); + }, 1000); } - - setTimeout(() => { - window.location.reload(); - }, 1000); + } catch { + // Server still down, keep trying } - } catch { - // Server still down, keep trying - } + })(); }, 2000); }; diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index 00867e3..aa28969 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -4,7 +4,7 @@ import { join } from "path"; import { spawn } from "child_process"; import { env } from "~/env"; import { observable } from '@trpc/server/observable'; -import { existsSync, statSync } from "fs"; +import { existsSync, statSync, createWriteStream } from "fs"; interface GitHubRelease { tag_name: string; @@ -133,7 +133,6 @@ export const versionRouter = createTRPCRouter({ return observable<{ type: 'log' | 'complete' | 'error'; message: string }>((emit) => { const logPath = join(process.cwd(), 'update.log'); let lastSize = 0; - let checkInterval: NodeJS.Timeout; const checkLogFile = async () => { try { @@ -156,16 +155,18 @@ export const versionRouter = createTRPCRouter({ emit.next({ type: 'log', message: line }); } } - } catch (error) { + } catch { // File might be being written to, ignore errors } }; // Start monitoring the log file - checkInterval = setInterval(checkLogFile, 500); + const checkInterval = setInterval(() => { + void checkLogFile(); + }, 500); // Also check immediately - checkLogFile().catch(console.error); + void checkLogFile(); // Cleanup function return () => { @@ -195,7 +196,7 @@ export const versionRouter = createTRPCRouter({ }); // Capture stdout and stderr to log file - const logStream = require('fs').createWriteStream(logPath, { flags: 'a' }); + const logStream = createWriteStream(logPath, { flags: 'a' }); child.stdout?.pipe(logStream); child.stderr?.pipe(logStream); From d581cd78eec95c412fbd2467782206b6c6a1887d Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:16:37 +0200 Subject: [PATCH 34/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 29 +++++----- src/server/api/routers/version.ts | 80 ++++++++++++-------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 9cb4f40..cc43d10 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -98,28 +98,25 @@ export function VersionDisplay() { } }); - // Subscribe to update progress - api.version.streamUpdateProgress.useSubscription(undefined, { + // Poll for update logs + const { data: updateLogsData, refetch: refetchLogs } = api.version.getUpdateLogs.useQuery(undefined, { enabled: shouldSubscribe, - onData: (data) => { + refetchInterval: 1000, // Poll every second + refetchIntervalInBackground: true, + }); + + // Update logs when data changes + useEffect(() => { + if (updateLogsData?.success && updateLogsData.logs) { lastLogTimeRef.current = Date.now(); + setUpdateLogs(updateLogsData.logs); - if (data.type === 'log') { - setUpdateLogs(prev => [...prev, data.message]); - } else if (data.type === 'complete') { + if (updateLogsData.isComplete) { setUpdateLogs(prev => [...prev, 'Update complete! Server restarting...']); setIsNetworkError(true); - } else if (data.type === 'error') { - setUpdateLogs(prev => [...prev, `Error: ${data.message}`]); } - }, - onError: () => { - // Connection lost - likely server restarted - console.log('Update stream connection lost, server likely restarting'); - setIsNetworkError(true); - setUpdateLogs(prev => [...prev, 'Connection lost - server restarting...']); - }, - }); + } + }, [updateLogsData]); // Monitor for server connection loss and auto-reload useEffect(() => { diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index aa28969..e33d037 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -3,8 +3,7 @@ import { readFile, writeFile } from "fs/promises"; import { join } from "path"; import { spawn } from "child_process"; import { env } from "~/env"; -import { observable } from '@trpc/server/observable'; -import { existsSync, statSync, createWriteStream } from "fs"; +import { existsSync, createWriteStream } from "fs"; interface GitHubRelease { tag_name: string; @@ -127,52 +126,45 @@ export const versionRouter = createTRPCRouter({ } }), - // Stream update progress by monitoring the log file - streamUpdateProgress: publicProcedure - .subscription(() => { - return observable<{ type: 'log' | 'complete' | 'error'; message: string }>((emit) => { + // Get update logs from the log file + getUpdateLogs: publicProcedure + .query(async () => { + try { const logPath = join(process.cwd(), 'update.log'); - let lastSize = 0; - - const checkLogFile = async () => { - try { - if (!existsSync(logPath)) { - return; - } - - const stats = statSync(logPath); - const currentSize = stats.size; - - if (currentSize > lastSize) { - // Read only the new content - const fileHandle = await readFile(logPath, 'utf-8'); - const newContent = fileHandle.slice(lastSize); - lastSize = currentSize; - - // Emit new log lines - const lines = newContent.split('\n').filter(line => line.trim()); - for (const line of lines) { - emit.next({ type: 'log', message: line }); - } - } - } catch { - // File might be being written to, ignore errors - } - }; - - // Start monitoring the log file - const checkInterval = setInterval(() => { - void checkLogFile(); - }, 500); + + if (!existsSync(logPath)) { + return { + success: true, + logs: [], + isComplete: false + }; + } - // Also check immediately - void checkLogFile(); + const logs = await readFile(logPath, 'utf-8'); + const logLines = logs.split('\n').filter(line => line.trim()); + + // Check if update is complete by looking for completion indicators + const isComplete = logLines.some(line => + line.includes('Update complete') || + line.includes('Server restarting') || + line.includes('npm start') || + line.includes('Restarting server') + ); - // Cleanup function - return () => { - clearInterval(checkInterval); + return { + success: true, + logs: logLines, + isComplete }; - }); + } catch (error) { + console.error('Error reading update logs:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to read update logs', + logs: [], + isComplete: false + }; + } }), // Execute update script From c3a327b87c7ed951313ccef41123f187e63b48af Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:19:32 +0200 Subject: [PATCH 35/41] Update update.sh --- src/server/api/routers/version.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index e33d037..f419b2e 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -4,6 +4,7 @@ import { join } from "path"; import { spawn } from "child_process"; import { env } from "~/env"; import { existsSync, createWriteStream } from "fs"; +import stripAnsi from "strip-ansi"; interface GitHubRelease { tag_name: string; @@ -141,7 +142,9 @@ export const versionRouter = createTRPCRouter({ } const logs = await readFile(logPath, 'utf-8'); - const logLines = logs.split('\n').filter(line => line.trim()); + const logLines = logs.split('\n') + .filter(line => line.trim()) + .map(line => stripAnsi(line)); // Strip ANSI color codes // Check if update is complete by looking for completion indicators const isComplete = logLines.some(line => From 50c36015e2d127c39fe2e43ea4a47e2327e5e30a Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:23:18 +0200 Subject: [PATCH 36/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 16 +++++++++++----- src/server/api/routers/version.ts | 8 +++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index cc43d10..170e2e6 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -77,6 +77,7 @@ export function VersionDisplay() { const [isNetworkError, setIsNetworkError] = useState(false); const [updateLogs, setUpdateLogs] = useState([]); const [shouldSubscribe, setShouldSubscribe] = useState(false); + const [updateStartTime, setUpdateStartTime] = useState(null); const lastLogTimeRef = useRef(Date.now()); const reconnectIntervalRef = useRef(null); @@ -99,7 +100,7 @@ export function VersionDisplay() { }); // Poll for update logs - const { data: updateLogsData, refetch: refetchLogs } = api.version.getUpdateLogs.useQuery(undefined, { + const { data: updateLogsData } = api.version.getUpdateLogs.useQuery(undefined, { enabled: shouldSubscribe, refetchInterval: 1000, // Poll every second refetchIntervalInBackground: true, @@ -126,18 +127,22 @@ export function VersionDisplay() { const checkInterval = setInterval(() => { const timeSinceLastLog = Date.now() - lastLogTimeRef.current; - // If no logs for 3 seconds and we're updating, assume server is restarting - if (timeSinceLastLog > 3000 && isUpdating) { + // Only start reconnection if we've been updating for at least 30 seconds + // and no logs for 10 seconds (to avoid premature reconnection during npm install) + const hasBeenUpdatingLongEnough = updateStartTime && (Date.now() - updateStartTime) > 60000; + const noLogsForAWhile = timeSinceLastLog > 40000; + + if (hasBeenUpdatingLongEnough && noLogsForAWhile && isUpdating) { setIsNetworkError(true); setUpdateLogs(prev => [...prev, 'Server restarting... waiting for reconnection...']); // Start trying to reconnect startReconnectAttempts(); } - }, 1000); + }, 1500); // Check every 2 seconds instead of 1 return () => clearInterval(checkInterval); - }, [shouldSubscribe, isUpdating]); + }, [shouldSubscribe, isUpdating, updateStartTime]); // Attempt to reconnect and reload page when server is back const startReconnectAttempts = () => { @@ -184,6 +189,7 @@ export function VersionDisplay() { setIsNetworkError(false); setUpdateLogs([]); setShouldSubscribe(false); + setUpdateStartTime(Date.now()); lastLogTimeRef.current = Date.now(); executeUpdate.mutate(); }; diff --git a/src/server/api/routers/version.ts b/src/server/api/routers/version.ts index f419b2e..3455d60 100644 --- a/src/server/api/routers/version.ts +++ b/src/server/api/routers/version.ts @@ -151,7 +151,13 @@ export const versionRouter = createTRPCRouter({ line.includes('Update complete') || line.includes('Server restarting') || line.includes('npm start') || - line.includes('Restarting server') + line.includes('Restarting server') || + line.includes('Server started') || + line.includes('Ready on http') || + line.includes('Application started') || + line.includes('Service enabled and started successfully') || + line.includes('Service is running') || + line.includes('Update completed successfully') ); return { From 36ead1bb856f85aaf426c1fbc1f6d0551e418264 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:24:38 +0200 Subject: [PATCH 37/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 170e2e6..125f6b0 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -23,23 +23,23 @@ function LoadingOverlay({ return (
-
+
- -
+ +
-

+

{isNetworkError ? 'Server Restarting' : 'Updating Application'}

-

+

{isNetworkError ? 'The server is restarting after the update...' : 'Please stand by while we update your application...' }

-

+

{isNetworkError ? 'This may take a few moments. The page will reload automatically.' : 'The server will restart automatically when complete.' @@ -49,7 +49,7 @@ function LoadingOverlay({ {/* Log output */} {logs.length > 0 && ( -

+
{logs.map((log, index) => (
{log} @@ -60,9 +60,9 @@ function LoadingOverlay({ )}
-
-
-
+
+
+
@@ -283,8 +283,8 @@ export function VersionDisplay() { {updateResult && (
{updateResult.message}
@@ -293,7 +293,7 @@ export function VersionDisplay() { )} {isUpToDate && ( - + ✓ Up to date )} From 37ce598f80993158a89804068430599e14e8fb62 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:27:09 +0200 Subject: [PATCH 38/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 125f6b0..249a2ec 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -115,34 +115,37 @@ export function VersionDisplay() { if (updateLogsData.isComplete) { setUpdateLogs(prev => [...prev, 'Update complete! Server restarting...']); setIsNetworkError(true); + // Start reconnection attempts when we know update is complete + startReconnectAttempts(); } } }, [updateLogsData]); - // Monitor for server connection loss and auto-reload + // Monitor for server connection loss and auto-reload (fallback only) useEffect(() => { if (!shouldSubscribe) return; - // Check if logs have stopped coming for a while + // Only use this as a fallback - the main trigger should be completion detection const checkInterval = setInterval(() => { const timeSinceLastLog = Date.now() - lastLogTimeRef.current; - // Only start reconnection if we've been updating for at least 30 seconds - // and no logs for 10 seconds (to avoid premature reconnection during npm install) - const hasBeenUpdatingLongEnough = updateStartTime && (Date.now() - updateStartTime) > 60000; - const noLogsForAWhile = timeSinceLastLog > 40000; + // Only start reconnection if we've been updating for at least 3 minutes + // and no logs for 60 seconds (very conservative fallback) + const hasBeenUpdatingLongEnough = updateStartTime && (Date.now() - updateStartTime) > 180000; // 3 minutes + const noLogsForAWhile = timeSinceLastLog > 60000; // 60 seconds - if (hasBeenUpdatingLongEnough && noLogsForAWhile && isUpdating) { + if (hasBeenUpdatingLongEnough && noLogsForAWhile && isUpdating && !isNetworkError) { + console.log('Fallback: Assuming server restart due to long silence'); setIsNetworkError(true); setUpdateLogs(prev => [...prev, 'Server restarting... waiting for reconnection...']); // Start trying to reconnect startReconnectAttempts(); } - }, 1500); // Check every 2 seconds instead of 1 + }, 10000); // Check every 10 seconds return () => clearInterval(checkInterval); - }, [shouldSubscribe, isUpdating, updateStartTime]); + }, [shouldSubscribe, isUpdating, updateStartTime, isNetworkError]); // Attempt to reconnect and reload page when server is back const startReconnectAttempts = () => { From 77398b5958874b76bb003d24789ea5edd7bef3dc Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:27:58 +0200 Subject: [PATCH 39/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 249a2ec..6a86bb3 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -23,23 +23,23 @@ function LoadingOverlay({ return (
-
+
-

+

{isNetworkError ? 'Server Restarting' : 'Updating Application'}

-

+

{isNetworkError ? 'The server is restarting after the update...' : 'Please stand by while we update your application...' }

-

+

{isNetworkError ? 'This may take a few moments. The page will reload automatically.' : 'The server will restart automatically when complete.' @@ -49,7 +49,7 @@ function LoadingOverlay({ {/* Log output */} {logs.length > 0 && ( -

+
{logs.map((log, index) => (
{log} From 2fb46615b88ce0311f7c1008660254b9dadfdd29 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:30:59 +0200 Subject: [PATCH 40/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 10 +++++----- src/styles/globals.css | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 6a86bb3..249a2ec 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -23,23 +23,23 @@ function LoadingOverlay({ return (
-
+
-

+

{isNetworkError ? 'Server Restarting' : 'Updating Application'}

-

+

{isNetworkError ? 'The server is restarting after the update...' : 'Please stand by while we update your application...' }

-

+

{isNetworkError ? 'This may take a few moments. The page will reload automatically.' : 'The server will restart automatically when complete.' @@ -49,7 +49,7 @@ function LoadingOverlay({ {/* Log output */} {logs.length > 0 && ( -

+
{logs.map((log, index) => (
{log} diff --git a/src/styles/globals.css b/src/styles/globals.css index 40b5937..a68b83e 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -65,7 +65,7 @@ /* Semantic color utility classes */ .bg-background { background-color: hsl(var(--background)); } .text-foreground { color: hsl(var(--foreground)); } -.bg-card { background-color: hsl(var(--card)); } +.bg-card { background-color: hsl(var(--card)) !important; } .text-card-foreground { color: hsl(var(--card-foreground)); } .bg-popover { background-color: hsl(var(--popover)); } .text-popover-foreground { color: hsl(var(--popover-foreground)); } From c5e1e7cd21a5f017991546a46c4d5c5b08e50668 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Wed, 8 Oct 2025 10:38:31 +0200 Subject: [PATCH 41/41] Update update.sh --- src/app/_components/VersionDisplay.tsx | 2 +- update.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/_components/VersionDisplay.tsx b/src/app/_components/VersionDisplay.tsx index 249a2ec..f2afdc8 100644 --- a/src/app/_components/VersionDisplay.tsx +++ b/src/app/_components/VersionDisplay.tsx @@ -241,7 +241,7 @@ export function VersionDisplay() {
How to update:
-
Click the button to update
+
Click the button to update, when installed via the helper script
or update manually:
cd $PVESCRIPTLOCAL_DIR
git pull
diff --git a/update.sh b/update.sh index b89606e..1d41b32 100755 --- a/update.sh +++ b/update.sh @@ -382,7 +382,7 @@ check_service() { # Stop the application before updating stop_application() { - log "Stopping application before update..." + # Change to the application directory if we're not already there local app_dir