- 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.
609 lines
19 KiB
Bash
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 |