Backup Common
Wspólny skrypt dla skryptów backup
Skrypt zawiera funkcje współdzielone przez pozostałe skrypty backup (logowanie, blokady, retencja). Jest potrzebny do ujednolicenia zachowania i ograniczenia powielania logiki.
Założenia konfiguracyjne:
- Skrypty znajdują się w
/srv/config/scripts/. - Logi skryptów trafiają na
stdout/stderr - Pozostałe skrypty backup:
backup-db-mariadb.sh,backup-db-postgres.sh,backup-restic.sh
Tworzenie skryptu
Tworzymy plik:
micro /srv/config/scripts/backup-common.sh
W pliku umieszczamy:
backup-db-common.sh
#!/usr/bin/env bash
# /srv/config/scripts/backup-db-common.sh
# 1. Strict Mode (Safety)
set -euo pipefail
IFS=$'\n\t'
# ANSI Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[31m'
NC='\033[0m' # No Color
# Globals
CURRENT_BACKUP_DIR=""
LOCK_BASE_DIR="/srv/backups/.locks"
# --- Logging Functions ---
log() { echo -e "${GREEN}[backup]${NC} $1"; }
header() { echo -e "\n${CYAN}=== $1 ===${NC}"; }
warn() { echo -e "${YELLOW}Warning: $1${NC}"; }
error() { echo -e "${RED}Error: $1${NC}" >&2; }
critical() { echo -e "${RED}CRITICAL ERROR: $1${NC}" >&2; exit 1; }
# --- Security Checks ---
check_root() {
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
critical "Run as root (required for docker interaction)."
fi
}
check_config_perms() {
local config_file="$1"
if [[ ! -f "$config_file" ]]; then
critical "Config file missing: $config_file"
fi
# Check for symlinks (Safety)
if [[ -L "$config_file" ]]; then
critical "Config file is a symlink (unsafe): $config_file"
fi
# Strict check: owned by root:root and mode 600
if [[ "$(stat -c '%U:%G %a' "$config_file")" != "root:root 600" ]]; then
critical "Unsafe permissions for $config_file (expected root:root 600)"
fi
}
# --- Improved Lock Mechanism (Dynamic FD + Central Dir) ---
acquire_lock() {
local lock_name="$1"
# Ensure lock directory exists
if [[ ! -d "$LOCK_BASE_DIR" ]]; then
mkdir -p "$LOCK_BASE_DIR" || critical "Cannot create lock dir: $LOCK_BASE_DIR"
fi
local lock_file="${LOCK_BASE_DIR}/${lock_name}.lock"
# Use dynamic file descriptor allocation {var_name} (Bash 4.1+)
# This prevents FD collisions (like the static 200 issue)
exec {lock_fd}>"$lock_file"
if ! flock -n "$lock_fd"; then
error "Script is already running. Locked: $lock_file"
exit 1
fi
# Note: lock_fd stays open until script exit
}
# --- Trap Handler ---
backup_exit_handler() {
local rc=$?
if [[ $rc -ne 0 ]] && [[ -n "$CURRENT_BACKUP_DIR" ]]; then
: > "${CURRENT_BACKUP_DIR}/.FAILED"
error "Backup failed (rc=$rc). Check logs."
fi
}
# --- Environment Setup ---
setup_backup_env() {
local out_dir="$1"
CURRENT_BACKUP_DIR="$out_dir"
mkdir -p "$out_dir"
trap backup_exit_handler EXIT
}
# --- Safe Retention (Safety Stop) ---
perform_retention() {
local target_dir="$1"
local days="$2"
header "Retention Policy"
# Safety Check 1: Empty variable
if [[ -z "$target_dir" ]]; then
critical "Retention SAFETY STOP: Target directory variable is empty."
fi
# Safety Check 2: Path sanity (Must be under /srv/backups)
if [[ "$target_dir" != /srv/backups/* ]]; then
critical "Retention SAFETY STOP: Path '$target_dir' is outside /srv/backups. Aborting delete."
fi
# Safety Check 3: Directory exists
if [[ ! -d "$target_dir" ]]; then
warn "Retention: Directory $target_dir does not exist. Skipping."
return
fi
log "Cleaning directories older than $days days in: $target_dir"
# Execution
find "$target_dir" -maxdepth 1 -mindepth 1 -type d \
-name '20????????_????' \
-mtime +"$days" \
-print -exec rm -rf {} +
}
# --- Finalization ---
finish_backup() {
local out_dir="$1"
local fail_count="$2"
echo ""
if [[ "$fail_count" -eq 0 ]]; then
: > "$out_dir/.SUCCESS"
log "Completed successfully."
echo "FINAL_OUT_DIR=$out_dir"
else
critical "Backup finished with $fail_count errors."
fi
}
Nadawanie uprawnień
Uprawnienia 0640 ograniczają zapis wyłącznie do konta root, a odczyt udostępniają tylko rootowi i uprzywilejowanej grupie (np. sudo)
sudo chown root:root /srv/config/scripts/backup-common.sh
sudo chmod 0640 /srv/config/scripts/backup-common.sh