#!/bin/bash # Pulse for Proxmox VE LXC Installation Script # This script automates the installation and setup of Pulse within a Proxmox LXC container. # --- Configuration --- NODE_MAJOR_VERSION=20 # Specify the desired Node.js major version (e.g., 18, 20) PULSE_DIR="/opt/pulse-proxmox" PULSE_USER="pulse" # Dedicated user to run Pulse SERVICE_NAME="pulse-monitor.service" SCRIPT_NAME="install-pulse.sh" # Used for cron job identification LOG_FILE="/var/log/pulse_update.log" # Log file for cron updates SCRIPT_ABS_PATH="" # Store absolute path of the script here # --- Flags --- MODE_UPDATE=false # Flag to run in non-interactive update mode INSTALL_MODE="" # Stores the determined action: install, update, remove, cancel, error # --- Argument Parsing --- while [[ "$#" -gt 0 ]]; do case $1 in --update) MODE_UPDATE=true ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift done # --- Helper Functions --- print_info() { echo -e "\033[1;34m[INFO]\033[0m $1" } print_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1" } print_warning() { echo -e "\033[1;33m[WARNING]\033[0m $1" } print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1" >&2 } check_root() { if [ "$(id -u)" -ne 0 ]; then print_error "This script must be run as root. Please use sudo." exit 1 fi } # --- Installation Status Check --- Function moved up check_installation_status() { print_info "Checking Pulse installation directory $PULSE_DIR..." if [ -d "$PULSE_DIR" ]; then # Check if it's a git repository if [ -d "$PULSE_DIR/.git" ]; then # If --update flag is set, determine mode is 'update' if [ "$MODE_UPDATE" = true ]; then print_info "Running in non-interactive update mode..." INSTALL_MODE="update" return 0 # Continue to main update logic fi # Otherwise, show interactive menu print_warning "Pulse seems to be already installed in $PULSE_DIR." echo "Choose an action:" echo " 1) Update Pulse to the latest version" echo " 2) Remove Pulse" echo " 3) Cancel installation" read -p "Enter your choice (1-3): " user_choice case $user_choice in 1) INSTALL_MODE="update" ;; 2) INSTALL_MODE="remove" ;; 3) INSTALL_MODE="cancel" ;; *) print_error "Invalid choice."; INSTALL_MODE="error" ;; esac else # Directory exists but doesn't seem to be a git repository print_error "Directory $PULSE_DIR exists but does not appear to be a valid Pulse git repository." print_error "Please remove this directory manually or choose a different installation path and re-run the script." INSTALL_MODE="error" fi else # Directory doesn't exist, proceed with cloning INSTALL_MODE="install" fi return 0 # Return success, INSTALL_MODE holds the result } # --- System Setup Functions --- (apt_update_upgrade, install_dependencies, setup_node, create_pulse_user) # ... (These functions remain unchanged, but are called later) ... apt_update_upgrade() { print_info "Updating package lists and upgrading packages..." if apt-get update > /dev/null && apt-get upgrade -y > /dev/null; then print_success "System packages updated and upgraded." else print_error "Failed to update/upgrade system packages." exit 1 fi } install_dependencies() { print_info "Installing necessary dependencies (git, curl, sudo, gpg)..." if apt-get install -y git curl sudo gpg > /dev/null; then print_success "Dependencies installed." else print_error "Failed to install dependencies." exit 1 fi } setup_node() { print_info "Setting up Node.js repository (NodeSource)..." # Check if Node.js is already installed and meets version requirement (optional, for robustness) # node_version=$(node -v 2>/dev/null | sed 's/v//' | cut -d. -f1) # if [[ "$node_version" -ge "$NODE_MAJOR_VERSION" ]]; then # print_info "Node.js version ${node_version} already installed and meets requirement (>=${NODE_MAJOR_VERSION}). Skipping setup." # return 0 # fi if ! command -v curl &> /dev/null; then print_error "curl is required but not found. Please install it first." exit 1 fi # Add NodeSource repository GPG key and setup script KEYRING_DIR="/usr/share/keyrings" KEYRING_FILE="$KEYRING_DIR/nodesource.gpg" if [ ! -d "$KEYRING_DIR" ]; then mkdir -p "$KEYRING_DIR" || { print_error "Failed to create $KEYRING_DIR"; exit 1; } fi print_info "Downloading/refreshing NodeSource GPG key..." curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --yes --dearmor -o "$KEYRING_FILE" if [ $? -ne 0 ]; then print_error "Failed to download or process NodeSource GPG key." rm -f "$KEYRING_FILE" # Clean up partial file exit 1 fi # Add the repository configuration echo "deb [signed-by=$KEYRING_FILE] https://deb.nodesource.com/node_$NODE_MAJOR_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null if [ $? -ne 0 ]; then print_error "Failed to add NodeSource repository to sources list." exit 1 fi print_info "Updating package list after adding NodeSource repository..." if ! apt-get update > /dev/null; then print_error "Failed to update package list after adding NodeSource repository." # Attempt cleanup rm -f /etc/apt/sources.list.d/nodesource.list "$KEYRING_FILE" exit 1 fi print_info "Installing Node.js ${NODE_MAJOR_VERSION}.x..." if apt-get install nodejs -y > /dev/null; then print_success "Node.js ${NODE_MAJOR_VERSION}.x installed successfully." print_info "Node version: $(node -v)" print_info "npm version: $(npm -v)" else print_error "Failed to install Node.js." # Attempt cleanup rm -f /etc/apt/sources.list.d/nodesource.list "$KEYRING_FILE" exit 1 fi } create_pulse_user() { print_info "Creating dedicated user '$PULSE_USER'..." if id "$PULSE_USER" &>/dev/null; then print_warning "User '$PULSE_USER' already exists. Skipping creation." else # Create a system user with no login shell and no home directory (or specify one if needed) useradd -r -s /bin/false "$PULSE_USER" if [ $? -eq 0 ]; then print_success "User '$PULSE_USER' created successfully." else print_error "Failed to create user '$PULSE_USER'." # Decide if this is fatal. Maybe allow script to continue if user exists? # For now, exiting as it might indicate a problem. exit 1 fi fi } # --- Core Action Functions --- (perform_update, perform_remove) # ... (These functions remain mostly unchanged) ... perform_update() { print_info "Attempting to update Pulse..." cd "$PULSE_DIR" || { print_error "Failed to change directory to $PULSE_DIR"; return 1; } # Add safe directory config for root user, just in case git config --global --add safe.directory "$PULSE_DIR" > /dev/null 2>&1 || print_warning "Could not configure safe.directory for root user." print_info "Fetching latest changes from git (running as user $PULSE_USER)..." if ! sudo -u "$PULSE_USER" git fetch origin; then print_error "Failed to fetch latest changes from git." cd .. return 1 fi print_info "Resetting local repository to match remote 'main' branch..." # Reset local main to exactly match the fetched origin/main, discarding local changes/commits if ! sudo -u "$PULSE_USER" git reset --hard origin/main; then print_error "Failed to reset local repository to origin/main." cd .. return 1 fi print_info "Cleaning repository (removing untracked files)..." # Remove untracked files and directories to ensure a clean state # Use -f for files, -d for directories. Do NOT use -x which removes ignored files like .env if ! sudo -u "$PULSE_USER" git clean -fd; then print_warning "Failed to clean untracked files from the repository." # Continue anyway, as the core update (reset) succeeded fi print_info "Re-installing npm dependencies (root)..." if ! npm install --omit=dev --unsafe-perm > /dev/null 2>&1; then print_warning "Failed to install root npm dependencies during update. Continuing..." # Decide if this is fatal or just a warning else print_success "Root dependencies updated." fi print_info "Re-installing server dependencies..." cd server || { print_error "Failed to change directory to $PULSE_DIR/server"; cd ..; return 1; } if npm install --omit=dev --unsafe-perm > /dev/null 2>&1; then print_success "Server dependencies updated." else print_error "Failed to install server npm dependencies." exit 1 # Exit if server deps fail fi cd .. # Back to PULSE_DIR set_permissions # Ensure permissions are correct after update # Ensure the systemd service is configured correctly before restarting print_info "Ensuring systemd service ($SERVICE_NAME) is configured..." if ! setup_systemd_service true; then # Pass true to indicate update mode (skip start/enable) print_error "Failed to configure systemd service during update." # Decide if this should be fatal, likely yes as restart will fail return 1 fi print_info "Restarting Pulse service ($SERVICE_NAME)..." if systemctl restart "$SERVICE_NAME"; then print_success "Pulse service restarted." else print_error "Failed to restart Pulse service. Check service status manually: systemctl status $SERVICE_NAME" return 1 fi print_success "Pulse updated successfully!" return 0 } perform_remove() { print_warning "This will stop and disable the Pulse service(s) and remove the installation directory ($PULSE_DIR)." # Allow non-interactive removal if called directly with logic before # For now, keep interactive confirmation here local remove_confirm="" if [ -t 0 ]; then # Check if stdin is a terminal for interactive prompt read -p "Are you sure you want to remove Pulse? (y/N): " remove_confirm if [[ ! "$remove_confirm" =~ ^[Yy]$ ]]; then print_info "Removal cancelled." return 1 # Indicate cancellation fi else print_warning "Running non-interactively. Proceeding with removal..." fi # List of potential service names to remove local potential_services=("pulse-monitor.service" "pulse-proxmox.service") local service_removed=false for service_name in "${potential_services[@]}"; do local service_file_path="/etc/systemd/system/$service_name" if systemctl list-units --full -all | grep -q "$service_name"; then print_info "Stopping service ($service_name)..." systemctl stop "$service_name" > /dev/null 2>&1 # Ignore errors if already stopped print_info "Disabling service ($service_name)..." systemctl disable "$service_name" > /dev/null 2>&1 # Ignore errors if already disabled if [ -f "$service_file_path" ]; then print_info "Removing systemd service file ($service_file_path)..." rm -f "$service_file_path" if [ $? -eq 0 ]; then print_success "Service file $service_file_path removed." service_removed=true else print_warning "Failed to remove service file $service_file_path. Please remove it manually." fi else print_info "Service file $service_file_path not found, skipping removal." fi else print_info "Service $service_name not found, skipping stop/disable." # Also check if the file exists even if service isn't loaded if [ -f "$service_file_path" ]; then print_info "Removing orphaned systemd service file ($service_file_path)..." rm -f "$service_file_path" if [ $? -eq 0 ]; then print_success "Orphaned service file $service_file_path removed." service_removed=true else print_warning "Failed to remove orphaned service file $service_file_path. Please remove it manually." fi fi fi done # Reload systemd daemon if any service file was removed if [ "$service_removed" = true ]; then print_info "Reloading systemd daemon..." systemctl daemon-reload fi print_info "Removing Pulse installation directory ($PULSE_DIR)..." if rm -rf "$PULSE_DIR"; then print_success "Installation directory removed." else print_error "Failed to remove installation directory $PULSE_DIR. Please remove it manually." return 1 # Indicate error fi print_success "Pulse removed successfully." return 0 } # --- Installation Step Functions --- (install_npm_deps, set_permissions, configure_environment, setup_systemd_service) # ... (These functions remain mostly unchanged, but setup_systemd_service might need slight modification) ... install_npm_deps() { print_info "Installing npm dependencies..." if [ ! -d "$PULSE_DIR" ]; then print_error "Pulse directory $PULSE_DIR not found. Cannot install dependencies." # This shouldn't happen if called in the right flow, but check anyway return 1 fi cd "$PULSE_DIR" || { print_error "Failed to change directory to $PULSE_DIR"; return 1; } print_info "Installing root dependencies..." # Use --unsafe-perm if running npm install as root, which might be necessary for some packages # Use --omit=dev to skip development dependencies if npm install --omit=dev --unsafe-perm > /dev/null 2>&1; then print_success "Root dependencies installed." else print_error "Failed to install root npm dependencies." return 1 fi print_info "Installing server dependencies..." cd server || { print_error "Failed to change directory to $PULSE_DIR/server"; cd ..; return 1; } if npm install --omit=dev --unsafe-perm > /dev/null 2>&1; then print_success "Server dependencies installed." else print_error "Failed to install server npm dependencies." cd .. # Go back before returning error return 1 fi # Return to script execution directory or root, if needed cd .. return 0 } set_permissions() { print_info "Setting permissions for $PULSE_DIR..." if chown -R "$PULSE_USER":"$PULSE_USER" "$PULSE_DIR"; then print_success "Permissions set correctly." return 0 else print_error "Failed to set permissions for $PULSE_DIR." # This might not be fatal, but could cause runtime issues. Log warning? print_warning "Check ownership and permissions for $PULSE_DIR." return 1 # Indicate potential problem fi } configure_environment() { print_info "Configuring Pulse environment..." local env_example_path="$PULSE_DIR/.env.example" local env_path="$PULSE_DIR/.env" if [ ! -f "$env_example_path" ]; then print_error "Environment example file not found at $env_example_path. Cannot configure." return 1 fi # Check if .env already exists if [ -f "$env_path" ]; then print_warning "Configuration file $env_path already exists." # Only prompt if interactive if [ -t 0 ]; then read -p "Overwrite existing configuration? (y/N): " overwrite_confirm if [[ ! "$overwrite_confirm" =~ ^[Yy]$ ]]; then print_info "Skipping environment configuration." return 0 # Exit the function successfully without configuring fi print_info "Proceeding to overwrite existing configuration..." else print_info "Running non-interactively, skipping environment configuration as file exists." return 0 fi fi # --- Gather Proxmox Details (Only if interactive) --- local proxmox_host="" local proxmox_token_id="" local proxmox_token_secret="" local allow_self_signed="y" local pulse_port="" if [ -t 0 ]; then echo "Please provide your Proxmox connection details:" read -p " -> Proxmox Host URL (e.g., https://192.168.1.100:8006): " proxmox_host while [ -z "$proxmox_host" ]; do print_warning "Proxmox Host URL cannot be empty." read -p " -> Proxmox Host URL (e.g., https://192.168.1.100:8006): " proxmox_host done # Validate and potentially prepend https:// if [[ ! "$proxmox_host" =~ ^https?:// ]]; then print_warning "URL does not start with http:// or https://. Prepending https://." proxmox_host="https://$proxmox_host" print_info "Using Proxmox Host URL: $proxmox_host" fi # --- Display Token Generation Info --- (Remains the same) # ... (token info display code) ... echo "" print_info "You need a Proxmox API Token. You can create one via the Proxmox Web UI," print_info "or run the following commands on your Proxmox host shell:" echo "----------------------------------------------------------------------" echo ' # 1. Create user 'pulse-monitor' (enter password when prompted):' echo " pveum useradd pulse-monitor@pam -comment "API user for Pulse monitoring"" echo ' ' echo ' # 2. Create API token 'pulse' for user (COPY THE SECRET VALUE!):' echo " pveum user token add pulse-monitor@pam pulse --privsep=1" echo ' ' echo ' # 3. Assign PVEAuditor role to user:' echo " pveum acl modify / -user pulse-monitor@pam -role PVEAuditor" echo "----------------------------------------------------------------------" echo "After running the 'token add' command, copy the Token ID and Secret Value" echo "and paste them below." echo "" read -p " -> Proxmox API Token ID (e.g., user@pam!tokenid): " proxmox_token_id while [ -z "$proxmox_token_id" ]; do print_warning "Proxmox Token ID cannot be empty." read -p " -> Proxmox API Token ID (e.g., user@pam!tokenid): " proxmox_token_id done read -sp " -> Proxmox API Token Secret: " proxmox_token_secret echo while [ -z "$proxmox_token_secret" ]; do print_warning "Proxmox Token Secret cannot be empty." read -sp " -> Proxmox API Token Secret: " proxmox_token_secret echo done # --- Optional Settings --- (Remains the same) read -p "Allow self-signed certificates for Proxmox? (Y/n): " allow_self_signed read -p "Port for Pulse server (leave blank for default 7655): " pulse_port else print_warning "Running non-interactively. Cannot prompt for environment details." print_warning "Ensure $env_path is configured manually or exists from a previous run." # Use defaults or skip creation if running non-interactively? # For now, just skip the interactive part and proceed to copy/sed if file doesn't exist # or if overwrite was forced (though non-interactive won't force) if [ -f "$env_path" ]; then return 0; fi # Skip if file exists and non-interactive fi # Determine values (use defaults if not set interactively) local self_signed_value="true" if [[ "$allow_self_signed" =~ ^[Nn]$ ]]; then self_signed_value="false"; fi local port_value="7655" if [ -n "$pulse_port" ]; then if [[ "$pulse_port" =~ ^[0-9]+$ ]] && [ "$pulse_port" -ge 1 ] && [ "$pulse_port" -le 65535 ]; then port_value="$pulse_port" else print_warning "Invalid port number entered. Using default 7655." fi fi # --- Create .env file --- (Handle case where variables might be empty if non-interactive) print_info "Creating $env_path from example..." if cp "$env_example_path" "$env_path"; then # Only run sed if variables were set (i.e., interactive mode ran) if [ -n "$proxmox_host" ]; then sed -i "s|^PROXMOX_HOST=.*|PROXMOX_HOST=$proxmox_host|" "$env_path" sed -i "s|^PROXMOX_TOKEN_ID=.*|PROXMOX_TOKEN_ID=$proxmox_token_id|" "$env_path" sed -i "s|^PROXMOX_TOKEN_SECRET=.*|PROXMOX_TOKEN_SECRET=$proxmox_token_secret|" "$env_path" sed -i "s|^PROXMOX_ALLOW_SELF_SIGNED_CERTS=.*|PROXMOX_ALLOW_SELF_SIGNED_CERTS=$self_signed_value|" "$env_path" sed -i "s|^PORT=.*|PORT=$port_value|" "$env_path" else print_warning "Skipping variable substitution in .env as no values were provided (non-interactive?)." print_warning "Please edit $env_path manually." fi # Set ownership & permissions regardless chown "$PULSE_USER":"$PULSE_USER" "$env_path" chmod 600 "$env_path" print_success "Environment file created/updated in $env_path." return 0 else print_error "Failed to copy $env_example_path to $env_path." return 1 fi } setup_systemd_service() { # Add optional argument to skip start/enable during updates local update_mode=${1:-false} # Default to false if no argument passed print_info "Setting up systemd service ($SERVICE_NAME)..." local service_file="/etc/systemd/system/$SERVICE_NAME" # Find Node path (needed for systemd ExecStart) local node_path node_path=$(command -v node) if [ -z "$node_path" ]; then print_error "Could not find Node.js executable path. Cannot create service." return 1 fi # Find npm path (needed for systemd ExecStart) local npm_path npm_path=$(command -v npm) if [ -z "$npm_path" ]; then print_warning "Could not find npm executable path. Service might require adjustment." # Try to find it relative to node? Often in ../lib/node_modules/npm/bin/npm-cli.js local node_dir node_dir=$(dirname "$node_path") # This is a common structure, adjust if needed npm_path="$node_dir/npm" # Adjust if npm is installed elsewhere if ! command -v "$npm_path" &> /dev/null; then print_error "Could not reliably find npm executable path. Cannot create service." return 1 fi print_info "Found npm at $npm_path" fi print_info "Creating/Updating service file at $service_file..." # Use a HEREDOC to write the service file contents cat << EOF > "$service_file" [Unit] Description=Pulse for Proxmox VE Monitoring Application After=network.target [Service] Type=simple User=$PULSE_USER Group=$PULSE_USER WorkingDirectory=$PULSE_DIR # Load environment variables from .env file in the working directory # Using explicit path instead of %W due to potential expansion issues EnvironmentFile=$PULSE_DIR/.env # Start command using absolute paths found earlier ExecStart=$node_path $npm_path run start # Restart policy Restart=on-failure RestartSec=5 # Environment (optional, if needed, but .env should handle this) # Environment="NODE_ENV=production" # Standard output/error logging StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF if [ $? -ne 0 ]; then print_error "Failed to create systemd service file $service_file." return 1 fi # Set permissions for the service file chmod 644 "$service_file" print_info "Reloading systemd daemon..." systemctl daemon-reload # Skip enable/start if in update mode if [ "$update_mode" = true ]; then print_info "(Update mode: Skipping service enable/start)" return 0 fi print_info "Enabling $SERVICE_NAME to start on boot..." if systemctl enable "$SERVICE_NAME" > /dev/null 2>&1; then print_success "Service enabled successfully." else print_error "Failed to enable systemd service." # Attempt cleanup of service file? # rm -f "$service_file" return 1 fi print_info "Starting $SERVICE_NAME..." if systemctl start "$SERVICE_NAME"; then print_success "Service started successfully." else print_error "Failed to start systemd service." print_warning "Please check the service status using: systemctl status $SERVICE_NAME" print_warning "And check the logs using: journalctl -u $SERVICE_NAME" # Don't necessarily exit, user might be able to fix it. return 1 # Indicate start failure fi return 0 } # --- Final Steps Functions --- (setup_cron_update, prompt_for_cron_setup, final_instructions) # ... (These functions remain mostly unchanged) ... setup_cron_update() { local cron_schedule="" local cron_command="" # Use the pre-calculated absolute path local script_path="$SCRIPT_ABS_PATH" if [ -z "$script_path" ] || [ ! -f "$script_path" ]; then print_warning "Could not reliably determine script path for cron job. Skipping auto-update setup." return 1 fi # Escape path for use in sed/grep (already handled by pre-calculation? Check this) # Re-calculate escaped path just in case local escaped_script_path escaped_script_path=$(sed 's/[&/\\]/\\&/g' <<< "$script_path") print_info "Choose update frequency:" echo " 1) Daily" echo " 2) Weekly" echo " 3) Monthly" echo " 4) Never (Cancel)" read -p "Enter your choice (1-4): " freq_choice case $freq_choice in 1) cron_schedule="@daily" ;; 2) cron_schedule="@weekly" ;; 3) cron_schedule="@monthly" ;; 4) print_info "Automatic updates setup cancelled."; return 0 ;; *) print_error "Invalid choice. Skipping auto-update setup."; return 1 ;; esac # Construct the cron command # Runs as root, uses absolute path to bash and the script, redirects output cron_command="$cron_schedule /usr/bin/bash $script_path --update >> $LOG_FILE 2>&1" local cron_identifier="# Pulse-Auto-Update ($SCRIPT_NAME)" # Identifier comment # Check if cron job already exists for this script if crontab -l -u root 2>/dev/null | grep -q "$cron_identifier"; then print_warning "An automatic update cron job for Pulse already seems to exist." read -p "Overwrite existing schedule? (y/N): " overwrite_cron if [[ ! "$overwrite_cron" =~ ^[Yy]$ ]]; then print_info "Skipping cron job modification." return 0 fi # Remove existing job before adding the new one (crontab -l -u root 2>/dev/null | grep -v "$cron_identifier") | crontab -u root - if [ $? -ne 0 ]; then print_error "Failed to remove existing cron job." return 1 fi print_info "Existing cron job removed." fi # Add the new cron job print_info "Adding cron job with schedule: $cron_schedule" # Add identifier comment and the command (crontab -l -u root 2>/dev/null; echo "$cron_identifier"; echo "$cron_command") | crontab -u root - if [ $? -eq 0 ]; then print_success "Cron job for automatic updates added successfully." print_info "Update logs will be written to $LOG_FILE" # Ensure log file exists and is writable (optional, cron might create it) touch "$LOG_FILE" || print_warning "Could not touch log file $LOG_FILE" chmod 644 "$LOG_FILE" || print_warning "Could not chmod log file $LOG_FILE" else print_error "Failed to add cron job. Please check crontab configuration." return 1 fi return 0 } prompt_for_cron_setup() { # Only prompt if not running in update mode if [ "$MODE_UPDATE" = false ]; then echo "" # Add spacing read -p "Do you want to set up automatic updates for Pulse? (Y/n): " setup_cron_confirm if [[ ! "$setup_cron_confirm" =~ ^[Nn]$ ]]; then # Proceed if not 'N' or 'n' (Default Yes) setup_cron_update else print_info "Skipping automatic update setup." fi fi } final_instructions() { # Try to get the IP address of the container local ip_address ip_address=$(hostname -I | awk '{print $1}') # Get the first IP address local port_value # Read the port from the .env file, fallback to default if not found/readable local env_path="$PULSE_DIR/.env" if [ -f "$env_path" ] && grep -q '^PORT=' "$env_path"; then port_value=$(grep '^PORT=' "$env_path" | cut -d'=' -f2) else port_value="7655" # Default port fi echo "" print_success "Pulse for Proxmox VE installation/update complete!" echo "-------------------------------------------------------------" print_info "You should be able to access the Pulse dashboard at:" if [ -n "$ip_address" ]; then echo " http://$ip_address:$port_value" else echo " http://:$port_value" print_warning "Could not automatically determine the LXC IP address." fi echo "" print_info "The Pulse service ($SERVICE_NAME) is running and enabled on boot." print_info "To check the status: sudo systemctl status $SERVICE_NAME" print_info "To view logs: sudo journalctl -u $SERVICE_NAME -f" print_info "Configuration file: $PULSE_DIR/.env" echo "-------------------------------------------------------------" } # --- Main Execution --- Refactored check_root # Determine absolute path of the script early if command -v readlink &> /dev/null && readlink -f "$0" &> /dev/null; then SCRIPT_ABS_PATH=$(readlink -f "$0") else # Basic fallback if readlink -f is not available or fails if [[ "$0" == /* ]]; then SCRIPT_ABS_PATH="$0" else SCRIPT_ABS_PATH="$(pwd)/$0" fi # Verify the fallback path if [ ! -f "$SCRIPT_ABS_PATH" ]; then print_warning "Warning: Could not reliably determine absolute script path using fallback." SCRIPT_ABS_PATH="" # Clear path if unsure fi fi # Check installation status and user intent first check_installation_status # Sets the INSTALL_MODE variable case "$INSTALL_MODE" in "remove") perform_remove exit $? ;; "cancel") print_info "Installation cancelled by user." exit 0 ;; "error") # Error message already printed by check_installation_status exit 1 ;; "install" | "update") # Proceed with common setup steps print_info "Starting installation/update process..." apt_update_upgrade || exit 1 install_dependencies || exit 1 setup_node || exit 1 create_pulse_user || exit 1 if [ "$INSTALL_MODE" = "install" ]; then # --- Installation specific steps --- print_info "Cloning Pulse repository into $PULSE_DIR..." # Clone the main branch. Consider adding --depth 1 for faster clone if history isn't needed. if git clone https://github.com/rcourtman/Pulse.git "$PULSE_DIR" > /dev/null 2>&1; then print_success "Repository cloned successfully." else print_error "Failed to clone repository." exit 1 fi install_npm_deps || exit 1 set_permissions || exit 1 configure_environment || exit 1 # Prompt user for details setup_systemd_service || exit 1 # Create, enable, start service final_instructions prompt_for_cron_setup # Ask about cron on fresh install else # Update mode # --- Update specific steps --- print_success "Proceeding with update..." if perform_update; then # Don't re-run configure_environment on update unless specifically requested # Display final instructions, but maybe tailor message slightly? print_success "Pulse update completed successfully." final_instructions prompt_for_cron_setup # Ask about cron even on update? else print_error "Pulse update failed." exit 1 fi fi ;; *) print_error "Internal script error: Unknown mode '$INSTALL_MODE'" exit 1 ;; esac print_info "Script finished."