#!/bin/bash # Desk-Moloni v3.0 Maintenance Script # # Automated maintenance tasks including log cleanup, optimization, # health checks, and system maintenance operations. set -euo pipefail # Script configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MODULE_DIR="$(dirname "$SCRIPT_DIR")" CLI_DIR="$MODULE_DIR/cli" LOG_DIR="$MODULE_DIR/logs" LOCK_DIR="$MODULE_DIR/locks" CACHE_DIR="$MODULE_DIR/cache" TEMP_DIR="$MODULE_DIR/temp" # Maintenance configuration DEFAULT_LOG_RETENTION_DAYS=30 DEFAULT_QUEUE_CLEANUP_DAYS=7 DEFAULT_TEMP_CLEANUP_HOURS=24 DEFAULT_OPTIMIZATION_INTERVAL=7 # days # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Logging functions log_info() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local message="[$timestamp] [INFO] $1" echo -e "${BLUE}$message${NC}" echo "$message" >> "$LOG_DIR/maintenance.log" 2>/dev/null || true } log_success() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local message="[$timestamp] [SUCCESS] $1" echo -e "${GREEN}$message${NC}" echo "$message" >> "$LOG_DIR/maintenance.log" 2>/dev/null || true } log_warning() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local message="[$timestamp] [WARNING] $1" echo -e "${YELLOW}$message${NC}" echo "$message" >> "$LOG_DIR/maintenance.log" 2>/dev/null || true } log_error() { local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local message="[$timestamp] [ERROR] $1" echo -e "${RED}$message${NC}" echo "$message" >> "$LOG_DIR/maintenance.log" 2>/dev/null || true } # Help function show_help() { cat << EOF Desk-Moloni v3.0 Maintenance Script Usage: $0 [OPTIONS] [TASKS] Tasks: all Run all maintenance tasks (default) cleanup Clean up old logs and temporary files optimize Optimize database tables and indexes health-check Perform comprehensive health check queue-maintenance Clean up and optimize queue cache-cleanup Clear expired cache files log-rotation Rotate and compress log files backup-cleanup Clean up old backup files token-refresh Refresh OAuth tokens if needed stats-update Update performance statistics Options: -h, --help Show this help message --log-retention DAYS Log retention period (default: $DEFAULT_LOG_RETENTION_DAYS) --queue-cleanup DAYS Queue cleanup period (default: $DEFAULT_QUEUE_CLEANUP_DAYS) --temp-cleanup HOURS Temp file cleanup period (default: $DEFAULT_TEMP_CLEANUP_HOURS) --dry-run Show what would be done without changes --verbose Verbose output --force Force operations without prompts Examples: $0 # Run all maintenance tasks $0 cleanup optimize # Run specific tasks $0 --dry-run # Preview maintenance actions $0 health-check --verbose # Detailed health check EOF } # Parse command line arguments TASKS=() LOG_RETENTION_DAYS=$DEFAULT_LOG_RETENTION_DAYS QUEUE_CLEANUP_DAYS=$DEFAULT_QUEUE_CLEANUP_DAYS TEMP_CLEANUP_HOURS=$DEFAULT_TEMP_CLEANUP_HOURS DRY_RUN=false VERBOSE=false FORCE=false while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; --log-retention) LOG_RETENTION_DAYS="$2" shift 2 ;; --queue-cleanup) QUEUE_CLEANUP_DAYS="$2" shift 2 ;; --temp-cleanup) TEMP_CLEANUP_HOURS="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; --verbose) VERBOSE=true shift ;; --force) FORCE=true shift ;; all|cleanup|optimize|health-check|queue-maintenance|cache-cleanup|log-rotation|backup-cleanup|token-refresh|stats-update) TASKS+=("$1") shift ;; *) log_error "Unknown option: $1" show_help exit 1 ;; esac done # Default to all tasks if none specified if [[ ${#TASKS[@]} -eq 0 ]]; then TASKS=("all") fi # Create required directories ensure_directories() { local dirs=("$LOG_DIR" "$LOCK_DIR" "$CACHE_DIR" "$TEMP_DIR") for dir in "${dirs[@]}"; do if [[ ! -d "$dir" ]]; then if [[ "$DRY_RUN" == true ]]; then log_info "Would create directory: $dir" else mkdir -p "$dir" log_info "Created directory: $dir" fi fi done } # Log cleanup task task_cleanup() { log_info "Starting cleanup task" local files_removed=0 local space_freed=0 # Clean up old log files if [[ -d "$LOG_DIR" ]]; then log_info "Cleaning up log files older than $LOG_RETENTION_DAYS days" if [[ "$DRY_RUN" == true ]]; then local old_logs old_logs=$(find "$LOG_DIR" -name "*.log" -type f -mtime +$LOG_RETENTION_DAYS 2>/dev/null | wc -l) log_info "Would remove $old_logs old log files" else while IFS= read -r -d '' file; do local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo 0) rm "$file" ((files_removed++)) ((space_freed += size)) [[ "$VERBOSE" == true ]] && log_info "Removed: $(basename "$file")" done < <(find "$LOG_DIR" -name "*.log" -type f -mtime +$LOG_RETENTION_DAYS -print0 2>/dev/null) fi fi # Clean up temporary files if [[ -d "$TEMP_DIR" ]]; then log_info "Cleaning up temporary files older than $TEMP_CLEANUP_HOURS hours" if [[ "$DRY_RUN" == true ]]; then local old_temps old_temps=$(find "$TEMP_DIR" -type f -mmin +$((TEMP_CLEANUP_HOURS * 60)) 2>/dev/null | wc -l) log_info "Would remove $old_temps temporary files" else while IFS= read -r -d '' file; do local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo 0) rm "$file" ((files_removed++)) ((space_freed += size)) [[ "$VERBOSE" == true ]] && log_info "Removed: $(basename "$file")" done < <(find "$TEMP_DIR" -type f -mmin +$((TEMP_CLEANUP_HOURS * 60)) -print0 2>/dev/null) fi fi # Clean up orphaned lock files if [[ -d "$LOCK_DIR" ]]; then log_info "Cleaning up orphaned lock files" for lock_file in "$LOCK_DIR"/*.lock; do if [[ -f "$lock_file" ]]; then # Check if process is still running (basic check) local lock_name=$(basename "$lock_file" .lock) if ! pgrep -f "$lock_name" > /dev/null; then if [[ "$DRY_RUN" == true ]]; then log_info "Would remove orphaned lock: $(basename "$lock_file")" else rm "$lock_file" log_info "Removed orphaned lock: $(basename "$lock_file")" fi fi fi done fi if [[ "$DRY_RUN" == false ]]; then local space_mb=$((space_freed / 1024 / 1024)) log_success "Cleanup completed: $files_removed files removed, ${space_mb}MB freed" fi } # Database optimization task task_optimize() { log_info "Starting database optimization task" local tables=("desk_moloni_config" "desk_moloni_mapping" "desk_moloni_sync_queue" "desk_moloni_sync_log") if [[ "$DRY_RUN" == true ]]; then log_info "Would optimize ${#tables[@]} database tables" return fi # Get database connection details from PHP config local db_config if ! db_config=$(php -r " require_once '$MODULE_DIR/config/bootstrap.php'; \$config = include '$MODULE_DIR/config/config.php'; echo \$config['database']['host'] . '|' . \$config['database']['database'] . '|' . \$config['database']['username']; " 2>/dev/null); then log_error "Failed to get database configuration" return 1 fi IFS='|' read -r db_host db_name db_user <<< "$db_config" # Read password securely (this is a simplified approach) log_info "Enter database password for optimization:" read -s db_password local mysql_cmd="mysql -h$db_host -u$db_user -p$db_password $db_name" for table in "${tables[@]}"; do log_info "Optimizing table: $table" # Check if table exists if ! echo "SHOW TABLES LIKE '$table';" | $mysql_cmd 2>/dev/null | grep -q "$table"; then log_warning "Table not found: $table" continue fi # Optimize table if echo "OPTIMIZE TABLE $table;" | $mysql_cmd &>/dev/null; then log_success "Optimized: $table" else log_error "Failed to optimize: $table" fi # Analyze table for better query planning if echo "ANALYZE TABLE $table;" | $mysql_cmd &>/dev/null; then [[ "$VERBOSE" == true ]] && log_info "Analyzed: $table" fi done log_success "Database optimization completed" } # Health check task task_health_check() { log_info "Starting comprehensive health check" local issues=0 # Check CLI commands local cli_commands=("queue_processor.php" "sync_commands.php") for cmd in "${cli_commands[@]}"; do local cmd_path="$CLI_DIR/$cmd" if [[ -f "$cmd_path" && -x "$cmd_path" ]]; then log_success "✓ CLI command available: $cmd" else log_error "✗ CLI command missing or not executable: $cmd" ((issues++)) fi done # Check directory permissions local required_dirs=("$LOG_DIR" "$LOCK_DIR" "$CACHE_DIR" "$TEMP_DIR") for dir in "${required_dirs[@]}"; do if [[ -d "$dir" && -w "$dir" ]]; then log_success "✓ Directory writable: $(basename "$dir")" else log_error "✗ Directory not writable: $(basename "$dir")" ((issues++)) fi done # Check disk space local disk_usage disk_usage=$(df "$MODULE_DIR" | awk 'NR==2 {print $5}' | sed 's/%//') if [[ "$disk_usage" -lt 90 ]]; then log_success "✓ Disk usage OK: ${disk_usage}%" else log_warning "⚠ Disk usage high: ${disk_usage}%" fi # Check memory usage (if possible) if command -v free &> /dev/null; then local mem_usage mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}') if [[ "$mem_usage" -lt 80 ]]; then log_success "✓ Memory usage OK: ${mem_usage}%" else log_warning "⚠ Memory usage high: ${mem_usage}%" fi fi # Check recent errors in logs if [[ -f "$LOG_DIR/queue_processor.log" ]]; then local recent_errors recent_errors=$(tail -n 100 "$LOG_DIR/queue_processor.log" | grep -c "ERROR" || true) if [[ "$recent_errors" -eq 0 ]]; then log_success "✓ No recent errors in queue processor" else log_warning "⚠ $recent_errors recent errors in queue processor" fi fi # Test basic CLI functionality if [[ "$DRY_RUN" == false ]]; then if php "$CLI_DIR/sync_commands.php" test-connection &>/dev/null; then log_success "✓ API connection test passed" else log_warning "⚠ API connection test failed (may be expected)" fi fi if [[ "$issues" -eq 0 ]]; then log_success "Health check completed - no critical issues found" else log_error "Health check found $issues critical issues" return 1 fi } # Queue maintenance task task_queue_maintenance() { log_info "Starting queue maintenance task" if [[ "$DRY_RUN" == true ]]; then log_info "Would clean up completed queue entries older than $QUEUE_CLEANUP_DAYS days" log_info "Would reset stuck processing tasks" return fi # Clean up old completed tasks php "$CLI_DIR/sync_commands.php" queue-cleanup --days="$QUEUE_CLEANUP_DAYS" || true # Reset stuck processing tasks (running for more than 1 hour) php "$CLI_DIR/sync_commands.php" queue-reset-stuck || true log_success "Queue maintenance completed" } # Cache cleanup task task_cache_cleanup() { log_info "Starting cache cleanup task" if [[ ! -d "$CACHE_DIR" ]]; then log_info "Cache directory not found, skipping" return fi local cache_files=0 local cache_size=0 if [[ "$DRY_RUN" == true ]]; then cache_files=$(find "$CACHE_DIR" -type f 2>/dev/null | wc -l) log_info "Would clear $cache_files cache files" else # Clear all cache files for cache_file in "$CACHE_DIR"/*; do if [[ -f "$cache_file" ]]; then local size=$(stat -f%z "$cache_file" 2>/dev/null || stat -c%s "$cache_file" 2>/dev/null || echo 0) rm "$cache_file" ((cache_files++)) ((cache_size += size)) fi done local size_mb=$((cache_size / 1024 / 1024)) log_success "Cache cleanup completed: $cache_files files, ${size_mb}MB cleared" fi } # Log rotation task task_log_rotation() { log_info "Starting log rotation task" if [[ ! -d "$LOG_DIR" ]]; then return fi local rotated=0 # Rotate large log files for log_file in "$LOG_DIR"/*.log; do if [[ -f "$log_file" ]]; then local size=$(stat -f%z "$log_file" 2>/dev/null || stat -c%s "$log_file" 2>/dev/null || echo 0) local size_mb=$((size / 1024 / 1024)) # Rotate files larger than 10MB if [[ "$size_mb" -gt 10 ]]; then local base_name=$(basename "$log_file" .log) local timestamp=$(date +%Y%m%d_%H%M%S) local rotated_name="$LOG_DIR/${base_name}_${timestamp}.log" if [[ "$DRY_RUN" == true ]]; then log_info "Would rotate large log file: $(basename "$log_file") (${size_mb}MB)" else cp "$log_file" "$rotated_name" > "$log_file" # Truncate original file # Compress rotated file if command -v gzip &> /dev/null; then gzip "$rotated_name" log_info "Rotated and compressed: $(basename "$log_file")" else log_info "Rotated: $(basename "$log_file")" fi ((rotated++)) fi fi fi done if [[ "$DRY_RUN" == false ]]; then log_success "Log rotation completed: $rotated files rotated" fi } # Backup cleanup task task_backup_cleanup() { log_info "Starting backup cleanup task" local backup_dir="$MODULE_DIR/backups" if [[ ! -d "$backup_dir" ]]; then log_info "No backup directory found, skipping" return fi local retention_days=90 # Keep backups for 90 days local removed=0 if [[ "$DRY_RUN" == true ]]; then local old_backups old_backups=$(find "$backup_dir" -type d -mtime +$retention_days 2>/dev/null | wc -l) log_info "Would remove $old_backups old backup directories" else while IFS= read -r -d '' backup; do rm -rf "$backup" ((removed++)) [[ "$VERBOSE" == true ]] && log_info "Removed backup: $(basename "$backup")" done < <(find "$backup_dir" -type d -mtime +$retention_days -print0 2>/dev/null) log_success "Backup cleanup completed: $removed old backups removed" fi } # Token refresh task task_token_refresh() { log_info "Starting token refresh task" if [[ "$DRY_RUN" == true ]]; then log_info "Would check and refresh OAuth tokens if needed" return fi # Run token refresh command if php "$CLI_DIR/sync_commands.php" token-refresh &>/dev/null; then log_success "Token refresh completed" else log_warning "Token refresh failed or not needed" fi } # Statistics update task task_stats_update() { log_info "Starting statistics update task" if [[ "$DRY_RUN" == true ]]; then log_info "Would update performance and usage statistics" return fi # Update statistics if php "$CLI_DIR/sync_commands.php" stats-update &>/dev/null; then log_success "Statistics update completed" else log_warning "Statistics update failed" fi } # Execute maintenance tasks run_tasks() { local start_time=$(date +%s) log_info "Starting maintenance run with tasks: ${TASKS[*]}" if [[ "$DRY_RUN" == true ]]; then log_warning "DRY RUN MODE - No changes will be made" fi for task in "${TASKS[@]}"; do case "$task" in all) task_cleanup task_optimize task_health_check task_queue_maintenance task_cache_cleanup task_log_rotation task_backup_cleanup task_token_refresh task_stats_update ;; cleanup) task_cleanup ;; optimize) task_optimize ;; health-check) task_health_check ;; queue-maintenance) task_queue_maintenance ;; cache-cleanup) task_cache_cleanup ;; log-rotation) task_log_rotation ;; backup-cleanup) task_backup_cleanup ;; token-refresh) task_token_refresh ;; stats-update) task_stats_update ;; *) log_error "Unknown task: $task" ;; esac done local end_time=$(date +%s) local duration=$((end_time - start_time)) log_success "Maintenance completed in ${duration} seconds" } # Main execution main() { # Ensure required directories exist ensure_directories # Run maintenance tasks run_tasks } # Error handling trap 'log_error "Maintenance script failed on line $LINENO"' ERR # Execute if called directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi