#!/bin/bash # LXS - CloudPanel Installation Script # Description: Install and manage CloudPanel hosting platform # Author: LXS # Date: 2025 # Load LXS common library (colors, separator, run_spinner, loggers) LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}" _lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; } eval "$_lib" unset _lib export LXS_LOG_FILE="/tmp/lxs_cloudpanel.log" # ═══════════════════════════════════════════════════════════════════════════ # Configuration # ═══════════════════════════════════════════════════════════════════════════ INSTALL_DIR="/usr/local/cloudpanel" PANEL_PORT=8443 INSTALLER_URL="https://installer.cloudpanel.io/ce/v2/install.sh" INSTALLER_SHA256="19cfa702e7936a79e47812ff57d9859175ea902c62a68b2c15ccd1ebaf36caeb" # ═══════════════════════════════════════════════════════════════════════════ # Helper Functions # ═══════════════════════════════════════════════════════════════════════════ is_installed() { [ -d "$INSTALL_DIR" ] || command -v clpctl &> /dev/null || [ -f "/usr/local/bin/clpctl" ] } check_requirements() { echo -e "${WHITE}Checking System Requirements...${NC}" # Check root access if [ "$EUID" -ne 0 ]; then echo -e "${RED}[✗] This script must be run as root${NC}" return 1 fi echo -e "${GREEN}[✓] Running as root${NC}" local cpu_cores=$(nproc) [ $cpu_cores -lt 1 ] && echo -e "${RED}[✗] Insufficient CPU cores (minimum: 1)${NC}" && return 1 echo -e "${GREEN}[✓] CPU cores: $cpu_cores${NC}" local total_mem_gb=$(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024 / 1024)) if [ $total_mem_gb -lt 1 ]; then echo -e "${RED}[✗] Insufficient RAM: ${total_mem_gb}GB (minimum: 1GB)${NC}" return 1 elif [ $total_mem_gb -lt 2 ]; then echo -e "${YELLOW}[!] Warning: 2GB RAM is recommended (found: ${total_mem_gb}GB)${NC}" else echo -e "${GREEN}[✓] Memory: ${total_mem_gb}GB${NC}" fi local available_gb=$(($(df / | awk 'NR==2 {print $4}') / 1024 / 1024)) if [ $available_gb -lt 10 ]; then echo -e "${RED}[✗] Insufficient disk space: ${available_gb}GB (minimum: 10GB)${NC}" return 1 elif [ $available_gb -lt 20 ]; then echo -e "${YELLOW}[!] Warning: 20GB free space is recommended (found: ${available_gb}GB)${NC}" else echo -e "${GREEN}[✓] Disk space: ${available_gb}GB available${NC}" fi local arch=$(uname -m) if [[ "$arch" != "x86_64" && "$arch" != "aarch64" ]]; then echo -e "${RED}[✗] Unsupported architecture: $arch${NC}" return 1 fi echo -e "${GREEN}[✓] Architecture: $arch${NC}" # Check OS - CloudPanel supports Ubuntu 24.04, 22.04, Debian 12, 11 if [ -f /etc/os-release ]; then . /etc/os-release case "$ID" in ubuntu) if [[ "$VERSION_ID" == "24.04" || "$VERSION_ID" == "22.04" ]]; then echo -e "${GREEN}[✓] OS: $PRETTY_NAME${NC}" else echo -e "${YELLOW}[!] Warning: Ubuntu version $VERSION_ID may not be officially supported${NC}" echo -e "${GRAY} Supported: Ubuntu 24.04 LTS, Ubuntu 22.04 LTS${NC}" fi ;; debian) if [[ "$VERSION_ID" == "12" || "$VERSION_ID" == "11" ]]; then echo -e "${GREEN}[✓] OS: $PRETTY_NAME${NC}" else echo -e "${YELLOW}[!] Warning: Debian version $VERSION_ID may not be officially supported${NC}" echo -e "${GRAY} Supported: Debian 12 LTS, Debian 11 LTS${NC}" fi ;; *) echo -e "${RED}[✗] Unsupported OS: $PRETTY_NAME${NC}" echo -e "${GRAY} Supported: Ubuntu 24.04/22.04, Debian 12/11${NC}" return 1 ;; esac else echo -e "${YELLOW}[!] Warning: Cannot detect OS version${NC}" fi return 0 } get_clpctl_path() { local max_attempts=30 local attempt=0 local clpctl_path="" while [ $attempt -lt $max_attempts ]; do if command -v clpctl &> /dev/null; then clpctl_path="clpctl" break elif [ -f "/usr/local/bin/clpctl" ]; then clpctl_path="/usr/local/bin/clpctl" break fi attempt=$((attempt + 1)) sleep 2 done echo "$clpctl_path" } configure_cloudpanel_basic_auth() { echo "" echo -e "${WHITE}${BOLD}Configuring CloudPanel Basic Auth...${NC}\n" # Wait for clpctl to be available echo -e "${PURPLE}[*] Waiting for CloudPanel CLI to be ready...${NC}" local clpctl_path=$(get_clpctl_path) if [ -z "$clpctl_path" ]; then echo -e "${YELLOW}[!] Warning: CloudPanel CLI not found, skipping Basic Auth configuration${NC}" return 1 fi echo -e "${GREEN}[✓] CloudPanel CLI is ready${NC}" echo "" # Generate random credentials (global variables for display later) BASIC_AUTH_USERNAME=$(generate_password 8) BASIC_AUTH_PASSWORD=$(generate_password 20) echo -e "${PURPLE}[*] Enabling Basic Auth...${NC}" # Enable Basic Auth if $clpctl_path cloudpanel:enable:basic-auth --userName="$BASIC_AUTH_USERNAME" --password="$BASIC_AUTH_PASSWORD" >> /tmp/lxs_cloudpanel.log 2>&1; then echo -e "${GREEN}[✓] Basic Auth enabled successfully${NC}" echo "" return 0 else echo -e "${YELLOW}[!] Warning: Failed to enable Basic Auth${NC}" echo -e "${GRAY} You can enable it manually later with:${NC}" echo -e "${GRAY} clpctl cloudpanel:enable:basic-auth --userName=USERNAME --password='PASSWORD'${NC}" return 1 fi } create_cloudpanel_admin() { echo "" echo -e "${WHITE}${BOLD}Creating CloudPanel Admin Account...${NC}\n" # Wait for clpctl to be available echo -e "${PURPLE}[*] Waiting for CloudPanel CLI to be ready...${NC}" local clpctl_path=$(get_clpctl_path) if [ -z "$clpctl_path" ]; then echo -e "${YELLOW}[!] Warning: CloudPanel CLI not found, skipping admin account creation${NC}" return 1 fi echo -e "${GREEN}[✓] CloudPanel CLI is ready${NC}" echo "" # Ask for admin email echo -e "${CYAN}Admin Email (ex. admin@example.com):${NC}" read -r ADMIN_EMAIL while [ -z "$ADMIN_EMAIL" ]; do echo -e "${RED}Email is required!${NC}" read -r ADMIN_EMAIL done echo "" # Generate random credentials (global variables for display later) ADMIN_USERNAME=$(generate_password 8 | tr '[:upper:]' '[:lower:]') ADMIN_PASSWORD=$(generate_password 20) echo -e "${PURPLE}[*] Creating admin account...${NC}" # Create admin user if $clpctl_path user:add --userName="$ADMIN_USERNAME" --email="$ADMIN_EMAIL" --firstName="Admin" --lastName="User" --password="$ADMIN_PASSWORD" --role="admin" --timezone="UTC" --status="1" >> /tmp/lxs_cloudpanel.log 2>&1; then echo -e "${GREEN}[✓] Admin account created successfully${NC}" echo "" return 0 else echo -e "${YELLOW}[!] Warning: Failed to create admin account${NC}" echo -e "${GRAY} You can create it manually later with:${NC}" echo -e "${GRAY} clpctl user:add --userName=$ADMIN_USERNAME --email=$ADMIN_EMAIL --firstName='Admin' --lastName='User' --password='$ADMIN_PASSWORD' --role='admin' --timezone='UTC' --status='1'${NC}" return 1 fi } # ═══════════════════════════════════════════════════════════════════════════ # Installation Functions # ═══════════════════════════════════════════════════════════════════════════ install_cloudpanel() { local start_time=$(date +%s) apt_noninteractive echo -e "${WHITE}${BOLD}CLOUDPANEL INSTALLATION${NC}\n" if is_installed; then echo -e "${YELLOW}CloudPanel is already installed!${NC}" return 0 fi check_requirements || return 1 echo "" wait_for_apt || return 1 echo "" # Update system and install required packages echo -e "${PURPLE}[*] Updating system packages...${NC}" if ! run_spinner "Updating system" apt update && apt -y upgrade && apt -y install curl wget sudo; then echo -e "${RED}[✗] Failed to update system packages${NC}" return 1 fi echo "" # Set database engine to MySQL 8.4 DB_ENGINE="MYSQL_8.4" echo -e "${GREEN}[✓] Database engine: MySQL 8.4${NC}" echo "" # Download installer echo -e "${PURPLE}[*] Downloading CloudPanel installer...${NC}" if ! curl -sS "$INSTALLER_URL" -o /tmp/cloudpanel_install.sh; then echo -e "${RED}[✗] Failed to download installer${NC}" return 1 fi # Verify SHA256 checksum echo -e "${PURPLE}[*] Verifying installer checksum...${NC}" local calculated_sha=$(sha256sum /tmp/cloudpanel_install.sh | awk '{print $1}') if [ "$calculated_sha" != "$INSTALLER_SHA256" ]; then echo -e "${RED}[✗] Installer checksum verification failed!${NC}" echo -e "${GRAY} Expected: $INSTALLER_SHA256${NC}" echo -e "${GRAY} Got: $calculated_sha${NC}" rm -f /tmp/cloudpanel_install.sh return 1 fi echo -e "${GREEN}[✓] Installer checksum verified${NC}" echo "" # Get server IP local server_ip=$(get_public_ip) echo -e "${WHITE}Starting Installation...${NC}" echo -e "${GRAY}[i] This may take 10-20 minutes...${NC}" echo "" # Execute installation show_separator DB_ENGINE=$DB_ENGINE bash /tmp/cloudpanel_install.sh local install_exit_code=$? show_separator # Clean up installer rm -f /tmp/cloudpanel_install.sh if [ $install_exit_code -ne 0 ]; then echo "" echo -e "${RED}[✗] Installation failed!${NC}" echo -e "${GRAY}Check /tmp/lxs_cloudpanel.log for details${NC}" return 1 fi echo -e "${GREEN}[✓] Installation completed${NC}" # Configure Basic Auth configure_cloudpanel_basic_auth # Create admin account create_cloudpanel_admin local duration=$(( $(date +%s) - start_time )) local minutes=$((duration / 60)) local seconds=$((duration % 60)) echo "" echo -e "${GREEN}${BOLD}Installation Completed Successfully!${NC}" echo -e "${GRAY}Installation time: ${minutes}m ${seconds}s${NC}" echo "" show_separator echo -e "${WHITE}${BOLD}CLOUDPANEL ACCESS INFORMATION${NC}" show_separator echo -e "${WHITE}Panel URL: ${GREEN}${BOLD}https://$server_ip:$PANEL_PORT${NC}" # Display Basic Auth credentials if configured if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then echo "" echo -e "${WHITE}${BOLD}BASIC AUTH CREDENTIALS${NC}" echo -e "${WHITE}Username: ${GREEN}${BOLD}$BASIC_AUTH_USERNAME${NC}" echo -e "${WHITE}Password: ${GREEN}${BOLD}$BASIC_AUTH_PASSWORD${NC}" echo "" echo -e "${YELLOW}[!] Basic Auth is enabled. You will need these credentials to access CloudPanel.${NC}" fi # Display Admin account credentials if created if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then echo "" echo -e "${WHITE}${BOLD}ADMIN ACCOUNT CREDENTIALS${NC}" echo -e "${WHITE}Email: ${GREEN}${BOLD}$ADMIN_EMAIL${NC}" echo -e "${WHITE}Username: ${GREEN}${BOLD}$ADMIN_USERNAME${NC}" echo -e "${WHITE}Password: ${GREEN}${BOLD}$ADMIN_PASSWORD${NC}" fi echo "" echo -e "${RED}${BOLD}IMPORTANT SECURITY STEPS:${NC}" echo -e "${YELLOW}1. Access CloudPanel immediately via the URL above${NC}" echo -e "${YELLOW}2. Ignore the self-signed certificate warning${NC}" echo -e "${YELLOW}3. Click 'Advanced' and 'Proceed' to continue${NC}" if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then echo -e "${YELLOW}4. Enter Basic Auth credentials when prompted${NC}" fi if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then echo -e "${YELLOW}$([ -n "$BASIC_AUTH_USERNAME" ] && echo "5" || echo "4"). Login with admin credentials above${NC}" fi echo "" echo -e "${RED}${BOLD}REQUIRED FIREWALL PORTS:${NC}" echo -e "${GRAY}20, 21, 22, 25, 53, 80, 443, 465, 587, 993, 995, $PANEL_PORT${NC}" show_separator if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then echo "" echo -e "${RED}${BOLD}IMPORTANT:${NC} ${YELLOW}Save Basic Auth credentials securely!${NC}" fi if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then echo "" echo -e "${RED}${BOLD}IMPORTANT:${NC} ${YELLOW}Save admin credentials securely!${NC}" fi } update_cloudpanel() { echo -e "${WHITE}${BOLD}CLOUDPANEL UPDATE${NC}\n" apt_noninteractive if ! is_installed; then echo -e "${RED}CloudPanel is not installed!${NC}" return 1 fi echo -e "${PURPLE}[*] Updating CloudPanel...${NC}" # Use clpctl if available if command -v clpctl &> /dev/null; then clpctl update elif [ -f "/usr/local/bin/clpctl" ]; then /usr/local/bin/clpctl update else echo -e "${YELLOW}[!] CloudPanel CLI not found, downloading latest installer...${NC}" curl -sS "$INSTALLER_URL" -o /tmp/cloudpanel_update.sh if echo "$INSTALLER_SHA256 /tmp/cloudpanel_update.sh" | sha256sum -c --quiet; then bash /tmp/cloudpanel_update.sh rm -f /tmp/cloudpanel_update.sh else echo -e "${RED}[✗] Failed to verify update installer${NC}" rm -f /tmp/cloudpanel_update.sh return 1 fi fi [ $? -eq 0 ] && echo -e "${GREEN}[✓] Update completed${NC}" || echo -e "${RED}[✗] Update failed${NC}" } show_status() { echo -e "${WHITE}${BOLD}CLOUDPANEL STATUS${NC}\n" if ! is_installed; then echo -e "${RED}CloudPanel is not installed!${NC}" return 1 fi # Check CloudPanel service if systemctl is-active --quiet cloudpanel; then echo -e "${GREEN}[✓] CloudPanel service is running${NC}" else echo -e "${YELLOW}[!] CloudPanel service status unknown${NC}" fi # Check web server if systemctl is-active --quiet nginx || systemctl is-active --quiet apache2; then echo -e "${GREEN}[✓] Web server is running${NC}" else echo -e "${YELLOW}[!] Web server status unknown${NC}" fi # Check database if systemctl is-active --quiet mysql || systemctl is-active --quiet mariadb; then echo -e "${GREEN}[✓] Database service is running${NC}" else echo -e "${YELLOW}[!] Database service status unknown${NC}" fi echo "" local server_ip=$(get_public_ip) echo -e "${CYAN}Panel URL: ${BOLD}https://$server_ip:$PANEL_PORT${NC}" # Show clpctl status if available if command -v clpctl &> /dev/null || [ -f "/usr/local/bin/clpctl" ]; then echo "" echo -e "${WHITE}CloudPanel CLI available${NC}" echo -e "${GRAY}Run 'clpctl' for available commands${NC}" fi return 0 } install_rclone() { echo -e "${PURPLE}[*] Checking rclone installation...${NC}" if command -v rclone &> /dev/null; then local rclone_version=$(rclone version | head -n 1 | awk '{print $2}') echo -e "${GREEN}[✓] rclone is already installed (version: $rclone_version)${NC}" return 0 fi echo -e "${PURPLE}[*] Installing rclone...${NC}" wait_for_apt || return 1 # Install rclone using official method if ! run_spinner "Installing rclone" curl https://rclone.org/install.sh | bash; then echo -e "${RED}[✗] Failed to install rclone${NC}" return 1 fi if command -v rclone &> /dev/null; then local rclone_version=$(rclone version | head -n 1 | awk '{print $2}') echo -e "${GREEN}[✓] rclone installed successfully (version: $rclone_version)${NC}" return 0 else echo -e "${RED}[✗] rclone installation failed${NC}" return 1 fi } configure_rclone_backblaze() { echo "" echo -e "${WHITE}${BOLD}Configure rclone for Backblaze B2${NC}\n" # Check if CloudPanel is installed if ! is_installed; then echo -e "${YELLOW}[!] CloudPanel is not installed. Please install CloudPanel first.${NC}" return 1 fi # Install rclone if needed if ! install_rclone; then return 1 fi echo "" echo -e "${CYAN}Backblaze B2 Configuration${NC}" echo -e "${GRAY}You need to provide your Backblaze B2 credentials.${NC}" echo -e "${GRAY}To get your credentials:${NC}" echo -e "${GRAY} 1. Log in to your Backblaze account${NC}" echo -e "${GRAY} 2. Go to 'App Keys' section${NC}" echo -e "${GRAY} 3. Create a new Application Key (or use an existing one)${NC}" echo -e "${GRAY} 4. Copy the 'Key ID' (Account ID) and 'Application Key'${NC}" echo "" # Ask for Account ID (Key ID) echo -e "${CYAN}Account ID (Key ID):${NC}" read -r B2_ACCOUNT_ID while [ -z "$B2_ACCOUNT_ID" ]; do echo -e "${RED}Account ID is required!${NC}" read -r B2_ACCOUNT_ID done # Ask for Application Key echo "" echo -e "${CYAN}Application Key:${NC}" read -rs B2_APPLICATION_KEY while [ -z "$B2_APPLICATION_KEY" ]; do echo -e "${RED}Application Key is required!${NC}" read -rs B2_APPLICATION_KEY done echo "" # Ask for bucket name (optional, for information) echo "" echo -e "${CYAN}Bucket Name (optional, for reference):${NC}" read -r B2_BUCKET_NAME echo "" echo -e "${PURPLE}[*] Configuring rclone...${NC}" # Create rclone config directory if it doesn't exist # Use root's home directory since script runs as root local rclone_config_dir="/root/.config/rclone" mkdir -p "$rclone_config_dir" # Configure rclone non-interactively # The remote name must be "remote" for CloudPanel to recognize it local rclone_config_file="$rclone_config_dir/rclone.conf" # Check if remote already exists if [ -f "$rclone_config_file" ] && grep -q "^\[remote\]" "$rclone_config_file" 2>/dev/null; then echo -e "${YELLOW}[!] A remote named 'remote' already exists.${NC}" echo -e "${CYAN}Do you want to overwrite it? (y/n):${NC}" read -r overwrite_choice if [[ ! "$overwrite_choice" =~ ^[Yy]$ ]]; then echo -e "${YELLOW}[!] Configuration cancelled${NC}" return 1 fi # Create a temporary file without the [remote] section local temp_config=$(mktemp) local in_remote_section=false while IFS= read -r line || [ -n "$line" ]; do if [[ "$line" =~ ^\[remote\] ]]; then in_remote_section=true continue elif [[ "$line" =~ ^\[ ]] && [ "$in_remote_section" = true ]; then in_remote_section=false echo "$line" >> "$temp_config" elif [ "$in_remote_section" = false ]; then echo "$line" >> "$temp_config" fi done < "$rclone_config_file" mv "$temp_config" "$rclone_config_file" fi # Add new remote configuration cat >> "$rclone_config_file" << EOF [remote] type = b2 account = $B2_ACCOUNT_ID key = $B2_APPLICATION_KEY hard_delete = false EOF if [ $? -eq 0 ]; then echo -e "${GREEN}[✓] rclone configured successfully${NC}" else echo -e "${RED}[✗] Failed to configure rclone${NC}" return 1 fi # Test the configuration echo "" echo -e "${PURPLE}[*] Testing rclone configuration...${NC}" if rclone lsd remote: > /dev/null 2>&1; then echo -e "${GREEN}[✓] rclone connection test successful${NC}" # List buckets if available echo "" echo -e "${CYAN}Available buckets:${NC}" rclone lsd remote: 2>/dev/null | awk '{print " - " $5}' || echo -e "${GRAY} (Unable to list buckets)${NC}" else echo -e "${YELLOW}[!] Warning: Could not test connection to Backblaze B2${NC}" echo -e "${GRAY} Please verify your credentials are correct${NC}" fi echo "" show_separator echo -e "${WHITE}${BOLD}Configuration Complete!${NC}" show_separator echo "" echo -e "${GREEN}[✓] rclone has been configured with Backblaze B2${NC}" echo "" echo -e "${WHITE}${BOLD}Next Steps:${NC}" echo -e "${CYAN}1. Access CloudPanel at: ${BOLD}https://$(get_public_ip):$PANEL_PORT${NC}" echo -e "${CYAN}2. Go to Settings > Backups${NC}" echo -e "${CYAN}3. Select 'Custom Rclone Config' as the storage provider${NC}" if [ -n "$B2_BUCKET_NAME" ]; then echo -e "${CYAN}4. In 'Storage Directory', enter: ${BOLD}$B2_BUCKET_NAME${NC}" else echo -e "${CYAN}4. In 'Storage Directory', enter your Backblaze B2 bucket name${NC}" fi echo -e "${CYAN}5. Configure backup frequency and retention as needed${NC}" echo -e "${CYAN}6. Save the configuration${NC}" echo "" echo -e "${GRAY}[i] Note: The rclone remote is named 'remote' as required by CloudPanel${NC}" show_separator } # ═══════════════════════════════════════════════════════════════════════════ # Main Menu # ═══════════════════════════════════════════════════════════════════════════ show_menu() { clear echo -e "${WHITE}${BOLD}CLOUDPANEL MANAGEMENT${NC}\n" echo -e " ${GREEN}[1]${NC} Install CloudPanel" echo -e " ${YELLOW}[2]${NC} Update CloudPanel" echo -e " ${CYAN}[3]${NC} View Status" echo -e " ${PURPLE}[4]${NC} Configure rclone for Backblaze B2" echo -e " ${RED}[0]${NC} Back to main menu" echo "" echo -n "Choice [0-4]: " } main() { while true; do show_menu read -r choice echo "" case $choice in 1) install_cloudpanel ;; 2) update_cloudpanel ;; 3) show_status ;; 4) configure_rclone_backblaze ;; 0) return 0 ;; *) echo -e "${RED}Invalid option${NC}" ;; esac echo "" read -p "Press Enter to continue..." done } main