#!/bin/bash # ZitiNexus Router Enrollment UI Installation Script # For Ubuntu 22.04/24.04 LTS set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration WEB_DIR="/var/www/ziti-enrollment" DOMAIN="ziti-enrollment.local" WEB_USER="www-data" PHP_VERSION="" # Logging function log() { local level=$1 shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') case $level in "ERROR") echo -e "${RED}[ERROR]${NC} $message" >&2 ;; "SUCCESS") echo -e "${GREEN}[SUCCESS]${NC} $message" ;; "WARNING") echo -e "${YELLOW}[WARNING]${NC} $message" ;; "INFO") echo -e "${BLUE}[INFO]${NC} $message" ;; *) echo "$message" ;; esac } # Error handling error_exit() { log "ERROR" "$1" exit 1 } # Check if running as root check_root() { if [[ $EUID -ne 0 ]]; then error_exit "This script must be run as root (use sudo)" fi } # Detect available PHP version detect_php_version() { log "INFO" "Detecting available PHP version..." # Update package cache first apt update >/dev/null 2>&1 # Check for available PHP versions in order of preference for version in "8.3" "8.2" "8.1" "8.0"; do log "INFO" "Checking for PHP $version..." # Check multiple ways to ensure package availability if apt-cache show "php${version}" >/dev/null 2>&1 || \ apt-cache show "php${version}-cli" >/dev/null 2>&1 || \ apt list "php${version}" 2>/dev/null | grep -q "php${version}"; then # Double-check that the FPM package exists for Nginx if apt-cache show "php${version}-fpm" >/dev/null 2>&1 || \ apt list "php${version}-fpm" 2>/dev/null | grep -q "php${version}-fpm"; then PHP_VERSION="$version" log "SUCCESS" "Found PHP $PHP_VERSION with FPM support" break else log "WARNING" "PHP $version found but FPM package not available" fi else log "INFO" "PHP $version not available" fi done if [[ -z "$PHP_VERSION" ]]; then log "ERROR" "No compatible PHP version found. Available packages:" apt list --installed | grep php 2>/dev/null || echo "No PHP packages found" log "INFO" "Trying to install default PHP..." # Fallback: try to install default php package if apt-cache show "php" >/dev/null 2>&1; then log "INFO" "Found default PHP package, will use system default" # Get the default PHP version PHP_VERSION=$(apt-cache show php | grep "^Depends:" | grep -o "php[0-9]\+\.[0-9]\+" | head -1 | sed 's/php//') if [[ -n "$PHP_VERSION" ]]; then log "SUCCESS" "Will use system default PHP $PHP_VERSION" else error_exit "Could not determine PHP version from default package" fi else error_exit "No PHP packages available. Please install PHP manually first." fi fi log "SUCCESS" "Selected PHP version: $PHP_VERSION" } # Detect web server preference detect_web_server() { echo log "INFO" "Which web server would you like to use?" echo "1) Apache (recommended)" echo "2) Nginx" read -p "Enter your choice (1-2): " choice case $choice in 1) WEB_SERVER="apache" ;; 2) WEB_SERVER="nginx" ;; *) log "WARNING" "Invalid choice, defaulting to Apache" WEB_SERVER="apache" ;; esac log "INFO" "Selected web server: $WEB_SERVER" } # Install web server and PHP install_web_server() { log "INFO" "Installing web server and PHP $PHP_VERSION..." # Update package list apt update || error_exit "Failed to update package list" # Determine required PHP packages PHP_PACKAGES="php${PHP_VERSION} php${PHP_VERSION}-curl" # JSON support is bundled in modern PHP versions (php-cli, php-fpm, etc.) log "INFO" "Skipping php${PHP_VERSION}-json as it's bundled in php${PHP_VERSION}-cli" if [[ "$WEB_SERVER" == "apache" ]]; then # Install Apache and PHP apt install -y apache2 $PHP_PACKAGES libapache2-mod-php${PHP_VERSION} || error_exit "Failed to install Apache and PHP" # Enable and start Apache systemctl enable apache2 || error_exit "Failed to enable Apache" systemctl start apache2 || error_exit "Failed to start Apache" log "SUCCESS" "Apache and PHP $PHP_VERSION installed successfully" elif [[ "$WEB_SERVER" == "nginx" ]]; then # Install Nginx and PHP-FPM apt install -y nginx php${PHP_VERSION}-fpm $PHP_PACKAGES || error_exit "Failed to install Nginx and PHP" # Enable and start services systemctl enable nginx php${PHP_VERSION}-fpm || error_exit "Failed to enable Nginx and PHP-FPM" systemctl start nginx php${PHP_VERSION}-fpm || error_exit "Failed to start Nginx and PHP-FPM" log "SUCCESS" "Nginx and PHP $PHP_VERSION installed successfully" fi } # Deploy UI files deploy_ui() { log "INFO" "Deploying UI files..." # Create web directory mkdir -p "$WEB_DIR" || error_exit "Failed to create web directory" # Check for required directories log "INFO" "Current directory: $(pwd)" log "INFO" "Directory contents: $(ls -la)" if [[ ! -d "public" ]]; then error_exit "public directory not found. Current directory: $(pwd)" fi if [[ ! -d "includes" ]]; then error_exit "includes directory not found. Current directory: $(pwd)" fi log "INFO" "Found required directories: public and includes" # Copy main UI files (assets are now inside public) cp -r public includes "$WEB_DIR/" || error_exit "Failed to copy UI files" # Copy root-level PHP files for direct access (when document root is main directory) if [[ -f "index.php" ]]; then cp index.php "$WEB_DIR/" || log "WARNING" "Failed to copy root index.php" fi if [[ -f "dashboard.php" ]]; then cp dashboard.php "$WEB_DIR/" || log "WARNING" "Failed to copy root dashboard.php" fi # Copy optional files if they exist if [[ -f "README.md" ]]; then cp README.md "$WEB_DIR/" || log "WARNING" "Failed to copy README.md" fi if [[ -f ".htaccess" ]]; then cp .htaccess "$WEB_DIR/" || log "WARNING" "Failed to copy .htaccess" fi # Create logs and temp directories if they don't exist if [[ -d "logs" ]]; then cp -r logs "$WEB_DIR/" || log "WARNING" "Failed to copy logs directory" else mkdir -p "$WEB_DIR/logs" || error_exit "Failed to create logs directory" log "INFO" "Created logs directory" fi if [[ -d "temp" ]]; then cp -r temp "$WEB_DIR/" || log "WARNING" "Failed to copy temp directory" else mkdir -p "$WEB_DIR/temp" || error_exit "Failed to create temp directory" log "INFO" "Created temp directory" fi # Set proper permissions chown -R "$WEB_USER:$WEB_USER" "$WEB_DIR" || error_exit "Failed to set ownership" chmod -R 755 "$WEB_DIR" || error_exit "Failed to set permissions" chmod -R 777 "$WEB_DIR/logs" "$WEB_DIR/temp" || error_exit "Failed to set log/temp permissions" log "SUCCESS" "UI files deployed successfully" } # Configure Apache configure_apache() { log "INFO" "Configuring Apache virtual host..." # Create virtual host configuration cat > "/etc/apache2/sites-available/ziti-enrollment.conf" << EOF ServerName $DOMAIN DocumentRoot $WEB_DIR/public AllowOverride All Require all granted DirectoryIndex index.php ErrorLog \${APACHE_LOG_DIR}/ziti-enrollment_error.log CustomLog \${APACHE_LOG_DIR}/ziti-enrollment_access.log combined EOF # Enable site and modules a2ensite ziti-enrollment.conf || error_exit "Failed to enable site" a2enmod rewrite || error_exit "Failed to enable rewrite module" # Disable default site a2dissite 000-default.conf || log "WARNING" "Failed to disable default site" # Reload Apache systemctl reload apache2 || error_exit "Failed to reload Apache" log "SUCCESS" "Apache configured successfully" } # Configure Nginx configure_nginx() { log "INFO" "Configuring Nginx virtual host..." # Create virtual host configuration cat > "/etc/nginx/sites-available/ziti-enrollment" << EOF server { listen 80; server_name $DOMAIN; root $WEB_DIR/public; index index.php; location / { try_files \$uri \$uri/ /index.php?\$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php${PHP_VERSION}-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name; include fastcgi_params; } location ~ /\. { deny all; } } EOF # Enable site ln -sf "/etc/nginx/sites-available/ziti-enrollment" "/etc/nginx/sites-enabled/" || error_exit "Failed to enable site" # Remove default site rm -f "/etc/nginx/sites-enabled/default" || log "WARNING" "Failed to remove default site" # Test and reload Nginx nginx -t || error_exit "Nginx configuration test failed" systemctl reload nginx || error_exit "Failed to reload Nginx" log "SUCCESS" "Nginx configured successfully" } # Configure PHP configure_php() { log "INFO" "Configuring PHP..." # Find PHP configuration file if [[ "$WEB_SERVER" == "apache" ]]; then PHP_INI="/etc/php/${PHP_VERSION}/apache2/php.ini" else PHP_INI="/etc/php/${PHP_VERSION}/fpm/php.ini" fi # Check if exec functions are disabled if grep -q "disable_functions.*exec" "$PHP_INI"; then log "WARNING" "PHP exec functions may be disabled. Please check $PHP_INI" log "INFO" "Ensure exec, shell_exec, proc_open are NOT in disable_functions" fi # Restart web server to apply PHP changes if [[ "$WEB_SERVER" == "apache" ]]; then systemctl restart apache2 || error_exit "Failed to restart Apache" else systemctl restart php${PHP_VERSION}-fpm || error_exit "Failed to restart PHP-FPM" fi log "SUCCESS" "PHP configured successfully" } # Set up OpenZiti package repository and install packages setup_openziti_repository() { log "INFO" "Setting up OpenZiti package repository..." local gpgKeyUrl='https://get.openziti.io/tun/package-repos.gpg' local gpgKeyPath='/usr/share/keyrings/openziti.gpg' # Check if GPG key already exists and is valid if [[ -f "$gpgKeyPath" && -s "$gpgKeyPath" ]]; then local keySize=$(wc -c < "$gpgKeyPath") log "SUCCESS" "OpenZiti GPG key already exists ($keySize bytes), skipping installation" else log "INFO" "Installing OpenZiti GPG key..." # Ensure keyrings directory exists mkdir -p /usr/share/keyrings || error_exit "Failed to create keyrings directory" chmod 755 /usr/share/keyrings || error_exit "Failed to set keyrings directory permissions" # Download GPG key to temporary file local tempGpgFile=$(mktemp) log "INFO" "Downloading GPG key from $gpgKeyUrl..." if curl -sSLf --connect-timeout 30 --max-time 60 "$gpgKeyUrl" -o "$tempGpgFile"; then local fileSize=$(wc -c < "$tempGpgFile") log "SUCCESS" "GPG key downloaded successfully ($fileSize bytes)" # Try multiple methods to process the GPG key local methods=( "gpg --dearmor --output '$gpgKeyPath' '$tempGpgFile'" "cat '$tempGpgFile' | gpg --dearmor > '$gpgKeyPath'" "gpg --dearmor < '$tempGpgFile' > '$gpgKeyPath'" ) local success=false for method in "${methods[@]}"; do log "INFO" "Trying GPG processing method: $method" if eval "$method" 2>/dev/null; then if [[ -f "$gpgKeyPath" && -s "$gpgKeyPath" ]]; then local processedSize=$(wc -c < "$gpgKeyPath") log "SUCCESS" "GPG key processed successfully ($processedSize bytes)" success=true break fi fi done # Fallback: copy raw file and let apt handle it if [[ "$success" != true ]]; then log "WARNING" "GPG processing failed, trying raw file copy..." if cp "$tempGpgFile" "$gpgKeyPath"; then log "SUCCESS" "GPG key copied as raw file - apt will handle format conversion" success=true fi fi rm -f "$tempGpgFile" if [[ "$success" != true ]]; then error_exit "Failed to install OpenZiti GPG key" fi else rm -f "$tempGpgFile" error_exit "Failed to download OpenZiti GPG key from $gpgKeyUrl" fi fi # Set proper permissions on GPG key chmod a+r "$gpgKeyPath" || error_exit "Failed to set GPG key permissions" # Configure OpenZiti repository local repoFile='/etc/apt/sources.list.d/openziti-release.list' local repoContent='deb [signed-by=/usr/share/keyrings/openziti.gpg] https://packages.openziti.org/zitipax-openziti-deb-stable debian main' if [[ -f "$repoFile" ]]; then log "INFO" "OpenZiti repository already configured" else log "INFO" "Configuring OpenZiti repository..." echo "$repoContent" > "$repoFile" || error_exit "Failed to create repository file" log "SUCCESS" "OpenZiti repository configured" fi # Update package list log "INFO" "Updating package list..." if apt update; then log "SUCCESS" "Package list updated successfully" else log "WARNING" "Package list update had issues, but continuing..." fi # Verify repository is accessible log "INFO" "Verifying OpenZiti repository accessibility..." if apt-cache show openziti-router >/dev/null 2>&1; then log "SUCCESS" "OpenZiti repository is accessible and openziti-router package is available" elif apt-cache show ziti >/dev/null 2>&1; then log "SUCCESS" "OpenZiti repository is accessible and ziti package is available" else log "WARNING" "OpenZiti packages not found in repositories, but repository is configured" fi } # Install OpenZiti packages install_openziti_packages() { log "INFO" "Installing OpenZiti packages..." # Check if OpenZiti CLI is already installed if command -v ziti &> /dev/null; then local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown") log "INFO" "OpenZiti CLI already installed: $ziti_version" # Check if we also have the router package if dpkg -l | grep -q openziti-router; then log "SUCCESS" "OpenZiti router package already installed" return 0 fi fi log "INFO" "Installing OpenZiti packages using package repository..." # Try to install openziti-router package first (includes ziti CLI) log "INFO" "Installing openziti-router package..." if apt install -y openziti-router; then log "SUCCESS" "OpenZiti router package installed successfully" else log "WARNING" "Failed to install openziti-router package, trying ziti CLI only..." # Fallback: Try to install just the ziti CLI log "INFO" "Attempting to install ziti CLI only..." if apt install -y ziti; then log "SUCCESS" "OpenZiti CLI installed successfully" else error_exit "Failed to install OpenZiti packages from repository" fi fi # Verify installation if command -v ziti &> /dev/null; then local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown") log "SUCCESS" "OpenZiti CLI installed and working: $ziti_version" else error_exit "OpenZiti CLI installation failed - command not found after installation" fi # Additional verification - test basic ziti commands log "INFO" "Testing OpenZiti CLI functionality..." if ziti --help >/dev/null 2>&1; then log "SUCCESS" "OpenZiti CLI is functional" else log "WARNING" "OpenZiti CLI may not be fully functional" fi # Check for router-specific functionality if ziti router --help >/dev/null 2>&1; then log "SUCCESS" "OpenZiti router commands are available" else log "WARNING" "OpenZiti router commands may not be available" fi log "SUCCESS" "OpenZiti package installation completed" } # Set up sudo access setup_sudo() { log "INFO" "Setting up comprehensive sudo access for web server..." log "INFO" "This configuration works on both normal Ubuntu and CloudStack instances" # Create comprehensive sudoers file cat > "/etc/sudoers.d/ziti-enrollment" << 'EOF' # Allow www-data to run system commands for Ziti enrollment # Comprehensive permissions for all environments (normal Ubuntu + CloudStack) # Core system commands www-data ALL=(ALL) NOPASSWD: /usr/bin/apt-get www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl www-data ALL=(ALL) NOPASSWD: /usr/bin/mkdir www-data ALL=(ALL) NOPASSWD: /usr/bin/chmod www-data ALL=(ALL) NOPASSWD: /usr/bin/chown www-data ALL=(ALL) NOPASSWD: /usr/bin/cp www-data ALL=(ALL) NOPASSWD: /usr/bin/mv www-data ALL=(ALL) NOPASSWD: /usr/bin/rm www-data ALL=(ALL) NOPASSWD: /usr/bin/ln # Network and download commands www-data ALL=(ALL) NOPASSWD: /usr/bin/curl www-data ALL=(ALL) NOPASSWD: /usr/bin/wget # GPG and security commands www-data ALL=(ALL) NOPASSWD: /usr/bin/gpg www-data ALL=(ALL) NOPASSWD: /usr/bin/ziti # Information gathering commands www-data ALL=(ALL) NOPASSWD: /usr/bin/which www-data ALL=(ALL) NOPASSWD: /usr/bin/hostname www-data ALL=(ALL) NOPASSWD: /usr/bin/uname www-data ALL=(ALL) NOPASSWD: /usr/bin/lsb_release www-data ALL=(ALL) NOPASSWD: /usr/bin/whoami www-data ALL=(ALL) NOPASSWD: /usr/bin/id www-data ALL=(ALL) NOPASSWD: /usr/bin/pwd www-data ALL=(ALL) NOPASSWD: /usr/bin/date # File operations www-data ALL=(ALL) NOPASSWD: /usr/bin/tee www-data ALL=(ALL) NOPASSWD: /usr/bin/cat www-data ALL=(ALL) NOPASSWD: /usr/bin/test www-data ALL=(ALL) NOPASSWD: /usr/bin/ls www-data ALL=(ALL) NOPASSWD: /usr/bin/touch www-data ALL=(ALL) NOPASSWD: /usr/bin/echo www-data ALL=(ALL) NOPASSWD: /usr/bin/head www-data ALL=(ALL) NOPASSWD: /usr/bin/tail www-data ALL=(ALL) NOPASSWD: /usr/bin/wc www-data ALL=(ALL) NOPASSWD: /usr/bin/grep www-data ALL=(ALL) NOPASSWD: /usr/bin/sed www-data ALL=(ALL) NOPASSWD: /usr/bin/awk www-data ALL=(ALL) NOPASSWD: /usr/bin/cut www-data ALL=(ALL) NOPASSWD: /usr/bin/sort www-data ALL=(ALL) NOPASSWD: /usr/bin/uniq # Network diagnostic commands www-data ALL=(ALL) NOPASSWD: /usr/bin/nslookup www-data ALL=(ALL) NOPASSWD: /usr/bin/ping www-data ALL=(ALL) NOPASSWD: /usr/bin/dig www-data ALL=(ALL) NOPASSWD: /usr/bin/host # Process and system monitoring www-data ALL=(ALL) NOPASSWD: /usr/bin/ps www-data ALL=(ALL) NOPASSWD: /usr/bin/top www-data ALL=(ALL) NOPASSWD: /usr/bin/htop www-data ALL=(ALL) NOPASSWD: /usr/bin/free www-data ALL=(ALL) NOPASSWD: /usr/bin/df www-data ALL=(ALL) NOPASSWD: /usr/bin/du # Text processing and utilities www-data ALL=(ALL) NOPASSWD: /usr/bin/find www-data ALL=(ALL) NOPASSWD: /usr/bin/xargs www-data ALL=(ALL) NOPASSWD: /usr/bin/basename www-data ALL=(ALL) NOPASSWD: /usr/bin/dirname www-data ALL=(ALL) NOPASSWD: /usr/bin/realpath www-data ALL=(ALL) NOPASSWD: /usr/bin/readlink # Archive and compression www-data ALL=(ALL) NOPASSWD: /usr/bin/tar www-data ALL=(ALL) NOPASSWD: /usr/bin/gzip www-data ALL=(ALL) NOPASSWD: /usr/bin/gunzip www-data ALL=(ALL) NOPASSWD: /usr/bin/zip www-data ALL=(ALL) NOPASSWD: /usr/bin/unzip # Shell and environment www-data ALL=(ALL) NOPASSWD: /bin/bash www-data ALL=(ALL) NOPASSWD: /bin/sh www-data ALL=(ALL) NOPASSWD: /usr/bin/env www-data ALL=(ALL) NOPASSWD: /usr/bin/sleep www-data ALL=(ALL) NOPASSWD: /usr/bin/timeout EOF # Validate sudoers file if visudo -c -f "/etc/sudoers.d/ziti-enrollment"; then log "SUCCESS" "Comprehensive sudo access configured successfully" log "INFO" "Configuration includes all permissions needed for any environment" else error_exit "Invalid sudoers configuration" fi } # Update hosts file update_hosts() { log "INFO" "Updating hosts file..." # Check if entry already exists if ! grep -q "$DOMAIN" /etc/hosts; then echo "127.0.0.1 $DOMAIN" >> /etc/hosts log "SUCCESS" "Added $DOMAIN to hosts file" else log "INFO" "Domain already exists in hosts file" fi } # Test installation test_installation() { log "INFO" "Testing installation..." # Test web server if [[ "$WEB_SERVER" == "apache" ]]; then if systemctl is-active --quiet apache2; then log "SUCCESS" "Apache is running" else log "ERROR" "Apache is not running" fi else if systemctl is-active --quiet nginx && systemctl is-active --quiet php${PHP_VERSION}-fpm; then log "SUCCESS" "Nginx and PHP-FPM are running" else log "ERROR" "Nginx or PHP-FPM is not running" fi fi # Test PHP if php -v > /dev/null 2>&1; then log "SUCCESS" "PHP is working" # Test JSON support if php -r 'var_dump(function_exists("json_encode"));' 2>/dev/null | grep -q "bool(true)"; then log "SUCCESS" "PHP JSON support is available" else log "WARNING" "PHP JSON support may not be available" fi else log "ERROR" "PHP is not working" fi # Test sudo access if sudo -u www-data sudo -n systemctl --version > /dev/null 2>&1; then log "SUCCESS" "Sudo access is working" else log "WARNING" "Sudo access may not be working properly" fi # Test file permissions if [[ -r "$WEB_DIR/public/index.php" ]]; then log "SUCCESS" "File permissions are correct" else log "ERROR" "File permissions may be incorrect" fi # Test OpenZiti installation if command -v ziti &> /dev/null; then local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown") log "SUCCESS" "OpenZiti CLI is installed and working: $ziti_version" # Test ziti router command if ziti router --help >/dev/null 2>&1; then log "SUCCESS" "OpenZiti router commands are functional" else log "WARNING" "OpenZiti router commands may not be available" fi else log "ERROR" "OpenZiti CLI is not installed or not working" fi } # Show final information show_final_info() { echo echo "==============================================" echo " INSTALLATION COMPLETED" echo "==============================================" echo log "SUCCESS" "ZitiNexus Router Enrollment UI installed successfully!" echo echo "Access Information:" echo " URL: http://$DOMAIN" echo " Username: admin" echo " Password: admin123" echo echo "Important Notes:" echo " 1. OpenZiti packages are now pre-installed and ready for enrollment" echo " 2. Change the default password in production" echo " 3. Consider setting up HTTPS for production use" echo " 4. Review security settings in $WEB_DIR/includes/config.php" echo " 5. The UI will now focus only on enrollment using hash keys" echo echo "File Locations:" echo " Web Directory: $WEB_DIR" echo " Configuration: $WEB_DIR/includes/config.php" echo " Logs: $WEB_DIR/logs/" echo echo "Useful Commands:" if [[ "$WEB_SERVER" == "apache" ]]; then echo " Check status: systemctl status apache2" echo " View logs: tail -f /var/log/apache2/ziti-enrollment_error.log" else echo " Check status: systemctl status nginx php${PHP_VERSION}-fpm" echo " View logs: tail -f /var/log/nginx/error.log" fi echo " Test sudo: sudo -u www-data sudo -l" echo } # Main installation function main() { echo "==============================================" echo " ZitiNexus Router Enrollment UI Installer" echo "==============================================" echo # Check if running as root check_root # Detect available PHP version detect_php_version # Detect web server preference detect_web_server # Install web server and PHP install_web_server # Deploy UI files deploy_ui # Configure web server if [[ "$WEB_SERVER" == "apache" ]]; then configure_apache else configure_nginx fi # Configure PHP configure_php # Set up sudo access setup_sudo # Set up OpenZiti package repository setup_openziti_repository # Install OpenZiti packages install_openziti_packages # Update hosts file update_hosts # Test installation test_installation # Show final information show_final_info } # Run main function main "$@"