#!/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-proxmox.service" # --- 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 } 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)..." if apt-get install -y git curl sudo > /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 curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --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 } clone_repository() { print_info "Cloning Pulse repository into $PULSE_DIR..." if [ -d "$PULSE_DIR" ]; then print_warning "Directory $PULSE_DIR already exists. Assuming repo is already cloned or manually placed." # Optionally, add logic here to pull latest changes if desired # cd "$PULSE_DIR" && git pull origin main else # 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 fi } install_npm_deps() { print_info "Installing npm dependencies..." if [ ! -d "$PULSE_DIR" ]; then print_error "Pulse directory $PULSE_DIR not found. Cannot install dependencies." exit 1 fi cd "$PULSE_DIR" || { print_error "Failed to change directory to $PULSE_DIR"; exit 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." exit 1 fi print_info "Installing server dependencies..." cd server || { print_error "Failed to change directory to $PULSE_DIR/server"; exit 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." exit 1 fi # Return to script execution directory or root, if needed cd .. } set_permissions() { print_info "Setting permissions for $PULSE_DIR..." if chown -R "$PULSE_USER":"$PULSE_USER" "$PULSE_DIR"; then print_success "Permissions set correctly." 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." fi } configure_environment() { print_info "Configuring Pulse environment..." local env_example_path="$PULSE_DIR/server/.env.example" local env_path="$PULSE_DIR/server/.env" if [ ! -f "$env_example_path" ]; then print_error "Environment example file not found at $env_example_path. Cannot configure." exit 1 fi # Check if .env already exists if [ -f "$env_path" ]; then print_warning "Configuration file $env_path already exists." 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..." fi # --- Gather Proxmox Details --- 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 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 # Use -s for silent input for the secret read -sp " -> Proxmox API Token Secret: " proxmox_token_secret echo # Add a newline after secret input 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 --- read -p "Allow self-signed certificates for Proxmox? (y/N): " allow_self_signed local self_signed_value="false" if [[ "$allow_self_signed" =~ ^[Yy]$ ]]; then self_signed_value="true" fi read -p "Port for Pulse server (leave blank for default 7655): " pulse_port local port_value="7655" if [ -n "$pulse_port" ]; then # Basic validation: check if it's a number (optional) 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 --- print_info "Creating $env_path from example..." if cp "$env_example_path" "$env_path"; then # Use sed to replace placeholders. Use a different delimiter for sed if URLs contain slashes 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" # Set ownership chown "$PULSE_USER":"$PULSE_USER" "$env_path" chmod 600 "$env_path" # Restrict permissions for security print_success "Environment configured successfully in $env_path." else print_error "Failed to copy $env_example_path to $env_path." exit 1 fi } setup_systemd_service() { 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." exit 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." exit 1 fi print_info "Found npm at $npm_path" fi print_info "Creating 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 # 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." exit 1 fi # Set permissions for the service file chmod 644 "$service_file" print_info "Reloading systemd daemon..." systemctl daemon-reload 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" exit 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. 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/server/.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 and setup 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/server/.env" echo "-------------------------------------------------------------" } # --- Main Execution --- check_root apt_update_upgrade install_dependencies setup_node create_pulse_user clone_repository install_npm_deps set_permissions configure_environment setup_systemd_service final_instructions print_info "Script finished." # --- Placeholder for next steps --- # 1. Create dedicated user # 2. Clone repository # 3. Install npm dependencies # 4. Configure .env # 5. Setup systemd service # 6. Final instructions echo "" print_success "Script execution will continue..." # Placeholder