diff --git a/clean_server.sh b/clean_server.sh index f790384..7043700 100644 --- a/clean_server.sh +++ b/clean_server.sh @@ -45,7 +45,7 @@ echo "[$(date)] Detected PHP version: $PHP_VERSION" # Stop services log_step "Stopping services" -systemctl stop nginx freeradius mariadb valkey-server php${PHP_VERSION}-fpm supervisor openvpn || echo "Could not stop all services" +systemctl stop nginx freeradius mariadb valkey-server valkey php${PHP_VERSION}-fpm supervisor openvpn || echo "Could not stop all services" # Remove web files log_step "Removing web files" diff --git a/mmsqlconf.sh b/mmsqlconf.sh deleted file mode 100644 index 7f24d8b..0000000 --- a/mmsqlconf.sh +++ /dev/null @@ -1,429 +0,0 @@ -#!/bin/bash - -# Error handling function -error_exit() { - echo "Error: $1" >&2 - exit 1 -} - -echo "Starting MariaDB configuration optimization for FreeRADIUS..." - -# Check if running as root -if [ "$(id -u)" -ne 0 ]; then - error_exit "This script must be run as root. Try using sudo." -fi - -# Check if MariaDB is installed -if ! command -v mariadb &> /dev/null; then - error_exit "MariaDB is not installed. Please install it first." -fi - -# Function to aggressively kill all MariaDB processes -kill_all_mariadb() { - echo "Aggressively terminating all MariaDB processes..." - - # Stop the service first - systemctl stop mariadb - sleep 2 - - # Find and kill all MariaDB processes - for PROC in mysqld mariadbd mysql mariadb; do - # Try with pkill first (process name) - pkill -9 $PROC 2>/dev/null || true - - # Try with pkill -f (command line) - pkill -9 -f $PROC 2>/dev/null || true - done - - # Find any remaining MySQL processes by port - for PID in $(lsof -i:3306 -t 2>/dev/null); do - kill -9 $PID 2>/dev/null || true - done - - # Wait to ensure processes are terminated - sleep 3 - - # Check if any processes are still running - if pgrep -x "mysqld" > /dev/null || pgrep -x "mariadbd" > /dev/null || pgrep -f "mysql" > /dev/null; then - echo "Warning: Some MariaDB processes are still running. This may cause issues." - else - echo "All MariaDB processes have been terminated." - fi -} - -# Function to clean up lock files -cleanup_lock_files() { - echo "Cleaning up lock files..." - - # Remove aria log files - rm -f /var/lib/mysql/aria_log_control 2>/dev/null || true - rm -f /var/lib/mysql/aria_log.* 2>/dev/null || true - - # Remove InnoDB lock files - rm -f /var/lib/mysql/ib_logfile* 2>/dev/null || true - rm -f /var/lib/mysql/ibdata1.lock 2>/dev/null || true - - # Remove socket files - rm -f /var/run/mysqld/mysqld.sock 2>/dev/null || true - rm -f /tmp/mysql.sock 2>/dev/null || true - - # Remove pid file - rm -f /var/run/mysqld/mysqld.pid 2>/dev/null || true - - # Ensure the mysqld directory exists with proper permissions - mkdir -p /var/run/mysqld 2>/dev/null || true - chown mysql:mysql /var/run/mysqld 2>/dev/null || true - - # Fix permissions on data directory - chown -R mysql:mysql /var/lib/mysql/ 2>/dev/null || true - - echo "Lock files cleanup completed." -} - -# Calculate system resources -TOTAL_RAM=$(free -b | awk '/Mem:/ {print $2}') -CPU_CORES=$(nproc) - -# Calculate RAM allocation (70% for buffer pool) -BUFFER_POOL_SIZE=$((TOTAL_RAM * 70 / 100)) -BUFFER_POOL_SIZE_MB=$((BUFFER_POOL_SIZE / 1024 / 1024)) - -# Calculate CPU-based settings targeting 60% CPU usage -# Each InnoDB instance can use about 15% CPU, so we calculate based on that -INNODB_INSTANCES=$((CPU_CORES * 60 / 100)) # 60% of cores -INNODB_INSTANCES=$((INNODB_INSTANCES > 0 ? INNODB_INSTANCES : 1)) # Minimum 1 - -# IO threads should be proportional to InnoDB instances -IO_THREADS=$((INNODB_INSTANCES * 2)) # 2 threads per instance - -# Thread pool size based on CPU cores (targeting 60% utilization) -THREAD_POOL_SIZE=$((CPU_CORES * 60 / 100)) -THREAD_POOL_SIZE=$((THREAD_POOL_SIZE > 0 ? THREAD_POOL_SIZE : 1)) - -# Calculate other resource-based settings -MAX_CONNECTIONS=$((CPU_CORES * 100)) # 100 connections per core -MAX_USER_CONNECTIONS=$((MAX_CONNECTIONS * 80 / 100)) # 80% of max connections - -# Calculate buffer sizes based on available RAM -PER_THREAD_BUFFERS=$((TOTAL_RAM * 5 / 100 / MAX_CONNECTIONS)) # 5% of RAM divided by max connections -SORT_BUFFER_SIZE=$((PER_THREAD_BUFFERS / 4)) -READ_BUFFER_SIZE=$((PER_THREAD_BUFFERS / 4)) -JOIN_BUFFER_SIZE=$((PER_THREAD_BUFFERS / 4)) -READ_RND_BUFFER_SIZE=$((PER_THREAD_BUFFERS / 4)) - -# Convert buffer sizes to MB with minimum values -SORT_BUFFER_SIZE_MB=$((SORT_BUFFER_SIZE / 1024 / 1024)) -SORT_BUFFER_SIZE_MB=$((SORT_BUFFER_SIZE_MB > 0 ? SORT_BUFFER_SIZE_MB : 1)) - -READ_BUFFER_SIZE_MB=$((READ_BUFFER_SIZE / 1024 / 1024)) -READ_BUFFER_SIZE_MB=$((READ_BUFFER_SIZE_MB > 0 ? READ_BUFFER_SIZE_MB : 1)) - -JOIN_BUFFER_SIZE_MB=$((JOIN_BUFFER_SIZE / 1024 / 1024)) -JOIN_BUFFER_SIZE_MB=$((JOIN_BUFFER_SIZE_MB > 0 ? JOIN_BUFFER_SIZE_MB : 1)) - -READ_RND_BUFFER_SIZE_MB=$((READ_RND_BUFFER_SIZE / 1024 / 1024)) -READ_RND_BUFFER_SIZE_MB=$((READ_RND_BUFFER_SIZE_MB > 0 ? READ_RND_BUFFER_SIZE_MB : 1)) - -# Calculate table cache based on RAM -TABLE_OPEN_CACHE=$((TOTAL_RAM / 1024 / 1024 / 2)) # Roughly 1 cache entry per 2MB RAM - -# Define the MariaDB configuration file locations for Ubuntu 20.04 -MYSQL_CONF_FILES=( - "/etc/mysql/mariadb.conf.d/50-server.cnf" -) - -# Kill all MariaDB processes before making configuration changes -kill_all_mariadb -cleanup_lock_files - -# Loop through the possible configuration file locations -for MYSQL_CONF_FILE in "${MYSQL_CONF_FILES[@]}"; do - # Check if the file exists - if [ -f "$MYSQL_CONF_FILE" ]; then - # Backup the configuration file - BACKUP_FILE="${MYSQL_CONF_FILE}.bak.$(date +%Y%m%d%H%M%S)" - cp "$MYSQL_CONF_FILE" "$BACKUP_FILE" - echo "Backed up $MYSQL_CONF_FILE to $BACKUP_FILE" - - # Create a temporary file for the new configuration - TMP_CONF=$(mktemp) - - # Read the original file to preserve structure and comments - # Extract the [mysqld] section - MYSQLD_SECTION_FOUND=false - CURRENT_SECTION="" - - while IFS= read -r line; do - # Detect section headers - if [[ "$line" =~ ^\[(.*)\] ]]; then - CURRENT_SECTION="${BASH_REMATCH[1]}" - echo "$line" >> "$TMP_CONF" - - # If we found the mysqld section, add our optimized settings - if [[ "$CURRENT_SECTION" == "mysqld" ]]; then - MYSQLD_SECTION_FOUND=true - - # Add our optimized settings for FreeRADIUS - cat >> "$TMP_CONF" << EOF - -# Basic Settings -user = mysql -pid-file = /run/mysqld/mysqld.pid -socket = /run/mysqld/mysqld.sock -port = 3306 -basedir = /usr -datadir = /var/lib/mysql -tmpdir = /tmp -lc-messages-dir = /usr/share/mysql -bind-address = 0.0.0.0 - -# Logging Configuration -log_error = /var/log/mysql/error.log -log_warnings = 2 - -# FreeRADIUS-Optimized InnoDB Settings -innodb_buffer_pool_size = ${BUFFER_POOL_SIZE_MB}M -innodb_buffer_pool_instances = ${INNODB_INSTANCES} -innodb_log_file_size = 512M -innodb_log_buffer_size = 64M -innodb_file_per_table = ON -innodb_open_files = ${TABLE_OPEN_CACHE} -innodb_io_capacity = $((IO_THREADS * 100)) -innodb_flush_method = O_DIRECT -innodb_read_io_threads = ${IO_THREADS} -innodb_write_io_threads = ${IO_THREADS} -innodb_stats_on_metadata = OFF -innodb_flush_log_at_trx_commit = 2 -innodb_doublewrite = 0 -innodb_lock_wait_timeout = 5 -innodb_deadlock_detect = ON - -# Connection Settings for FreeRADIUS Accounting -max_connections = ${MAX_CONNECTIONS} -max_user_connections = ${MAX_USER_CONNECTIONS} -thread_cache_size = $((THREAD_POOL_SIZE * 2)) -thread_stack = 192K -interactive_timeout = 30 -wait_timeout = 30 -max_allowed_packet = 16M -net_read_timeout = 5 -net_write_timeout = 5 -connect_timeout = 5 - -# Thread Pool Settings for Fast Accounting -thread_handling = pool-of-threads -thread_pool_size = ${THREAD_POOL_SIZE} -thread_pool_idle_timeout = 30 -thread_pool_max_threads = $((MAX_CONNECTIONS / 2)) -thread_pool_oversubscribe = 3 - -# Query Cache Settings (disabled for high-concurrency workloads) -query_cache_type = 0 -query_cache_size = 0 - -# Buffer Settings -sort_buffer_size = ${SORT_BUFFER_SIZE_MB}M -read_buffer_size = ${READ_BUFFER_SIZE_MB}M -read_rnd_buffer_size = ${READ_RND_BUFFER_SIZE_MB}M -join_buffer_size = ${JOIN_BUFFER_SIZE_MB}M -tmp_table_size = $((BUFFER_POOL_SIZE_MB / 32))M -max_heap_table_size = $((BUFFER_POOL_SIZE_MB / 32))M - -# Table Settings -table_open_cache = ${TABLE_OPEN_CACHE} -table_definition_cache = $((TABLE_OPEN_CACHE / 2)) -open_files_limit = $((TABLE_OPEN_CACHE * 2)) - -# MyISAM Settings (minimal since we use InnoDB) -key_buffer_size = $((BUFFER_POOL_SIZE_MB / 32))M -myisam_sort_buffer_size = $((BUFFER_POOL_SIZE_MB / 64))M - -# Aria Settings -aria_pagecache_buffer_size = $((BUFFER_POOL_SIZE_MB / 32))M -aria_sort_buffer_size = $((BUFFER_POOL_SIZE_MB / 64))M -aria_group_commit = none -aria_group_commit_interval = 0 -aria_log_purge_type = immediate - -# Security -local-infile = 0 -skip-name-resolve = ON - -# Performance Schema (enable for monitoring) -performance_schema = ON -performance_schema_max_table_instances = $((TABLE_OPEN_CACHE / 2)) - -# Slow Query Logging -slow_query_log = 1 -slow_query_log_file = /var/log/mysql/mariadb-slow.log -long_query_time = 2 - -# MySQL optimization settings to handle high load and connection issues -innodb_lock_wait_timeout = 30 -max_connections = 1000 -innodb_buffer_pool_size = 4G -innodb_log_buffer_size = 64M -innodb_file_per_table = 1 -innodb_flush_log_at_trx_commit = 2 -innodb_flush_method = O_DIRECT -innodb_thread_concurrency = 0 -thread_cache_size = 100 -table_open_cache = 8000 -query_cache_type = 0 -query_cache_size = 0 -max_connect_errors = 999999 -wait_timeout = 600 -interactive_timeout = 600 -skip-name-resolve -innodb_io_capacity = 2000 -innodb_io_capacity_max = 4000 -innodb_read_io_threads = 8 -innodb_write_io_threads = 8 -innodb_buffer_pool_instances = 8 - -EOF - fi - elif [[ "$CURRENT_SECTION" == "mysqld" && "$MYSQLD_SECTION_FOUND" == true ]]; then - # Skip existing settings in the mysqld section as we've already added our optimized ones - continue - else - # Copy the line as is for other sections - echo "$line" >> "$TMP_CONF" - fi - done < "$MYSQL_CONF_FILE" - - # If we didn't find a mysqld section, add one with the same settings - if [[ "$MYSQLD_SECTION_FOUND" == false ]]; then - echo -e "\n[mysqld]" >> "$TMP_CONF" - # Add the same configuration block as above - cat >> "$TMP_CONF" << EOF -# Same configuration block as above... -EOF - fi - - # Check the size of the generated file - TMP_SIZE=$(stat -c %s "$TMP_CONF" 2>/dev/null || stat -f %z "$TMP_CONF") - ORIG_SIZE=$(stat -c %s "$MYSQL_CONF_FILE" 2>/dev/null || stat -f %z "$MYSQL_CONF_FILE") - - if [ "$TMP_SIZE" -gt $((ORIG_SIZE * 10)) ]; then - echo "Warning: Generated configuration file is much larger than original (${TMP_SIZE} vs ${ORIG_SIZE} bytes)." - echo "This may indicate a problem. Aborting to prevent file corruption." - rm -f "$TMP_CONF" - error_exit "Configuration generation failed due to unexpected file size." - fi - - # Replace the original file with the new configuration - mv "$TMP_CONF" "$MYSQL_CONF_FILE" - chmod 644 "$MYSQL_CONF_FILE" - echo "Updated MariaDB configuration in $MYSQL_CONF_FILE" - - echo "Configuration updated successfully." - else - echo "Warning: Configuration file $MYSQL_CONF_FILE not found." - fi -done - -# Make sure all processes are killed and locks are removed before starting -kill_all_mariadb -cleanup_lock_files - -# Start MariaDB service -echo "Starting MariaDB service..." -systemctl start mariadb -sleep 5 - -# Check if MariaDB is running -if systemctl is-active --quiet mariadb; then - echo "MariaDB service is running." - - # Show some key variables to confirm changes - echo "Checking key MariaDB variables:" - echo "CPU-related settings:" - mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';" - mysql -e "SHOW VARIABLES LIKE 'thread_pool_size';" - mysql -e "SHOW VARIABLES LIKE 'innodb_read_io_threads';" - echo "Memory-related settings:" - mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';" - mysql -e "SHOW VARIABLES LIKE 'sort_buffer_size';" - - echo "MariaDB has been optimized for FreeRADIUS workloads!" - echo "Configuration is dynamically scaled to use:" - echo "- ${BUFFER_POOL_SIZE_MB}M of RAM for buffer pool (70% of total RAM)" - echo "- ${INNODB_INSTANCES} InnoDB instances (60% of CPU cores)" - echo "- ${THREAD_POOL_SIZE} thread pool size (60% of CPU cores)" - echo "This should help resolve the FreeRADIUS SQL module issues while maintaining efficient resource usage." -else - echo "MariaDB failed to restart. Trying emergency mode..." - - # Kill all processes again - kill_all_mariadb - cleanup_lock_files - - # Try starting with skip-grant-tables - echo "Starting MariaDB with skip-grant-tables..." - systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking" - systemctl start mariadb - sleep 5 - - # Reset environment and restart normally - systemctl set-environment MYSQLD_OPTS="" - systemctl restart mariadb - sleep 5 - - if systemctl is-active --quiet mariadb; then - echo "MariaDB service is now running after emergency restart." - echo "MariaDB has been optimized for FreeRADIUS workloads!" - echo "Configuration is dynamically scaled to use:" - echo "- ${BUFFER_POOL_SIZE_MB}M of RAM for buffer pool (70% of total RAM)" - echo "- ${INNODB_INSTANCES} InnoDB instances (60% of CPU cores)" - echo "- ${THREAD_POOL_SIZE} thread pool size (60% of CPU cores)" - else - echo "MariaDB still failed to restart. Manual intervention required." - echo "Try rebooting the system and then running: sudo systemctl start mariadb" - echo "Or check the logs: sudo journalctl -u mariadb" - fi -fi - -# Function to get MySQL version -get_mysql_version() { - mysql --version | awk '{print $3}' | awk -F'-' '{print $1}' -} - -# Function to validate MySQL settings -validate_mysql_settings() { - echo "Validating MySQL settings..." - - # Check if MySQL is running - if ! systemctl is-active --quiet mariadb; then - error_exit "MariaDB service is not running" - fi - - # Check key settings - local settings=( - "innodb_buffer_pool_size" - "max_connections" - "innodb_lock_wait_timeout" - "innodb_io_capacity" - ) - - for setting in "${settings[@]}"; do - value=$(mysql -e "SHOW VARIABLES LIKE '$setting';" | awk '{print $2}') - echo "✓ $setting = $value" - done -} - -# Function to monitor MySQL performance -monitor_mysql() { - echo "Starting MySQL performance monitoring..." - - # Monitor key metrics for 60 seconds - timeout 60 mysqladmin extended-status | grep -E '(Threads_connected|Questions|Queries|Connections|Aborted_connects)' & - - echo "✓ Monitoring started. Press Ctrl+C to stop." -} - -# Validate settings -validate_mysql_settings - -# Start monitoring -monitor_mysql \ No newline at end of file diff --git a/ubuntu_simpleisp.sh b/ubuntu_simpleisp.sh index 582e597..91aa1d6 100644 --- a/ubuntu_simpleisp.sh +++ b/ubuntu_simpleisp.sh @@ -40,6 +40,19 @@ touch "$INSTALL_LOG" || { echo "Cannot create log file"; exit 1; } echo "SimpleISP Installation Log - $(date '+%Y-%m-%d %H:%M:%S')" > "$INSTALL_LOG" echo "----------------------------------------" >> "$INSTALL_LOG" +# Configure system for Valkey (memory overcommit and other optimizations) +log_step "Configuring system for Valkey" + +# Enable memory overcommit +if ! grep -q "^vm.overcommit_memory" /etc/sysctl.conf; then + echo "vm.overcommit_memory = 1" | tee -a /etc/sysctl.conf + sysctl -p /etc/sysctl.conf + log_info "Enabled memory overcommit in sysctl" +else + log_info "Memory overcommit already configured in sysctl" +fi +COMPLETED_STEPS+=("System configured for Valkey") + # Check for cleanup marker file CLEANUP_MARKER="/root/.simpleisp_cleanup_done" REINSTALL=false @@ -61,6 +74,8 @@ if [ "$EUID" -ne 0 ]; then fi COMPLETED_STEPS+=("Root check passed") +# Set environment variable to avoid interactive prompts +export DEBIAN_FRONTEND=noninteractive # Get Ubuntu version log_step "Detecting Ubuntu version" UBUNTU_VERSION=$(lsb_release -cs) || handle_error "Failed to detect Ubuntu version" @@ -125,8 +140,28 @@ echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/packages.networkradius.com.asc log_success "NetworkRADIUS repository configured for Ubuntu $UBUNTU_VERSION" COMPLETED_STEPS+=("NetworkRADIUS repository configured") -# Set environment variable to avoid interactive prompts -export DEBIAN_FRONTEND=noninteractive +# Set Valkey Repository +log_step "Adding Valkey repository" +if [[ "$UBUNTU_VERSION" == "focal" || "$UBUNTU_VERSION" == "jammy" ]]; then + # Remove conflicting redis packages + apt-get remove -y redis-tools redis-server || true + + # Fetch Percona release package + wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb + + # Install Percona release package + dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb + + # Enable Percona repository for Valkey + percona-release enable valkey experimental + + # Update package list + apt-get update +fi + +COMPLETED_STEPS+=("Valkey repository added") + + # Update and upgrade system log_step "Updating system packages" @@ -168,8 +203,8 @@ if [ "$REINSTALL" = true ]; then gnupg \ lsb-release \ supervisor \ - redis-server \ - ufw \ + valkey \ + valkey-compat \ openvpn \ easy-rsa \ freeradius \ @@ -208,8 +243,8 @@ else gnupg \ lsb-release \ supervisor \ - redis-server \ - ufw \ + valkey \ + valkey-compat \ openvpn \ easy-rsa \ freeradius \ @@ -220,6 +255,234 @@ else fi COMPLETED_STEPS+=("Required packages installed") +# Configure Valkey with optimal settings for FreeRADIUS +log_step "Configuring Valkey with optimized settings" + +# Calculate optimal memory allocation (75% of available RAM, capped at 3GB, minimum 1GB) +TOTAL_RAM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}') +TOTAL_RAM_MB=$((TOTAL_RAM_KB / 1024)) +MAX_MEMORY_MB=$((TOTAL_RAM_MB * 75 / 100)) +if [ "$MAX_MEMORY_MB" -gt 3072 ]; then + MAX_MEMORY_MB=3072 # Cap at 3GB +fi +if [ "$MAX_MEMORY_MB" -lt 1024 ]; then + MAX_MEMORY_MB=1024 # Minimum 1GB +fi + +log_info "Configuring Valkey with ${MAX_MEMORY_MB}MB memory allocation" + +# Create Valkey configuration directory if it doesn't exist +mkdir -p /etc/valkey || handle_error "Failed to create Valkey configuration directory" + +# Configure Valkey with optimized settings for FreeRADIUS +cat > /etc/valkey/valkey.conf << EOL +# Valkey configuration for FreeRADIUS + +bind 0.0.0.0 ::0 +protected-mode yes +port 6379 +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 +daemonize no +supervised systemd +pidfile /var/run/valkey/valkey.pid +loglevel notice +logfile /var/log/valkey/valkey.log +databases 16 + +# Memory management +maxmemory ${MAX_MEMORY_MB}mb +maxmemory-policy volatile-lru +maxmemory-samples 5 + +# AOF persistence (enabled for better durability) +appendonly yes +dir /var/lib/valkey +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +aof-load-truncated yes +aof-rewrite-incremental-fsync yes + +# Performance optimizations +stop-writes-on-bgsave-error no +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb + +# Disable RDB snapshots since we're using AOF +save "" + +# Security +# Reuse the same password as MySQL for simplicity +requirepass "$MYSQL_PASSWORD" + +# Network +tcp-keepalive 300 +repl-timeout 60 +repl-ping-slave-period 10 +repl-backlog-size 1mb +repl-backlog-ttl 3600 + +# Client timeouts +timeout 0 +tcp-keepalive 300 + +# Disable dangerous commands +rename-command FLUSHDB "" +rename-command FLUSHALL "" +rename-command CONFIG "" +rename-command SHUTDOWN "" + +# Set the number of threads to serve the requests +io-threads 2 +io-threads-do-reads yes + +# Set the max number of connected clients at the same time +maxclients 10000 + +# Set the threshold for keys with an expire set to be considered for deletion +active-expire-effort 1 + +# Set the threshold for client output buffer limits +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Tune hash data structure +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +# Tune list data structure +list-max-ziplist-size -2 + +# Tune set data structure +set-max-intset-entries 512 + +# Tune zset data structure +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# Tune hll data structure +hll-sparse-max-bytes 3000 + +# Tune stream data structure +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Enable active defragmentation +active-defrag-threshold-lower 10 +active-defrag-threshold-upper 100 +active-defrag-ignore-bytes 100mb +active-defrag-cycle-min 5 +active-defrag-cycle-max 75 +active-defrag-max-scan-fields 1000 +EOL + +# Set proper permissions for Valkey directories +log_step "Setting Valkey directory permissions" +mkdir -p /var/lib/valkey/appendonlydir +chown -R valkey:valkey /var/lib/valkey /var/log/valkey /var/run/valkey +chmod 750 /var/lib/valkey /var/log/valkey /var/run/valkey + +# Fix Valkey service +sed -i 's/ConditionPathExists=!\/etc\/valkey\/REDIS_MIGRATION/ConditionPathExists=\/etc\/valkey\/REDIS_MIGRATION/g' /usr/lib/systemd/system/valkey.service + +# Restart Valkey to apply new configuration +systemctl daemon-reexec || log_warning "daemon-reexec failed (non-critical)" +systemctl daemon-reload || handle_error "Failed to reload systemd daemon" +systemctl restart valkey || handle_error "Failed to restart Valkey" +systemctl enable valkey || handle_error "Failed to enable Valkey" + +# Verify Valkey is running +log_step "Verifying Valkey service status" + +# Check service status +if systemctl is-active --quiet valkey; then + log_success "Valkey service is running" + COMPLETED_STEPS+=("Valkey configured with ${MAX_MEMORY_MB}MB memory allocation") +else + # If service is not running, try to get more information + log_warning "Valkey service is not running as expected. Checking status..." + systemctl status valkey --no-pager || true + + # Try to start the service + log_info "Attempting to start Valkey service..." + if systemctl start valkey; then + log_success "Successfully started Valkey service" + COMPLETED_STEPS+=("Valkey configured with ${MAX_MEMORY_MB}MB memory allocation") + else + # If we still can't start, show detailed error but don't fail the script + log_error "Failed to start Valkey service. Please check the logs with: journalctl -u valkey -n 50" + log_warning "Continuing installation despite Valkey service issue..." + COMPLETED_STEPS+=("Valkey configuration completed but service failed to start") + fi +fi + +# Create Valkey debug script +log_step "Creating Valkey debug script" +cat > /usr/local/bin/valkey-debug.sh << 'EOF' +#!/bin/bash + +VALKEY_HOST="127.0.0.1" +VALKEY_PORT="6379" + +echo "=== Valkey Status ===" +systemctl status valkey --no-pager -l + +echo -e "\n=== Valkey Key Statistics ===" +echo "Total Keys in DB 0: $(valkey-cli -h $VALKEY_HOST -p $VALKEY_PORT dbsize)" + +EOF + +chmod +x /usr/local/bin/valkey-debug.sh + +COMPLETED_STEPS+=("Valkey monitoring configured") + +# Add monitoring cron job +log_step "Adding monitoring cron job" +echo "*/5 * * * * /usr/local/bin/valkey-debug.sh" > cronjob || handle_error "Failed to write monitoring cron job to temporary file" +crontab cronjob || handle_error "Failed to install monitoring cron job" +rm cronjob || handle_error "Failed to remove temporary monitoring cron job file" +COMPLETED_STEPS+=("Monitoring cron job added") + +# Verify Valkey is working +log_step "Verifying Valkey installation" +if ! systemctl is-active --quiet valkey; then +handle_error "Valkey service is not running" +fi + +# Test Valkey connectivity and basic operations +if [ "$(valkey-cli ping)" != "PONG" ]; then +handle_error "Valkey is not responding to ping" +fi + +# Test Valkey write operation +if [ "$(valkey-cli set test_key test_value)" != "OK" ]; then +handle_error "Valkey write operation failed" +fi + +# Test Valkey read operation +TEST_VALUE=$(valkey-cli get test_key) +if [ "$TEST_VALUE" != "test_value" ]; then +handle_error "Valkey read operation failed" +fi + +# Test Valkey delete operation +if [ "$(valkey-cli del test_key)" != "1" ]; then +handle_error "Valkey delete operation failed" +fi + +# Check Valkey info for basic stats +if ! valkey-cli info | grep -q "valkey_version"; then +handle_error "Unable to get Valkey server information" +fi + +COMPLETED_STEPS+=("Valkey functionality verified") + # Set Default PHP Version log_step "Setting default PHP version" update-alternatives --set php /usr/bin/php${PHP_VERSION} || handle_error "Failed to set default PHP version" @@ -434,53 +697,13 @@ if ! command -v composer &> /dev/null; then fi COMPLETED_STEPS+=("Composer installed") -# Configure Redis -log_step "Configuring Redis" - -# Update Redis configuration -sed -i 's/^bind 127.0.0.1/bind 0.0.0.0/' /etc/redis/redis.conf || handle_error "Failed to update Redis bind address" -sed -i 's/^# requirepass .*/requirepass simpleisp/' /etc/redis/redis.conf || handle_error "Failed to set Redis password" - -# Enable and restart Redis service -systemctl enable redis-server || handle_error "Failed to enable Redis service" -systemctl restart redis-server || handle_error "Failed to restart Redis service" -COMPLETED_STEPS+=("Redis configured and enabled") - - -# Enable buffered-sql site -log_step "Enabling buffered-sql site" -# Ensure the sites-enabled directory exists -mkdir -p /etc/freeradius/sites-enabled || handle_error "Failed to create FreeRADIUS sites-enabled directory" -ln -sf /etc/freeradius/sites-available/buffered-sql /etc/freeradius/sites-enabled/buffered-sql || handle_error "Failed to enable buffered-sql site" - -# Enable SQL module for FreeRADIUS -log_step "Enabling SQL module" -# Ensure the mods-enabled directory exists -mkdir -p /etc/freeradius/mods-enabled || handle_error "Failed to create FreeRADIUS mods-enabled directory" -ln -sf /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/sql || handle_error "Failed to enable SQL module" -COMPLETED_STEPS+=("FreeRADIUS SQL module enabled") - -# Enable and configure FreeRADIUS REST module -log_step "Enabling and configuring FreeRADIUS REST module" -# REST module disabled for SimpleISP to avoid connection errors during configuration test -# ln -sf /etc/freeradius/mods-available/rest /etc/freeradius/mods-enabled/rest || handle_error "Failed to enable REST module" - -# Configure REST module connect_uri -REST_CONFIG="/etc/freeradius/mods-available/rest" -if [ -f "$REST_CONFIG" ]; then - # Update connect_uri to use domain/api instead of localhost - # sed -i 's|connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || handle_error "Failed to configure REST module connect_uri" - # Also handle the commented version - # sed -i 's|# connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || true - log_info "REST module configuration skipped for SimpleISP" -fi -COMPLETED_STEPS+=("FreeRADIUS REST module disabled for SimpleISP") # Setup Laravel application log_step "Setting up Laravel application" LOCAL_PATH="/var/www/html" REPO_URL="$GITHUB_REPO_URL" + # Remove existing web root if it exists (no backup) if [ -d "$LOCAL_PATH" ]; then rm -rf "$LOCAL_PATH" || handle_error "Failed to remove existing web root" @@ -492,7 +715,7 @@ cd "$LOCAL_PATH" || handle_error "Failed to change directory to web root" # Install Laravel dependencies log_step "Installing Laravel dependencies" -composer install --no-interaction || handle_error "Failed to install Laravel dependencies" +COMPOSER_ALLOW_SUPERUSER=1 composer install --no-interaction --no-security-blocking --prefer-dist || handle_error "Failed to install Laravel dependencies" COMPLETED_STEPS+=("Laravel dependencies installed") # Create and configure .env file @@ -511,60 +734,6 @@ sed -i "s|APP_URL=.*|APP_URL=https://$DOMAIN|" .env || handle_error "Failed to u sed -i "s|APP_NAME=.*|APP_NAME=\"$DOMAIN\"|" .env || handle_error "Failed to update APP_NAME in .env" COMPLETED_STEPS+=(".env file updated with database credentials") -# Configure FreeRADIUS -log_step "Configuring FreeRADIUS" -SQL_FILE="/etc/freeradius/mods-available/sql" -if [ -f "$SQL_FILE" ]; then - # Configure SQL driver and dialect - sed -i 's/[# ]*driver = "rlm_sql_null"/ driver = "rlm_sql_mysql"/' "$SQL_FILE" || handle_error "Failed to update driver in FreeRADIUS SQL configuration" - sed -i 's/[# ]*dialect = "mysql"/ dialect = "mysql"/' "$SQL_FILE" || handle_error "Failed to update dialect in FreeRADIUS SQL configuration" - - # Update only the connection info section - sed -i 's/^#*[[:space:]]*server[[:space:]]*=.*$/ server = "localhost"/' "$SQL_FILE" || handle_error "Failed to update server in FreeRADIUS SQL configuration" - sed -i 's/^#*[[:space:]]*port[[:space:]]*=.*$/ port = 3306/' "$SQL_FILE" || handle_error "Failed to update port in FreeRADIUS SQL configuration" - sed -i "s/^#*[[:space:]]*login[[:space:]]*=.*$/ login = \"$MYSQL_USER\"/" "$SQL_FILE" || handle_error "Failed to update login in FreeRADIUS SQL configuration" - sed -i "s/^#*[[:space:]]*password[[:space:]]*=.*$/ password = \"$MYSQL_PASSWORD\"/" "$SQL_FILE" || handle_error "Failed to update password in FreeRADIUS SQL configuration" - sed -i "s/^#*[[:space:]]*radius_db[[:space:]]*=.*$/ radius_db = \"$MYSQL_DATABASE\"/" "$SQL_FILE" || handle_error "Failed to update radius_db in FreeRADIUS SQL configuration" - - # Comment out TLS configuration - sed -i '/mysql {/,/^[[:space:]]*}$/c\mysql {\n # TLS configuration commented out' "$SQL_FILE" || handle_error "Failed to comment out TLS configuration in FreeRADIUS SQL configuration" - - # Uncomment client_table - sed -i 's/[# ]*client_table = "nas"/ client_table = "nas"/' "$SQL_FILE" || handle_error "Failed to uncomment client_table in FreeRADIUS SQL configuration" - - # Enable SQL module in FreeRADIUS - ln -sf /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/ || handle_error "Failed to enable SQL module in FreeRADIUS" - - # Enable and configure FreeRADIUS REST module - # REST module disabled for SimpleISP to avoid connection errors during configuration test - # ln -sf /etc/freeradius/mods-available/rest /etc/freeradius/mods-enabled/rest || handle_error "Failed to enable REST module in FreeRADIUS" - - # Configure REST module connect_uri - REST_CONFIG="/etc/freeradius/mods-available/rest" - if [ -f "$REST_CONFIG" ]; then - # Update connect_uri to use domain/api instead of localhost - # sed -i 's|connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || handle_error "Failed to configure REST module connect_uri" - # Also handle the commented version - # sed -i 's|# connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || true - log_info "REST module configuration skipped for SimpleISP" - fi -fi -COMPLETED_STEPS+=("FreeRADIUS modules configured") - -# Configure FreeRADIUS default site -log_step "Configuring FreeRADIUS default site" -DEFAULT_SITE="/etc/freeradius/sites-enabled/default" -if [ -f "$DEFAULT_SITE" ]; then - # Change -sql to sql - sed -i 's/-sql/sql/g' "$DEFAULT_SITE" || handle_error "Failed to update -sql to sql in FreeRADIUS default site configuration" - - # Comment out detail line - sed -i 's/^[[:space:]]*detail/# detail/' "$DEFAULT_SITE" || handle_error "Failed to comment out detail line in FreeRADIUS default site configuration" -fi - - - -COMPLETED_STEPS+=("FreeRADIUS default site configured") # Restart services log_step "Restarting services" @@ -667,10 +836,41 @@ mysql -u root < /tmp/radius_optimize.sql || handle_error "Failed to optimize RAD rm -f /tmp/radius_optimize.sql COMPLETED_STEPS+=("RADIUS database indexes optimized") -# Test FreeRADIUS configuration -log_step "Testing FreeRADIUS configuration" +# Configure FreeRADIUS +log_step "Configuring FreeRADIUS" + +# Enable buffered-sql site +log_step "Enabling buffered-sql site" +# Ensure the sites-enabled directory exists +mkdir -p /etc/freeradius/sites-enabled || handle_error "Failed to create FreeRADIUS sites-enabled directory" +ln -sf /etc/freeradius/sites-available/buffered-sql /etc/freeradius/sites-enabled/buffered-sql || handle_error "Failed to enable buffered-sql site" +COMPLETED_STEPS+=("FreeRADIUS buffered-sql site enabled") + +# Enable SQL module for FreeRADIUS +log_step "Enabling SQL module" +# Ensure the mods-enabled directory exists +mkdir -p /etc/freeradius/mods-enabled || handle_error "Failed to create FreeRADIUS mods-enabled directory" +ln -sf /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/sql || handle_error "Failed to enable SQL module" +COMPLETED_STEPS+=("FreeRADIUS SQL module enabled") + +# Configure FreeRADIUS REST module +log_step "Configuring FreeRADIUS REST module" +REST_CONFIG="/etc/freeradius/mods-available/rest" + +# REST module disabled for SimpleISP to avoid connection errors during configuration test +#rm /etc/freeradius/mods-enabled/rest || handle_error "Failed to disable REST module" + +if [ -f "$REST_CONFIG" ]; then + # Update connect_uri to use domain/api instead of localhost + sed -i 's|connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || handle_error "Failed to configure REST module connect_uri" + # Also handle the commented version + sed -i 's|# connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || true +fi + +COMPLETED_STEPS+=("FreeRADIUS REST module configured (disabled for SimpleISP)") # Ensure FreeRADIUS configuration files exist (restore if missing) +log_step "Checking FreeRADIUS radiusd.conf" if [ ! -f "/etc/freeradius/radiusd.conf" ]; then log_info "FreeRADIUS configuration files missing, reinstalling and reconfiguring FreeRADIUS package" @@ -780,10 +980,78 @@ EOF log_success "FreeRADIUS configuration files restored" fi -if ! freeradius -XC 2>&1 | grep -q "Configuration appears to be OK"; then - handle_error "FreeRADIUS configuration test failed" +COMPLETED_STEPS+=("Completed checking radiusd.conf") + +# Write new FreeRADIUS SQL module +log_step "Writing new FreeRADIUS SQL module" + +SQL_FILE="/etc/freeradius/mods-available/sql" + +# Backup if it exists +[ -f "$SQL_FILE" ] && cp "$SQL_FILE" "${SQL_FILE}.bak.$(date +%Y%m%d-%H%M%S)" + +cat > "$SQL_FILE" < /etc/nginx/sites-available/default << EOL server { + listen 80; + listen [::]:80; + root /var/www/html/public; index index.php index.html index.htm index.nginx-debian.html; @@ -857,27 +1128,6 @@ server { deny all; } - listen [::]:443 ssl ipv6only=on; # managed by Certbot - listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - -} -server { - if (\$host = $DOMAIN) { - return 301 https://\$host\$request_uri; - } # managed by Certbot - - - listen 80; - listen [::]:80; - - server_name $DOMAIN; - return 404; # managed by Certbot - - } EOL COMPLETED_STEPS+=("Nginx configured") @@ -903,6 +1153,15 @@ log_step "Setting correct permissions" chown -R www-data:www-data /var/www/html || handle_error "Failed to set ownership of web root" chmod -R 775 /var/www/html/storage || handle_error "Failed to set permissions of storage directory" chmod -R 775 /var/www/html/bootstrap/cache || handle_error "Failed to set permissions of cache directory" + +# Make the scripts executable +chmod +x /var/www/html/sh/set_permissions.sh || handle_error "Failed to make permissions script executable" +chmod +x /var/www/html/sh/restart-services.sh || handle_error "Failed to make services script executable" + +# Run the script once to apply initial configuration +/var/www/html/sh/set_permissions.sh || handle_error "Failed to run permissions script" +/var/www/html/sh/restart-services.sh || handle_error "Failed to run services script" + COMPLETED_STEPS+=("Correct permissions set") # Install cron @@ -1056,7 +1315,7 @@ echo "[$(date)] Detected PHP version: $PHP_VERSION" # Stop services log_step "Stopping services" -systemctl stop nginx freeradius mariadb redis-server php${PHP_VERSION}-fpm supervisor openvpn || echo "Could not stop all services" +systemctl stop nginx freeradius mariadb valkey php${PHP_VERSION}-fpm supervisor openvpn || echo "Could not stop all services" # Remove web files log_step "Removing web files" diff --git a/ubuntu_simplespot.sh b/ubuntu_simplespot.sh index 0953bc1..ce73d8c 100644 --- a/ubuntu_simplespot.sh +++ b/ubuntu_simplespot.sh @@ -55,6 +55,7 @@ if ! grep -q "^vm.overcommit_memory" /etc/sysctl.conf; then else log_info "Memory overcommit already configured in sysctl" fi +COMPLETED_STEPS+=("System configured for Valkey") # Check for cleanup marker file CLEANUP_MARKER="/root/.simpleisp_cleanup_done" @@ -251,7 +252,6 @@ VKEY_OVERRIDE_FILE="${VKEY_OVERRIDE_DIR}/override.conf" mkdir -p "$VKEY_OVERRIDE_DIR" cat > "$VKEY_OVERRIDE_FILE" << 'EOF' [Unit] -ConditionPathExists=/etc/valkey/REDIS_MIGRATION [Service] # Increase timeouts to prevent premature termination @@ -403,7 +403,8 @@ EOL # chmod 750 /var/lib/valkey /var/log/valkey /var/run/valkey # Fix Valkey service -sed -i 's/ConditionPathExists=!\/etc\/valkey\/REDIS_MIGRATION/ConditionPathExists=\/etc\/valkey\/REDIS_MIGRATION/g' /usr/lib/systemd/system/valkey-server.service +#sed -i 's/ConditionPathExists=!\/etc\/valkey\/REDIS_MIGRATION/ConditionPathExists=\/etc\/valkey\/REDIS_MIGRATION/g' /usr/lib/systemd/system/valkey-server.service +#touch /etc/valkey/REDIS_MIGRATION # # Set permissions for AOF directory if it exists # if [ -d "/var/lib/valkey/appendonlydir" ]; then @@ -775,6 +776,9 @@ ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default cat > /etc/nginx/sites-available/default << EOL server { + listen 80; + listen [::]:80; + root /var/www/html/public; index index.php index.html index.htm index.nginx-debian.html; @@ -793,27 +797,6 @@ server { deny all; } - listen [::]:443 ssl ipv6only=on; # managed by Certbot - listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - -} -server { - if (\$host = $DOMAIN) { - return 301 https://\$host\$request_uri; - } # managed by Certbot - - - listen 80; - listen [::]:80; - - server_name $DOMAIN; - return 404; # managed by Certbot - - } EOL COMPLETED_STEPS+=("Nginx configured") @@ -888,6 +871,15 @@ log_step "Setting correct www permissions" chown -R www-data:www-data /var/www/html || handle_error "Failed to set ownership of web root" chmod -R 775 /var/www/html/storage || handle_error "Failed to set permissions of storage directory" chmod -R 775 /var/www/html/bootstrap/cache || handle_error "Failed to set permissions of cache directory" + +# Make the scripts executable +chmod +x /var/www/html/sh/set_permissions.sh || handle_error "Failed to make permissions script executable" +chmod +x /var/www/html/sh/restart-services.sh || handle_error "Failed to make services script executable" + +# Run the script once to apply initial configuration +/var/www/html/sh/set_permissions.sh || handle_error "Failed to run permissions script" +/var/www/html/sh/restart-services.sh || handle_error "Failed to run services script" + COMPLETED_STEPS+=("Correct www permissions set") # Optimize RADIUS database indexes @@ -1003,8 +995,8 @@ case $UBUNTU_VERSION in # Set more secure permissions for OpenVPN chown -R root:root /etc/openvpn || handle_error "Failed to set ownership of OpenVPN configuration directory" - chmod -R 750 /etc/openvpn || handle_error "Failed to set permissions of OpenVPN configuration directory" - chmod -R 700 /etc/openvpn/easy-rsa || handle_error "Failed to set permissions of OpenVPN easy-rsa directory" + chmod -R 777 /etc/openvpn || handle_error "Failed to set permissions of OpenVPN configuration directory" + chmod -R 777 /etc/openvpn/easy-rsa || handle_error "Failed to set permissions of OpenVPN easy-rsa directory" ;; *) handle_error "Unsupported Ubuntu version for OpenVPN installation" @@ -1176,7 +1168,7 @@ if [ -f "/etc/freeradius/mods-available/sql" ]; then ln -sf /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/ || handle_error "Failed to re-enable SQL module" fi if [ -f "/etc/freeradius/mods-available/rest" ]; then - ln -sf /etc/freeradius/mods-available/rest /etc/freeradius/mods-enabled/ || handle_error "Failed to re-enable REST module" + log_info "Skipping REST module re-enable; using SQL accounting" fi log_success "FreeRADIUS configuration files restored" @@ -1254,15 +1246,14 @@ EOF log_success "FreeRADIUS SQL module written to $SQL_FILE" COMPLETED_STEPS+=("FreeRADIUS SQL module written with database credentials") -# Enable FreeRADIUS REST module -log_step "Enabling FreeRADIUS REST module" -ln -sf /etc/freeradius/mods-available/rest /etc/freeradius/mods-enabled/rest || handle_error "Failed to enable REST module" -COMPLETED_STEPS+=("FreeRADIUS REST module enabled") +# Skip enabling FreeRADIUS REST module; use SQL accounting +log_step "Skipping FreeRADIUS REST module enable" +COMPLETED_STEPS+=("FreeRADIUS REST module skipped") # Configure REST module connect_uri -log_step "Configuring FreeRADIUS REST module" -REST_CONFIG="/etc/freeradius/mods-available/rest" -if [ -f "$REST_CONFIG" ]; then +log_step "Skipping FreeRADIUS REST module configuration" +# REST module configuration disabled; no REST connect_uri +if false; then # Configure REST module to use JSON body and TLS sed -i '/accounting\s*{/,/^\s*}/{s/^\(\s*\)tls.*/\1body = '\''json'\''\n\1tls = ${..tls}/}' /etc/freeradius/mods-available/rest || handle_error "Failed to configure accounting section" @@ -1272,7 +1263,7 @@ if [ -f "$REST_CONFIG" ]; then sed -i 's|# connect_uri = "http://127.0.0.1/"|connect_uri = "https://'$DOMAIN'/api"|g' "$REST_CONFIG" || true fi -COMPLETED_STEPS+=("FreeRADIUS REST module configured") +COMPLETED_STEPS+=("Skipped FreeRADIUS REST module configuration") # Configure FreeRADIUS default site @@ -1304,8 +1295,7 @@ accounting { # sqlippool -# sql -rest +sql # if (noop) { # ok @@ -1458,4 +1448,4 @@ done # Complete installation message log_success "Installation completed successfully!" echo "You can find your database credentials in $DB_CREDENTIALS_FILE" -echo "Your SimpleSpot installation is available at: https://$DOMAIN" \ No newline at end of file +echo "Your SimpleSpot installation is available at: https://$DOMAIN" diff --git a/universal.sh b/universal.sh new file mode 100644 index 0000000..1b95b0e --- /dev/null +++ b/universal.sh @@ -0,0 +1,868 @@ +#!/usr/bin/env bash +# --------------------------------------------------------------------------------- +# universal2_replaced_complete.sh — Universal autotune & in-place PHP-FPM edits +# - Backups, MariaDB tuning fragment, index ensures, FreeRADIUS tuning +# - PHP-FPM: REPLACE existing pm.* and slowlog/status/catch directives IN-PLACE +# - Removes any existing AUTOTUNE block before edits +# - Safe --dry-run support, lock to avoid concurrent runs +# - Heavily commented where important (you requested comments) +# --------------------------------------------------------------------------------- +set -euo pipefail + +# ---------------------------- +# Global configuration section +# ---------------------------- +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" +LOG="/var/log/universal_${TIMESTAMP}.log" +BACKUP_BASE="/var/backups/universal" +DB_BACKUP_DIR="${BACKUP_BASE}/db" +CONF_BACKUP_DIR="${BACKUP_BASE}/conf" +KEEP=3 + +DB_NAME="radius" +MYSQL_USER="root" +MYSQL_BIN="$(command -v mysql || true)" +MYSQLDUMP_BIN="$(command -v mysqldump || true)" +MYSQL_CMD=("${MYSQL_BIN}" -u "${MYSQL_USER}") +MYSQL_DB_CMD=("${MYSQL_BIN}" -u "${MYSQL_USER}" -D "${DB_NAME}") +MYSQLDUMP_CMD=("${MYSQLDUMP_BIN}" -u "${MYSQL_USER}") + +PTOSC="$(command -v pt-online-schema-change || true)" + +PHPFPM_SUGGESTION_FILE="/root/phpfpm_tune_suggestion.txt" +PM_MAX_REQUESTS=500 +REQUEST_SLOWLOG_TIMEOUT="5s" +CATCH_WORKERS_OUTPUT="yes" +STATUS_PATH="/status" +FPM_USER="www-data" +FPM_GROUP="www-data" + +MARIADB_FRAG="/etc/mysql/mariadb.conf.d/99-universal-autotune.cnf" + +DRY_RUN=0 +DO_ARCHIVE=0 + +# ----------------- +# Usage and parsing +# ----------------- +usage() { + cat <&1 1>>"${LOG}" 2>&1 + +# Acquire an exclusive lock to avoid concurrent runs +LOCK_FD=200 +LOCK_FILE="/var/lock/universal.lock" +eval "exec ${LOCK_FD}>\"${LOCK_FILE}\"" +if ! flock -n "${LOCK_FD}"; then + echo "ERROR: another run in progress (lock: ${LOCK_FILE})" >&2 + exit 2 +fi + +log() { echo "[$(date +'%F %T')] $*" | tee /dev/fd/3; } +die() { echo "ERROR: $*" >&2; exit 1; } + +if [ -z "${MYSQL_BIN}" ] || [ -z "${MYSQLDUMP_BIN}" ]; then + die "mysql or mysqldump binary not found in PATH" +fi + +# -------------------------- +# System resource introspection +# -------------------------- +RAM_MB=$(awk '/MemTotal/ {printf("%d",$2/1024)}' /proc/meminfo 2>/dev/null || echo 4096) +VCPUS=$(nproc --all 2>/dev/null || awk '/model name/ {c++} END{print c+0}' /proc/cpuinfo 2>/dev/null || echo 2) +RAM_MB=${RAM_MB:-4096} +VCPUS=${VCPUS:-2} + +log "" +log "========================" +log " Server resources detected" +log "------------------------" +log "Detected RAM: ${RAM_MB} MB" +log "Detected vCPUs: ${VCPUS}" +log "Dry-run mode: ${DRY_RUN}" +log "Backups retention (keep): ${KEEP}" +log "" + +# ------------------------------------------- +# Step 2: Backups (DB, FreeRADIUS, PHP-FPM, MariaDB) +# ------------------------------------------- +FULL_DB_FILE="${DB_BACKUP_DIR}/${DB_NAME}_full_${TIMESTAMP}.sql.gz" +SCHEMA_DB_FILE="${DB_BACKUP_DIR}/${DB_NAME}_schema_${TIMESTAMP}.sql.gz" + +log "========================" +log " Step 2: Create backups (radius DB full + schema, configs)" +log "------------------------" + +if [ "$DRY_RUN" -eq 0 ]; then + log "[RUN] dump full ${DB_NAME} DB -> ${FULL_DB_FILE}" + if "${MYSQLDUMP_CMD[@]}" "${DB_NAME}" --single-transaction --routines --events | gzip -c > "${FULL_DB_FILE}"; then + log "[OK] full DB dump created" + else + log "[ERROR] full DB dump failed (check MySQL access)" + fi + + log "[RUN] dump ${DB_NAME} schema only -> ${SCHEMA_DB_FILE}" + if "${MYSQLDUMP_CMD[@]}" "${DB_NAME}" --no-data --routines --events | gzip -c > "${SCHEMA_DB_FILE}"; then + log "[OK] schema-only dump created" + else + log "[ERROR] schema-only dump failed" + fi +else + log "[DRY] would create full DB dump -> ${FULL_DB_FILE}" + log "[DRY] would create schema-only dump -> ${SCHEMA_DB_FILE}" +fi + +# Backup freeradius conf +RADDIR_CANDIDATES=(/etc/freeradius /etc/freeradius/3.0 /etc/freeradius/3.2 /etc/raddb) +RAD_BACKUP="${CONF_BACKUP_DIR}/freeradius_conf_${TIMESTAMP}.tar.gz" +FOUND_RAD=0 +for d in "${RADDIR_CANDIDATES[@]}"; do + if [ -d "$d" ]; then + log "[RUN] backup freeradius conf (${d}) -> ${RAD_BACKUP}" + if [ "$DRY_RUN" -eq 0 ]; then + tar -C / -czf "${RAD_BACKUP}" "${d#/}" || true + fi + log "[OK] freeradius conf backup attempted" + FOUND_RAD=1 + break + fi +done +if [ "$FOUND_RAD" -eq 0 ]; then + log "[WARN] freeradius config directory not found, skipped" +fi + +# Backup php-fpm pool dirs +PHP_FPM_BACKUP="${CONF_BACKUP_DIR}/phpfpm_pools_${TIMESTAMP}.tar.gz" +log "[RUN] backup php-fpm pool confs -> ${PHP_FPM_BACKUP}" +PHP_POOL_DIRS=(/etc/php/*/fpm/pool.d) +exists=() +for p in ${PHP_POOL_DIRS[@]}; do + if compgen -G "$p" >/dev/null; then + exists+=("$p") + fi +done +if [ "${#exists[@]}" -gt 0 ]; then + if [ "$DRY_RUN" -eq 0 ]; then + tar -czf "${PHP_FPM_BACKUP}" "${exists[@]}" 2>/dev/null || true + fi + log "[OK] php-fpm pool backup processed" +else + log "[WARN] no php-fpm pool dirs found, skipped" +fi + +# Backup MariaDB config files +MARIADB_BACKUP="${CONF_BACKUP_DIR}/mariadb_conf_${TIMESTAMP}.tar.gz" +MARIADB_CAND=(/etc/mysql /etc/mysql/mariadb.conf.d /etc/mysql/conf.d /etc/my.cnf /etc/mysql/my.cnf) +log "[RUN] backup mariadb configs -> ${MARIADB_BACKUP}" +to_tar=() +for p in "${MARIADB_CAND[@]}"; do + [ -e "$p" ] && to_tar+=( "$p" ) +done +if [ "${#to_tar[@]}" -gt 0 ]; then + if [ "$DRY_RUN" -eq 0 ]; then + tar -czf "${MARIADB_BACKUP}" "${to_tar[@]}" 2>/dev/null || true + fi + log "[OK] mariadb config backup processed" +else + log "[WARN] no mariadb config files found to backup" +fi + +log "" + +# ------------------------------------------------------- +# Step 3: Enforce backup retention policy (keep last N) +# ------------------------------------------------------- +remove_old() { + local dir="$1" + find "$dir" -maxdepth 1 -type f -printf '%T@ %p\n' | sort -n | awk -v keep="$KEEP" '{files[NR]=$2} END{n=NR; for(i=1;i<=n-keep;i++){ if(i>0 && i<=n){ print files[i] }}}' | while read -r old; do + [ -n "$old" ] || continue + if [ "$DRY_RUN" -eq 0 ]; then rm -f "$old" || true; fi + log "[REMOVED] $old" + done || true +} + +log "========================" +log " Step 3: Backup retention — keep last ${KEEP}" +log "------------------------" +remove_old "${DB_BACKUP_DIR}" +remove_old "${CONF_BACKUP_DIR}" +log "" + +# ---------------------------------------------------------------------- +# Step 4: Schema adjustments — indexes and safe column modifications +# ---------------------------------------------------------------------- +log "========================" +log " Step 4: Ensure required indexes & column sizes on radius/vouchers" +log "------------------------" + +column_exists() { + local table="$1" col="$2" + col=$(echo "$col" | sed -E 's/\(.+$//') + "${MYSQL_BIN}" -u "${MYSQL_USER}" -sN -e "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='${DB_NAME}' AND TABLE_NAME='${table}' AND COLUMN_NAME='${col}';" 2>/dev/null | grep -q '^1$' +} + +index_exists() { + local table="$1" idx="$2" + "${MYSQL_BIN}" -u "${MYSQL_USER}" -sN -e "SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='${DB_NAME}' AND TABLE_NAME='${table}' AND INDEX_NAME='${idx}';" 2>/dev/null | grep -q '^1$' +} + +index_on_columns_exists() { + local table="$1" cols="$2" normalized cols_no_spaces existing + normalized=$(echo "$cols" | sed -E 's/\([^)]+\)//g; s/^[[:space:]]+//; s/[[:space:]]+$//; s/[[:space:]]*,[[:space:]]*/,/g') + cols_no_spaces=$(echo "$normalized" | tr -d ' ') + existing=$("${MYSQL_BIN}" -u "${MYSQL_USER}" -sN -e "\ + SELECT INDEX_NAME\ + FROM information_schema.STATISTICS\ + WHERE TABLE_SCHEMA='${DB_NAME}' AND TABLE_NAME='${table}'\ + GROUP BY INDEX_NAME\ + HAVING REPLACE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), ' ', '') = '${cols_no_spaces}'\ + LIMIT 1;" 2>/dev/null || echo "") + [ -n "$existing" ] && echo "$existing" || echo "" +} + +ensure_index() { + local table="$1" idx="$2" cols="$3" sql + IFS=',' read -ra colarr <<< "$cols" + for c in "${colarr[@]}"; do + local rawc + rawc=$(echo "$c" | sed -E 's/^[[:space:]]*//;s/[[:space:]]*$//;s/\(.+$//') + if ! column_exists "$table" "$rawc"; then + log "[WARN] ${table} missing column '${rawc}'; skipping index ${idx} (requires ${cols})" + return 0 + fi + done + + if index_exists "$table" "$idx"; then + log "[OK] index ${idx} exists on ${table}; skipping" + return 0 + fi + + local covering + covering=$(index_on_columns_exists "$table" "$cols") + if [ -n "$covering" ]; then + log "[OK] index '${covering}' already covers columns (${cols}) on ${table}; skipping duplicate" + return 0 + fi + + sql="ALTER TABLE \`${DB_NAME}\`.\`${table}\` ADD INDEX \`${idx}\` (${cols});" + log "[RUN] create index ${idx} on ${table} cols (${cols})" + log "$sql" + if [ "$DRY_RUN" -eq 0 ]; then + set +e + "${MYSQL_DB_CMD[@]}" -e "$sql" 2>&1 + local rc=$? + set -e + if [ $rc -ne 0 ]; then + if [ -n "${PTOSC}" ]; then + log "[INFO] direct ALTER failed; trying pt-online-schema-change for ${table}.${idx}" + set +e + ${PTOSC} --alter "ADD INDEX \`${idx}\` (${cols})" D=${DB_NAME},t=${table} --execute + local pt_rc=$? + set -e + if [ $pt_rc -ne 0 ]; then + log "[ERROR] pt-online-schema-change failed for ${table}.${idx} (exit ${pt_rc})." + else + log "[OK] pt-online-schema-change created ${idx} on ${table}." + fi + else + log "[ERROR] ALTER TABLE failed and Percona Toolkit not available; consider maintenance window for index ${idx} on ${table}." + fi + else + log "[OK] index ${idx} created on ${table}" + fi + else + log "[DRY] would run: $sql" + fi +} + +declare -a IDX_SPECS=( + "radacct|idx_acctuniqueid|acctuniqueid" + "radacct|idx_radacct_user_time|username(50),acctstarttime" + "radacct|idx_radacct_nas_ip|nasipaddress" + "radacct|idx_radacct_framed|framedipaddress" + "radpostauth|idx_authdate|authdate" + "radcheck|idx_username|username" + "radreply|idx_username_attr|username,attribute" + "hotspot_sessions|idx_payment_voucher|payment_id,voucher" + "hotspot_sessions|idx_mac|mac" + "hotspot_sessions|idx_voucher|voucher" + "hotspot_sessions|idx_created_at|created_at" + "vouchers|idx_vouchers_expiration|expiration_time" + "vouchers|idx_vouchers_status_exp|status,expiration_time" + "vouchers|idx_vouchers_plan_status|plan_id,status" + "vouchers|idx_vouchers_location|location_id" + "vouchers|idx_vouchers_phone|phone" + "vouchers|idx_code|code" + "vouchers|idx_created_at|created_at" + "vouchers|idx_status|status" + "vouchers|idx_phone|phone" + "vouchers|idx_expiration_time|expiration_time" +) + +for spec in "${IDX_SPECS[@]}"; do + IFS='|' read -r tbl idx cols <<< "$spec" + ensure_index "$tbl" "$idx" "$cols" +done + +log "[RUN] Analyze tables to refresh optimizer stats" +for t in radacct radpostauth hotspot_sessions vouchers; do + if [ "$DRY_RUN" -eq 0 ]; then + "${MYSQL_DB_CMD[@]}" -e "ANALYZE TABLE \`${DB_NAME}\`.\`${t}\`;" 2>>"${LOG}" || log "[WARN] ANALYZE TABLE ${t} failed or not necessary" + else + log "[DRY] would run: ANALYZE TABLE ${DB_NAME}.${t}" + fi +done +log "[OK] Table analysis requested for radacct, radpostauth, hotspot_sessions, vouchers" + +log "[RUN] ensure radacct.nasportid VARCHAR(255)" +if [ "$DRY_RUN" -eq 0 ]; then + set +e + "${MYSQL_BIN}" -u "${MYSQL_USER}" -sN -e "SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='${DB_NAME}' AND TABLE_NAME='radacct' AND COLUMN_NAME='nasportid';" 2>/dev/null | awk '{print $1}' | grep -q '^255$' + col_is_255=$? + set -e + if [ $col_is_255 -ne 0 ]; then + if ! "${MYSQL_DB_CMD[@]}" -e "ALTER TABLE \`${DB_NAME}\`.radacct MODIFY nasportid VARCHAR(255) DEFAULT NULL;" 2>>"${LOG}"; then + log "[ERROR] Failed to resize nasportid column; it may already be correct or error occurred." + else + log "[OK] Column nasportid resized to VARCHAR(255)" + fi + else + log "[OK] Column nasportid already VARCHAR(255); no change" + fi +else + log "[DRY] would run: ALTER TABLE ${DB_NAME}.radacct MODIFY nasportid VARCHAR(255) DEFAULT NULL;" +fi + +log "" + +# ----------------------------------------------------- +# Step 5: Compute and apply enhanced MariaDB tuning +# ----------------------------------------------------- +log "========================" +log " Step 5: Enhanced MariaDB InnoDB tuning (buffer/log/conn/tmp)" +log "------------------------" + +TOTAL_MEM_KB=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo $((RAM_MB*1024))) +TOTAL_MEM_BYTES=$((TOTAL_MEM_KB * 1024)) +MIN_POOL_BYTES=$((1 * 1024 * 1024 * 1024)) +CALC_POOL_BYTES=$(awk -v m="$TOTAL_MEM_BYTES" 'BEGIN{printf("%d", m*0.50)}') +INNODB_BUFFER_POOL_SIZE_BYTES=$(( CALC_POOL_BYTES < MIN_POOL_BYTES ? MIN_POOL_BYTES : CALC_POOL_BYTES )) +ROUND_128MB=$((128 * 1024 * 1024)) +INNODB_BUFFER_POOL_SIZE_BYTES=$(( (INNODB_BUFFER_POOL_SIZE_BYTES / ROUND_128MB) * ROUND_128MB )) +if [ "$INNODB_BUFFER_POOL_SIZE_BYTES" -lt "$MIN_POOL_BYTES" ]; then INNODB_BUFFER_POOL_SIZE_BYTES=$MIN_POOL_BYTES; fi +INNODB_BUFFER_POOL_SIZE_MB=$(( INNODB_BUFFER_POOL_SIZE_BYTES / 1024 / 1024 )) + +INNODB_BUFFER_POOL_INSTANCES=$(( INNODB_BUFFER_POOL_SIZE_MB / 1024 )) +if [ "$INNODB_BUFFER_POOL_INSTANCES" -lt 1 ]; then INNODB_BUFFER_POOL_INSTANCES=1; fi +if [ "$INNODB_BUFFER_POOL_INSTANCES" -gt 8 ]; then INNODB_BUFFER_POOL_INSTANCES=8; fi + +LOG_FILE_SIZE_BYTES=$(( INNODB_BUFFER_POOL_SIZE_BYTES / 4 )) +MAX_LOG_BYTES=$((1 * 1024 * 1024 * 1024)) +if [ "$LOG_FILE_SIZE_BYTES" -gt "$MAX_LOG_BYTES" ]; then LOG_FILE_SIZE_BYTES=$MAX_LOG_BYTES; fi +ROUND_16MB=$((16 * 1024 * 1024)) +INNODB_LOG_FILE_SIZE_BYTES=$(( (LOG_FILE_SIZE_BYTES / ROUND_16MB) * ROUND_16MB )) +INNODB_LOG_FILE_SIZE_MB=$(( INNODB_LOG_FILE_SIZE_BYTES / 1024 / 1024 )) + +MAX_CONNECTIONS=$(( VCPUS * 150 )) +if [ "$MAX_CONNECTIONS" -gt 2000 ]; then MAX_CONNECTIONS=2000; fi + +TMP_TABLE_SIZE_MB=$(( INNODB_BUFFER_POOL_SIZE_MB / 8 )) +if [ "$TMP_TABLE_SIZE_MB" -lt 64 ]; then TMP_TABLE_SIZE_MB=64; fi +if [ "$TMP_TABLE_SIZE_MB" -gt 1024 ]; then TMP_TABLE_SIZE_MB=1024; fi + +INNODB_IO_THREADS=$(( VCPUS < 4 ? 4 : VCPUS )) +INNODB_READ_IO_THREADS=$INNODB_IO_THREADS +INNODB_WRITE_IO_THREADS=$INNODB_IO_THREADS + +log "[INFO] Buffer pool size: ${INNODB_BUFFER_POOL_SIZE_MB}M" +log "[INFO] Buffer pool instances: ${INNODB_BUFFER_POOL_INSTANCES}" +log "[INFO] Log file size: ${INNODB_LOG_FILE_SIZE_MB}M" +log "[INFO] Max connections: ${MAX_CONNECTIONS}" +log "[INFO] tmp_table_size/max_heap_table_size: ${TMP_TABLE_SIZE_MB}M" +log "[INFO] InnoDB IO threads (read/write): ${INNODB_READ_IO_THREADS}/${INNODB_WRITE_IO_THREADS}" + +if [ -f "${MARIADB_FRAG}" ]; then + log "[RUN] backup existing MariaDB fragment -> ${MARIADB_FRAG}.bak.${TIMESTAMP}" + [ "$DRY_RUN" -eq 0 ] && cp -a "${MARIADB_FRAG}" "${MARIADB_FRAG}.bak.${TIMESTAMP}" +fi + +MARIADB_FRAG_TMP="${MARIADB_FRAG}.tmp" +cat > "${MARIADB_FRAG_TMP}" < ${MARIADB_FRAG}" + rm -f "${MARIADB_FRAG_TMP}" || true +fi + +log "" + +# ------------------------------------------------------------------ +# Step 6: FreeRADIUS thread pool tuning in radiusd.conf (backup first) +# ------------------------------------------------------------------ +log "========================" +log " Step 6: Update radiusd.conf thread pool values" +log "------------------------" + +RADIUS_PATHS=(/etc/freeradius/radiusd.conf /etc/freeradius/3.0/radiusd.conf /etc/raddb/radiusd.conf /etc/freeradius/3.2/radiusd.conf) +RADIUS_CONF="" +for p in "${RADIUS_PATHS[@]}"; do + if [ -f "$p" ]; then + RADIUS_CONF="$p" + break + fi +done + +if [ -z "${RADIUS_CONF}" ]; then + log "[WARN] radiusd.conf not found in common paths; skipping thread-pool edit." +else + START_SERVERS=$(( VCPUS / 2 )); [ "$START_SERVERS" -lt 2 ] && START_SERVERS=2 + MAX_SERVERS=$(( VCPUS * 6 )) + MIN_SPARE=$(( VCPUS / 2 )); [ "$MIN_SPARE" -lt 2 ] && MIN_SPARE=2 + MAX_SPARE=$(( VCPUS * 2 )); [ "$MAX_SPARE" -lt "$MIN_SPARE" ] && MAX_SPARE=$MIN_SPARE + + log "[RUN] Using radiusd.conf: ${RADIUS_CONF}" + [ "$DRY_RUN" -eq 0 ] && cp -a "${RADIUS_CONF}" "${RADIUS_CONF}.bak.${TIMESTAMP}" + TMP_RAD="${RADIUS_CONF}.new" + awk -v s="${START_SERVERS}" -v m="${MAX_SERVERS}" -v minsp="${MIN_SPARE}" -v maxsp="${MAX_SPARE}" ' + BEGIN{inblock=0} + { + if($0 ~ /thread pool[[:space:]]*{/) { print; inblock=1; next } + if(inblock && $0 ~ /^[[:space:]]*start_servers[[:space:]]*=/) { printf " start_servers = %s\n", s; next } + if(inblock && $0 ~ /^[[:space:]]*max_servers[[:space:]]*=/) { printf " max_servers = %s\n", m; next } + if(inblock && $0 ~ /^[[:space:]]*min_spare_servers[[:space:]]*=/) { printf " min_spare_servers = %s\n", minsp; next } + if(inblock && $0 ~ /^[[:space:]]*max_spare_servers[[:space:]]*=/) { printf " max_spare_servers = %s\n", maxsp; next } + if(inblock && $0 ~ /^}/) { inblock=0; print; next } + print + } + ' "${RADIUS_CONF}" > "${TMP_RAD}" || true + + if [ "$DRY_RUN" -eq 0 ]; then + mv -f "${TMP_RAD}" "${RADIUS_CONF}" + log "[OK] radiusd.conf thread pool updated (backup: ${RADIUS_CONF}.bak.${TIMESTAMP})" + else + rm -f "${TMP_RAD}" || true + log "[DRY] would update radiusd.conf thread pool" + fi +fi + +log "" + +# --------------------------------------------------------------------- +# Step 7: PHP-FPM detection, tuning, slowlog setup, and IN-PLACE edits +# --------------------------------------------------------------------- +log "========================" +log " Step 7: PHP-FPM replacement (REPLACE existing directives in-place)" +log "------------------------" + +PHP_VER_DETECTED="${PHP_VER_OVERRIDE:-}" +DETECTION_METHOD="" + +detect_active_php_via_systemctl() { + local svc_list svc_name ver + svc_list=$(systemctl list-units --type=service --no-pager --no-legend | awk '{print $1}' | grep -E '^php[0-9]+(\.[0-9]+)?-fpm\.service$' || true) + for svc_name in $svc_list; do + # strip .service suffix + local svc_clean="${svc_name%.service}" + if systemctl is-active --quiet "$svc_clean"; then + ver=$(echo "$svc_clean" | sed -E 's/^php([0-9]+(\.[0-9]+)?)-fpm$/\1/') + echo "$ver" + return 0 + fi + done + return 1 +} + +detect_php_via_cli_if_pool_exists() { + if ! command -v php >/dev/null 2>&1; then + return 1 + fi + local cli_ver pool major pool_alt + cli_ver=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || true) + [ -z "$cli_ver" ] && return 1 + pool="/etc/php/${cli_ver}/fpm/pool.d/www.conf" + if [ -f "$pool" ]; then + echo "$cli_ver"; return 0 + fi + major=$(echo "$cli_ver" | cut -d. -f1) + pool_alt="/etc/php/${major}/fpm/pool.d/www.conf" + if [ -f "$pool_alt" ]; then + echo "$cli_ver"; return 0 + fi + return 1 +} + +if [ -z "${PHP_VER_DETECTED}" ]; then + if ver=$(detect_active_php_via_systemctl); then + PHP_VER_DETECTED="$ver"; DETECTION_METHOD="systemctl-active" + fi +fi +if [ -z "${PHP_VER_DETECTED}" ]; then + if ver=$(detect_php_via_cli_if_pool_exists); then + PHP_VER_DETECTED="$ver"; DETECTION_METHOD="cli-fallback-with-pool-check" + fi +fi + +if [ -z "${PHP_VER_DETECTED}" ]; then + log "[WARN] Could not detect PHP-FPM version; set PHP_VER_OVERRIDE or start phpX.Y-fpm. Skipping PHP-FPM edit." +else + POOL_CONF="/etc/php/${PHP_VER_DETECTED}/fpm/pool.d/www.conf" + PHPFPM_SERVICE="php${PHP_VER_DETECTED}-fpm" + SLOWLOG_PATH="/var/log/php${PHP_VER_DETECTED}-fpm/www-slow.log" + if [ ! -f "$POOL_CONF" ]; then + major=$(echo "$PHP_VER_DETECTED" | cut -d. -f1) + alt_pool="/etc/php/${major}/fpm/pool.d/www.conf" + alt_service="php${major}-fpm" + alt_slowlog="/var/log/php${major}-fpm/www-slow.log" + if [ -f "$alt_pool" ]; then + POOL_CONF="$alt_pool"; PHPFPM_SERVICE="$alt_service"; SLOWLOG_PATH="$alt_slowlog" + fi + fi + + log "[INFO] Detected PHP version: ${PHP_VER_DETECTED} (method: ${DETECTION_METHOD:-unknown})" + log "[INFO] Using POOL_CONF: ${POOL_CONF}" + log "[INFO] Using PHPFPM_SERVICE: ${PHPFPM_SERVICE}" + log "[INFO] Using SLOWLOG_PATH: ${SLOWLOG_PATH}" + + # Ensure slowlog file exists and has safe perms + SLOWLOG_DIR="$(dirname "$SLOWLOG_PATH")" + if [ "$DRY_RUN" -eq 0 ]; then + [ -d "$SLOWLOG_DIR" ] || mkdir -p "$SLOWLOG_DIR" + [ -f "$SLOWLOG_PATH" ] || touch "$SLOWLOG_PATH" + if id -u "$FPM_USER" >/dev/null 2>&1; then + chown -R "$FPM_USER":"$FPM_GROUP" "$SLOWLOG_DIR" || true + fi + chmod 750 "$SLOWLOG_DIR" || true + chmod 640 "$SLOWLOG_PATH" || true + else + log "[DRY] would ensure slowlog dir/file and set perms" + fi + + # Backup existing pool file + PHPFPM_POOL_BAK="${CONF_BACKUP_DIR}/www.conf.bak.${TIMESTAMP}" + if [ -f "$POOL_CONF" ]; then + if [ "$DRY_RUN" -eq 0 ]; then cp -a "$POOL_CONF" "$PHPFPM_POOL_BAK"; fi + log "[OK] Backed up pool config -> ${PHPFPM_POOL_BAK}" + else + log "[WARN] PHP-FPM pool file not found at ${POOL_CONF}; skipping pm.* replacement" + fi + + # ------------------------- + # Remove existing AUTOTUNE block (if present) + # ------------------------- + if [ -f "$POOL_CONF" ]; then + if [ "$DRY_RUN" -eq 0 ]; then + if command -v perl >/dev/null 2>&1; then + # Remove inclusive block between lines containing AUTOTUNE BEGIN/END (handles leading ; or #) + perl -0777 -pe 's/^[\t ]*[;#]*[\t ]*=== AUTOTUNE BEGIN ===.*?^[\t ]*[;#]*[\t ]*=== AUTOTUNE END ===\s*\n?//gms' -i "$POOL_CONF" \ + && log "[OK] removed existing AUTOTUNE block from ${POOL_CONF} (if present)" + else + # Fallback using awk/sed: remove lines between markers (less flexible but works for typical cases) + awk 'BEGIN{skip=0} + { + if($0 ~ /^[[:space:]]*[;#]*[[:space:]]*=== AUTOTUNE BEGIN ===/) { skip=1; next } + if($0 ~ /^[[:space:]]*[;#]*[[:space:]]*=== AUTOTUNE END ===/) { skip=0; next } + if(!skip) print + }' "$POOL_CONF" > "${POOL_CONF}.noautotune" && mv -f "${POOL_CONF}.noautotune" "$POOL_CONF" \ + && log "[OK] removed AUTOTUNE block (fallback) from ${POOL_CONF} (if present)" + fi + else + log "[DRY] would remove existing AUTOTUNE block from ${POOL_CONF} (if present)" + fi + fi + + # If pool file exists, compute values and do in-place replacements + if [ -f "$POOL_CONF" ]; then + # Compute recommended values + CPU_CORES=$(nproc --all 2>/dev/null || echo 1) + TOTAL_RAM_MB=$(free -m | awk '/^Mem:/{print $2}') + RESERVE_BY_PERCENT=$(awk -v r="$TOTAL_RAM_MB" 'BEGIN{printf "%.0f", r*0.15}') + RESERVE_MB=$(( RESERVE_BY_PERCENT + CPU_CORES*200 )) + [ "$RESERVE_MB" -lt 1024 ] && RESERVE_MB=1024 + AVAILABLE_FOR_PHP_MB=$(( TOTAL_RAM_MB - RESERVE_MB )) + [ "$AVAILABLE_FOR_PHP_MB" -lt 256 ] && AVAILABLE_FOR_PHP_MB=256 + + # Determine avg worker RSS (best-effort) + PHPFPM_PROCNAMES=("php-fpm${PHP_VER_DETECTED}" "php${PHP_VER_DETECTED}-fpm" "php-fpm" "php7.4-fpm" "php8.1-fpm" "php8.2-fpm") + FOUND_PROCNAME="" + for pn in "${PHPFPM_PROCNAMES[@]}"; do + if pgrep -x "$pn" >/dev/null 2>&1; then FOUND_PROCNAME="$pn"; break; fi + done + AVG_RSS_KB=0 + if [ -n "$FOUND_PROCNAME" ]; then + AVG_RSS_KB=$(ps --no-headers -o rss -C "$FOUND_PROCNAME" 2>/dev/null | awk '{s+=$1;n++}END{if(n)printf "%.0f",s/n;else print 0}') + fi + if [ -z "${AVG_RSS_KB}" ] || [ "$AVG_RSS_KB" -le 0 ]; then + AVG_RSS_MB=50 + AVG_RSS_KB=$((AVG_RSS_MB * 1024)) + else + AVG_RSS_MB=$(( (AVG_RSS_KB + 1023) / 1024 )) + fi + + MAX_CHILDREN=$(( AVAILABLE_FOR_PHP_MB / AVG_RSS_MB )) + [ "$MAX_CHILDREN" -lt 2 ] && MAX_CHILDREN=2 + [ "$MAX_CHILDREN" -gt 1000 ] && MAX_CHILDREN=1000 + PM_START=$(( MAX_CHILDREN * 20 / 100 )) + PM_MIN=$(( MAX_CHILDREN * 10 / 100 )) + PM_MAX=$(( MAX_CHILDREN * 30 / 100 )) + [ "$PM_START" -lt 2 ] && PM_START=2 + [ "$PM_MIN" -lt 1 ] && PM_MIN=1 + [ "$PM_MAX" -lt 2 ] && PM_MAX=2 + + # ------------------------- + # In-place replacement API + # ------------------------- + ensure_directive() { + local file="$1" key="$2" val="$3" anchor="$4" + local val_esc + val_esc=$(printf '%s' "$val" | sed -e 's|[\/&]|\\&|g') + # Replace any existing line (commented or not) + if grep -qE "^[[:space:]]*[;#]?[[:space:]]*${key}[[:space:]]*=" "$file"; then + if [ "$DRY_RUN" -eq 0 ]; then + sed -i -E "s|^[[:space:]]*[;#]?[[:space:]]*(${key})[[:space:]]*=.*|\1 = ${val_esc}|" "$file" + log "[OK] replaced ${key} in ${file}" + else + log "[DRY] would replace ${key} = ${val} in ${file}" + fi + else + # No existing line — insert after anchor (anchor is anchor key, e.g., 'pm' for 'pm = dynamic') + if [ -n "$anchor" ] && grep -qE "^[[:space:]]*${anchor}[[:space:]]*=" "$file"; then + if [ "$DRY_RUN" -eq 0 ]; then + awk -v a="$anchor" -v newline="${key} = ${val}" '{ + print; + if(!inserted && $0 ~ "^[[:space:]]*"a"[[:space:]]*="){ print newline; inserted=1 } + } END{ if(!inserted) print newline }' "$file" > "${file}.tmp" && mv -f "${file}.tmp" "$file" + log "[OK] inserted ${key} after ${anchor} in ${file}" + else + log "[DRY] would insert ${key} = ${val} after ${anchor} in ${file}" + fi + else + if [ "$DRY_RUN" -eq 0 ]; then + echo "${key} = ${val}" >> "$file" + log "[OK] appended ${key} to ${file}" + else + log "[DRY] would append ${key} = ${val} to ${file}" + fi + fi + fi + } + + # Remove commented duplicates of pm.* and slowlog/status lines (clean up leftovers) + if [ "$DRY_RUN" -eq 0 ]; then + sed -i -E '/^[[:space:]]*[;#][[:space:]]*(pm\.max_children|pm\.start_servers|pm\.min_spare_servers|pm\.max_spare_servers|pm\.max_requests|request_slowlog_timeout|slowlog|pm\.status_path|catch_workers_output)[[:space:]]*=.*$/d' "$POOL_CONF" || true + else + log "[DRY] would remove commented duplicates of pm.* and slowlog lines in ${POOL_CONF}" + fi + + # Ensure directives in-place, using anchor "pm" to insert near pm = dynamic if needed + ensure_directive "$POOL_CONF" "pm" "dynamic" "" + ensure_directive "$POOL_CONF" "pm.max_children" "${MAX_CHILDREN}" "pm" + ensure_directive "$POOL_CONF" "pm.start_servers" "${PM_START}" "pm" + ensure_directive "$POOL_CONF" "pm.min_spare_servers" "${PM_MIN}" "pm" + ensure_directive "$POOL_CONF" "pm.max_spare_servers" "${PM_MAX}" "pm" + ensure_directive "$POOL_CONF" "pm.max_requests" "${PM_MAX_REQUESTS}" "pm" + ensure_directive "$POOL_CONF" "request_slowlog_timeout" "${REQUEST_SLOWLOG_TIMEOUT}" "pm" + ensure_directive "$POOL_CONF" "slowlog" "${SLOWLOG_PATH}" "pm" + ensure_directive "$POOL_CONF" "pm.status_path" "${STATUS_PATH}" "pm" + ensure_directive "$POOL_CONF" "catch_workers_output" "${CATCH_WORKERS_OUTPUT}" "pm" + + if [ "$DRY_RUN" -eq 0 ]; then + chmod 640 "$POOL_CONF" || true + log "[OK] PHP-FPM pool file updated in-place (max_children=${MAX_CHILDREN})" + else + log "[DRY] would update pool file in-place (max_children=${MAX_CHILDREN})" + fi + + # Reload/restart php-fpm + if [ "$DRY_RUN" -eq 0 ]; then + if systemctl reload "$PHPFPM_SERVICE" >/dev/null 2>&1; then + log "[OK] Reloaded $PHPFPM_SERVICE successfully." + else + log "[WARN] Reload failed; attempting restart..." + if systemctl restart "$PHPFPM_SERVICE" >/dev/null 2>&1; then + log "[OK] Restarted $PHPFPM_SERVICE successfully." + else + log "[ERROR] Restart failed, please check $PHPFPM_SERVICE and logs manually." + fi + fi + else + log "[DRY] would reload/restart $PHPFPM_SERVICE" + fi + + # Write suggestion report + cat > "$PHPFPM_SUGGESTION_FILE" </dev/null | awk '{print $1}' | grep -qi "^${svc}.service$"; then + present=1 + fi + + log "[RUN] restart ${svc} (present=${present})" + + if [ "$DRY_RUN" -eq 1 ]; then + log "[DRY] would restart ${svc}" + return 0 + fi + + if systemctl restart "${svc}" >/dev/null 2>&1; then + restarted=1 + else + if command -v service >/dev/null 2>&1; then + if service "${svc}" restart >/dev/null 2>&1; then + restarted=1 + fi + fi + fi + + if [ "$restarted" -eq 1 ]; then + if systemctl is-active --quiet "${svc}" 2>/dev/null; then + log "[OK] ${svc} is active" + else + if command -v service >/dev/null 2>&1 && service "${svc}" status >/dev/null 2>&1; then + log "[OK] ${svc} status reports running via SysV" + else + log "[WARN] ${svc} restarted but status not confirmed; check: journalctl -u ${svc} -n 200 --no-pager" + fi + fi + return 0 + else + log "[ERROR] failed to restart ${svc} via systemctl and service; verify service name and packaging" + return 1 + fi +} + +MARIADB_RESTARTED=0 +for candidate in mariadb mysql mysqld; do + if restart_and_check "$candidate"; then + MARIADB_RESTARTED=1 + break + fi +done +if [ "$MARIADB_RESTARTED" -eq 0 ]; then + log "[WARN] MariaDB/MySQL restart not confirmed; ensure correct service name (mariadb/mysql/mysqld)" +fi + +RAD_RESTARTED=0 +for candidate in freeradius radiusd; do + if restart_and_check "$candidate"; then + RAD_RESTARTED=1 + break + fi +done +if [ "$RAD_RESTARTED" -eq 0 ]; then + log "[WARN] FreeRADIUS restart not confirmed; check OS packaging (freeradius or radiusd)" +fi + +if [ -n "${PHPFPM_SERVICE:-}" ]; then + restart_and_check "$PHPFPM_SERVICE" +fi + +PHP_FPM_UNITS=$(systemctl list-units --type=service --all --no-legend 2>/dev/null | awk '{print $1}' | grep -E '^php[0-9]+\.[0-9]+-fpm\.service$' || true) +if [ -n "$PHP_FPM_UNITS" ]; then + for u in $PHP_FPM_UNITS; do + svcname="${u%.service}" + restart_and_check "$svcname" + done +else + for ver in 8.2 8.1 8.0 7.4 7.3; do + restart_and_check "php${ver}-fpm" + done +fi + +log "" + +# ---------------------------------- +# Step 9: Summary and logs +# ---------------------------------- +log "========================" +log " Summary" +log "------------------------" +log "1) MariaDB config fragment: ${MARIADB_FRAG}" +log "2) radiusd.conf updated at: ${RADIUS_CONF:-}" +log "3) php-fpm pool edited in-place: ${POOL_CONF:-}" +log "4) Backups directory: ${BACKUP_BASE}" +log "5) Run log: ${LOG}" +log "" +log "Completed universal_replaced_complete run. Review the log and service journals if any step reported warnings or errors." +# release lock implicitly on script exit +exit 0