196 lines
5.3 KiB
Bash
196 lines
5.3 KiB
Bash
#!/bin/bash
|
|
|
|
# Enable strict error handling
|
|
set -euo pipefail
|
|
trap 'handle_error $? $LINENO' ERR
|
|
|
|
# Configuration
|
|
BACKUP_SCRIPT="/home/litespeed/mb-backups/backup_all.sh"
|
|
LOG_DIR="/home/litespeed/mb-backups/logs/auto"
|
|
ACTION_LOG_FILE="${LOG_DIR}/schedule_actions.log"
|
|
BACKUP_LOG_PREFIX="${LOG_DIR}/backup_"
|
|
PASSWORD_FILE="/etc/restic-password"
|
|
LOCK_FILE="/tmp/backup_schedule.lock"
|
|
MAX_RETRIES=3
|
|
RETRY_DELAY=5
|
|
|
|
# Error handling function
|
|
handle_error() {
|
|
local exit_code=$1
|
|
local line_no=$2
|
|
log_action "ERROR: Command failed at line ${line_no} with exit code ${exit_code}"
|
|
cleanup_and_exit 1
|
|
}
|
|
|
|
# Cleanup function
|
|
cleanup_and_exit() {
|
|
local exit_code=$1
|
|
[ -f "$LOCK_FILE" ] && rm -f "$LOCK_FILE"
|
|
exit "${exit_code}"
|
|
}
|
|
|
|
# Ensure single instance
|
|
ensure_single_instance() {
|
|
if [ -f "$LOCK_FILE" ]; then
|
|
if kill -0 "$(cat "$LOCK_FILE")" 2>/dev/null; then
|
|
log_action "ERROR: Another instance is running"
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo $$ > "$LOCK_FILE"
|
|
}
|
|
|
|
# Enhanced logging function
|
|
log_action() {
|
|
local timestamp=$(date +'%Y-%m-%d %H:%M:%S')
|
|
local log_msg="[$timestamp] $1"
|
|
echo "$log_msg" | tee -a "$ACTION_LOG_FILE"
|
|
|
|
# Log critical errors to system log
|
|
if [[ "$1" == *"ERROR"* ]]; then
|
|
logger -t "backup-schedule" "$1"
|
|
fi
|
|
}
|
|
|
|
# Validate system requirements
|
|
check_system_requirements() {
|
|
local required_space=1048576 # 1GB in KB
|
|
local available_space=$(df -k "$LOG_DIR" | awk 'NR==2 {print $4}')
|
|
|
|
if [ ! -x "$BACKUP_SCRIPT" ]; then
|
|
log_action "ERROR: Backup script not executable or not found"
|
|
return 1
|
|
fi
|
|
|
|
if [ "$available_space" -lt "$required_space" ]; then
|
|
log_action "ERROR: Insufficient disk space"
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
# Validate cron schedule
|
|
validate_schedule() {
|
|
local schedule="$1"
|
|
if ! [[ $schedule =~ ^[0-9,\*/-]+ [0-9,\*/-]+ [0-9,\*/-]+ [0-9,\*/-]+ [0-9,\*/-]+$ ]]; then
|
|
log_action "ERROR: Invalid cron schedule format: $schedule"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Get Restic password with retry mechanism
|
|
get_restic_password() {
|
|
local retry_count=0
|
|
while [ $retry_count -lt $MAX_RETRIES ]; do
|
|
if [ -f "$PASSWORD_FILE" ] && [ -s "$PASSWORD_FILE" ]; then
|
|
cat "$PASSWORD_FILE"
|
|
return 0
|
|
fi
|
|
retry_count=$((retry_count + 1))
|
|
sleep $RETRY_DELAY
|
|
done
|
|
log_action "ERROR: Failed to retrieve Restic password after $MAX_RETRIES attempts"
|
|
return 1
|
|
}
|
|
|
|
# Enhanced cron job management
|
|
add_update_cron_job() {
|
|
local action="$1"
|
|
local schedule="$2"
|
|
local restic_password="$3"
|
|
local temp_crontab="/tmp/temp_crontab.$$"
|
|
|
|
# Validate inputs
|
|
if ! validate_schedule "$schedule"; then
|
|
return 1
|
|
fi
|
|
|
|
# Prepare cron command with environment variables and error handling
|
|
local CMD="RESTIC_PASSWORD=\"$restic_password\" \
|
|
LOG_FILE=\"${BACKUP_LOG_PREFIX}\$(date +\\%Y-\\%m-\\%d_\\%H-\\%M-\\%S).log\" \
|
|
$BACKUP_SCRIPT > \"\$LOG_FILE\" 2>&1 || echo \"\$(date) Backup failed\" >> \"$ACTION_LOG_FILE\""
|
|
|
|
# Safely update crontab
|
|
crontab -l 2>/dev/null | grep -v "$BACKUP_SCRIPT" > "$temp_crontab" || true
|
|
echo "$schedule $CMD" >> "$temp_crontab"
|
|
|
|
if crontab "$temp_crontab"; then
|
|
log_action "Successfully ${action}d backup schedule: $schedule"
|
|
rm -f "$temp_crontab"
|
|
return 0
|
|
else
|
|
log_action "ERROR: Failed to ${action} backup schedule"
|
|
rm -f "$temp_crontab"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Enhanced cron job removal
|
|
remove_cron_job() {
|
|
local temp_crontab="/tmp/temp_crontab.$$"
|
|
|
|
if crontab -l 2>/dev/null | grep -q "$BACKUP_SCRIPT"; then
|
|
crontab -l | grep -v "$BACKUP_SCRIPT" > "$temp_crontab"
|
|
if crontab "$temp_crontab"; then
|
|
log_action "Backup schedule successfully removed"
|
|
rm -f "$temp_crontab"
|
|
return 0
|
|
else
|
|
log_action "ERROR: Failed to remove backup schedule"
|
|
rm -f "$temp_crontab"
|
|
return 1
|
|
fi
|
|
else
|
|
log_action "No backup schedule found to remove"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
# Create log directory with proper permissions
|
|
mkdir -p "$LOG_DIR"
|
|
chmod 750 "$LOG_DIR"
|
|
|
|
# Ensure single instance
|
|
ensure_single_instance
|
|
|
|
# Check system requirements
|
|
if ! check_system_requirements; then
|
|
cleanup_and_exit 1
|
|
fi
|
|
|
|
case $1 in
|
|
add|update)
|
|
if [ "$#" -ne 3 ]; then
|
|
log_action "Usage: $0 {add|update} 'schedule' 'restic_password'"
|
|
log_action "Example: $0 add '0 1 * * *' 'secret_password'"
|
|
cleanup_and_exit 1
|
|
fi
|
|
|
|
local restic_password="${3:-$(get_restic_password)}"
|
|
if [ -z "$restic_password" ]; then
|
|
cleanup_and_exit 1
|
|
fi
|
|
|
|
add_update_cron_job "$1" "$2" "$restic_password"
|
|
;;
|
|
remove)
|
|
remove_cron_job
|
|
;;
|
|
status)
|
|
crontab -l | grep "$BACKUP_SCRIPT" || echo "No backup schedule found"
|
|
;;
|
|
*)
|
|
log_action "Invalid action: $1. Use add, update, remove, or status"
|
|
cleanup_and_exit 1
|
|
;;
|
|
esac
|
|
|
|
cleanup_and_exit 0
|
|
}
|
|
|
|
# Execute main function with all arguments
|
|
main "$@" |