Files
desk-moloni/scripts/maintenance.sh
Emanuel Almeida c19f6fd9ee fix(perfexcrm module): align version to 3.0.1, unify entrypoint, and harden routes/views
- Bump DESK_MOLONI version to 3.0.1 across module
- Normalize hooks to after_client_* and instantiate PerfexHooks safely
- Fix OAuthController view path and API client class name
- Add missing admin views for webhook config/logs; adjust view loading
- Harden client portal routes and admin routes mapping
- Make Dashboard/Logs/Queue tolerant to optional model methods
- Align log details query with existing schema; avoid broken joins

This makes the module operational in Perfex (admin + client), reduces 404s,
and avoids fatal errors due to inconsistent tables/methods.
2025-09-11 17:38:45 +01:00

609 lines
19 KiB
Bash

#!/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