This enhancement adds two major improvements to hybrid backup mode: 1. SEPARATE SCHEDULE CONFIGURATION: - Block device backups now have independent scheduling from file backups - Options: weekly, biweekly, monthly, custom day of week/month - File backups run on main schedule, block device on separate schedule - Stored in BLOCK_DEVICE_FREQUENCY and BLOCK_DEVICE_DAY config variables - Backup script checks date/week to determine if block backup should run 2. MANUAL BACKUP SELECTION: - Interactive menu when running backup in hybrid mode - Three options: files only, block only, or both - Files only: fast (~2-3 min) for quick daily archives - Block only: slow (~20-30 min) for VM conversion capability - Both: complete backup of everything - Backup script accepts "files", "block", or "yes" as FORCE_FULL parameter These features provide fine-grained control over backup execution while maintaining the convenience of hybrid mode. Users can now optimize backup frequency based on their needs (frequent file backups, less frequent disk images) and selectively run specific backup types manually. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2048 lines
64 KiB
Bash
Executable file
2048 lines
64 KiB
Bash
Executable file
#!/bin/bash
|
|
set -e
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Script configuration
|
|
SCRIPT_VERSION="1.1.0"
|
|
CONFIG_DIR="/etc/proxmox-backup-client"
|
|
TARGETS_DIR="$CONFIG_DIR/targets"
|
|
LOG_FILE="/var/log/pbs-client-installer.log"
|
|
|
|
# Helper functions
|
|
log() {
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
|
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
echo "[ERROR] $1" >> "$LOG_FILE"
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
echo "[WARN] $1" >> "$LOG_FILE"
|
|
}
|
|
|
|
info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
prompt() {
|
|
local prompt_text="$1"
|
|
local default_value="$2"
|
|
local user_input
|
|
|
|
if [ -n "$default_value" ]; then
|
|
read -p "$(echo -e ${BLUE}${prompt_text}${NC} [${default_value}]: )" user_input
|
|
echo "${user_input:-$default_value}"
|
|
else
|
|
read -p "$(echo -e ${BLUE}${prompt_text}${NC}: )" user_input
|
|
echo "$user_input"
|
|
fi
|
|
}
|
|
|
|
prompt_password() {
|
|
local prompt_text="$1"
|
|
local user_input
|
|
read -sp "$(echo -e ${BLUE}${prompt_text}${NC}: )" user_input
|
|
echo >&2 # Output newline to stderr so it doesn't get captured
|
|
echo "$user_input"
|
|
}
|
|
|
|
# Multi-target helper functions
|
|
list_targets() {
|
|
if [ ! -d "$TARGETS_DIR" ]; then
|
|
return 1
|
|
fi
|
|
|
|
local targets=()
|
|
for config in "$TARGETS_DIR"/*.conf; do
|
|
if [ -f "$config" ]; then
|
|
targets+=("$(basename "$config" .conf)")
|
|
fi
|
|
done
|
|
|
|
if [ ${#targets[@]} -eq 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
printf '%s\n' "${targets[@]}"
|
|
}
|
|
|
|
validate_target_name() {
|
|
local name="$1"
|
|
|
|
# Check if name is empty
|
|
if [ -z "$name" ]; then
|
|
error "Target name cannot be empty"
|
|
return 1
|
|
fi
|
|
|
|
# Check if name contains only alphanumeric, dash, underscore
|
|
if ! [[ "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
error "Target name can only contain letters, numbers, dash, and underscore"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
target_exists() {
|
|
local name="$1"
|
|
[ -f "$TARGETS_DIR/$name.conf" ]
|
|
}
|
|
|
|
get_target_config_path() {
|
|
local name="$1"
|
|
echo "$TARGETS_DIR/$name.conf"
|
|
}
|
|
|
|
migrate_legacy_config() {
|
|
# Check if old single-target config exists
|
|
if [ -f "$CONFIG_DIR/config" ] && [ ! -d "$TARGETS_DIR" ]; then
|
|
log "Migrating legacy configuration to multi-target system..."
|
|
|
|
mkdir -p "$TARGETS_DIR"
|
|
mv "$CONFIG_DIR/config" "$TARGETS_DIR/default.conf"
|
|
|
|
# Rename old backup script if it exists
|
|
if [ -f "$CONFIG_DIR/backup.sh" ]; then
|
|
mv "$CONFIG_DIR/backup.sh" "$CONFIG_DIR/backup-default.sh"
|
|
fi
|
|
|
|
# Rename old services if they exist
|
|
if [ -f "/etc/systemd/system/pbs-backup.service" ]; then
|
|
systemctl stop pbs-backup.service pbs-backup.timer 2>/dev/null || true
|
|
systemctl disable pbs-backup.service pbs-backup.timer 2>/dev/null || true
|
|
|
|
mv /etc/systemd/system/pbs-backup.service /etc/systemd/system/pbs-backup-default.service
|
|
mv /etc/systemd/system/pbs-backup-manual.service /etc/systemd/system/pbs-backup-default-manual.service 2>/dev/null || true
|
|
mv /etc/systemd/system/pbs-backup.timer /etc/systemd/system/pbs-backup-default.timer
|
|
|
|
# Update service files to point to new backup script
|
|
sed -i 's|/etc/proxmox-backup-client/backup.sh|/etc/proxmox-backup-client/backup-default.sh|g' \
|
|
/etc/systemd/system/pbs-backup-default.service \
|
|
/etc/systemd/system/pbs-backup-default-manual.service 2>/dev/null || true
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable pbs-backup-default.timer
|
|
systemctl start pbs-backup-default.timer
|
|
|
|
log "Legacy configuration migrated to 'default' target"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
show_targets_list() {
|
|
echo
|
|
echo "════════════════════════════════════════"
|
|
echo " Configured Backup Targets"
|
|
echo "════════════════════════════════════════"
|
|
echo
|
|
|
|
if ! list_targets >/dev/null 2>&1; then
|
|
warn "No backup targets configured"
|
|
return 1
|
|
fi
|
|
|
|
list_targets | while read -r target; do
|
|
local config_file="$(get_target_config_path "$target")"
|
|
if [ -f "$config_file" ]; then
|
|
source "$config_file"
|
|
|
|
echo "Target: $target"
|
|
echo " Server: ${PBS_SERVER:-unknown}:${PBS_PORT:-8007}"
|
|
echo " Datastore: ${PBS_DATASTORE:-unknown}"
|
|
echo " Type: ${BACKUP_TYPE:-unknown}"
|
|
echo " Schedule: ${TIMER_SCHEDULE:-unknown}"
|
|
|
|
# Check if timer is active
|
|
if systemctl is-enabled "pbs-backup-${target}.timer" &>/dev/null; then
|
|
if systemctl is-active "pbs-backup-${target}.timer" &>/dev/null; then
|
|
echo " Status: ✓ Active"
|
|
else
|
|
echo " Status: ✗ Inactive"
|
|
fi
|
|
else
|
|
echo " Status: ✗ Disabled"
|
|
fi
|
|
echo
|
|
fi
|
|
done
|
|
}
|
|
|
|
add_target() {
|
|
echo
|
|
log "Adding new backup target..."
|
|
echo
|
|
|
|
# Get target name
|
|
while true; do
|
|
TARGET_NAME=$(prompt "Enter target name (e.g., 'offsite', 'local', 'backup1')" "")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
continue
|
|
fi
|
|
|
|
if target_exists "$TARGET_NAME"; then
|
|
error "Target '$TARGET_NAME' already exists"
|
|
continue
|
|
fi
|
|
|
|
break
|
|
done
|
|
|
|
# Run interactive config for this target
|
|
interactive_config_for_target "$TARGET_NAME"
|
|
}
|
|
|
|
edit_target() {
|
|
echo
|
|
if ! list_targets >/dev/null 2>&1; then
|
|
error "No targets configured"
|
|
return 1
|
|
fi
|
|
|
|
echo "Available targets:"
|
|
list_targets | nl
|
|
echo
|
|
|
|
TARGET_NAME=$(prompt "Enter target name to edit" "")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! target_exists "$TARGET_NAME"; then
|
|
error "Target '$TARGET_NAME' does not exist"
|
|
return 1
|
|
fi
|
|
|
|
log "Editing target: $TARGET_NAME"
|
|
echo
|
|
echo "What would you like to edit?"
|
|
echo " 1) Connection only (server/credentials)"
|
|
echo " 2) Backup settings (type/schedule/retention)"
|
|
echo " 3) Full reconfiguration"
|
|
echo " 4) Cancel"
|
|
|
|
local EDIT_CHOICE=$(prompt "Select option [1/2/3/4]" "1")
|
|
|
|
case "$EDIT_CHOICE" in
|
|
1)
|
|
reconfigure_connection_for_target "$TARGET_NAME"
|
|
;;
|
|
2)
|
|
reconfigure_backup_settings_for_target "$TARGET_NAME"
|
|
;;
|
|
3)
|
|
interactive_config_for_target "$TARGET_NAME"
|
|
;;
|
|
4)
|
|
info "Cancelled"
|
|
return 0
|
|
;;
|
|
*)
|
|
error "Invalid option"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
delete_target() {
|
|
echo
|
|
if ! list_targets >/dev/null 2>&1; then
|
|
error "No targets configured"
|
|
return 1
|
|
fi
|
|
|
|
echo "Available targets:"
|
|
list_targets | nl
|
|
echo
|
|
|
|
TARGET_NAME=$(prompt "Enter target name to delete" "")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! target_exists "$TARGET_NAME"; then
|
|
error "Target '$TARGET_NAME' does not exist"
|
|
return 1
|
|
fi
|
|
|
|
warn "This will delete target '$TARGET_NAME' and stop all associated backups"
|
|
local CONFIRM=$(prompt "Are you sure? Type 'yes' to confirm" "no")
|
|
|
|
if [ "$CONFIRM" != "yes" ]; then
|
|
info "Cancelled"
|
|
return 0
|
|
fi
|
|
|
|
log "Deleting target: $TARGET_NAME"
|
|
|
|
# Stop and disable services
|
|
systemctl stop "pbs-backup-${TARGET_NAME}.service" "pbs-backup-${TARGET_NAME}.timer" 2>/dev/null || true
|
|
systemctl disable "pbs-backup-${TARGET_NAME}.service" "pbs-backup-${TARGET_NAME}.timer" 2>/dev/null || true
|
|
|
|
# Remove files
|
|
rm -f "/etc/systemd/system/pbs-backup-${TARGET_NAME}.service"
|
|
rm -f "/etc/systemd/system/pbs-backup-${TARGET_NAME}-manual.service"
|
|
rm -f "/etc/systemd/system/pbs-backup-${TARGET_NAME}.timer"
|
|
rm -f "$CONFIG_DIR/backup-${TARGET_NAME}.sh"
|
|
rm -f "$(get_target_config_path "$TARGET_NAME")"
|
|
|
|
systemctl daemon-reload
|
|
|
|
log "Target '$TARGET_NAME' deleted successfully"
|
|
}
|
|
|
|
show_target_detail() {
|
|
local target="$1"
|
|
|
|
if ! target_exists "$target"; then
|
|
error "Target '$target' does not exist"
|
|
return 1
|
|
fi
|
|
|
|
local config_file="$(get_target_config_path "$target")"
|
|
source "$config_file"
|
|
|
|
echo
|
|
echo "════════════════════════════════════════"
|
|
echo " Target: $target"
|
|
echo "════════════════════════════════════════"
|
|
echo
|
|
echo "Connection:"
|
|
echo " Server: ${PBS_SERVER}:${PBS_PORT}"
|
|
echo " Datastore: ${PBS_DATASTORE}"
|
|
echo " Repository: ${PBS_REPOSITORY}"
|
|
echo
|
|
echo "Backup Configuration:"
|
|
echo " Type: ${BACKUP_TYPE}"
|
|
|
|
if [[ "$BACKUP_TYPE" == "files" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Paths: ${BACKUP_PATHS}"
|
|
echo " Exclusions: ${EXCLUDE_PATTERNS}"
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "block" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Block Device: ${BLOCK_DEVICE}"
|
|
fi
|
|
|
|
echo
|
|
echo "Schedule:"
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Files: ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR})"
|
|
echo " Block: Weekly on Sunday"
|
|
else
|
|
echo " ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR})"
|
|
fi
|
|
|
|
echo
|
|
echo "Retention:"
|
|
echo " Last: ${KEEP_LAST}"
|
|
echo " Daily: ${KEEP_DAILY}"
|
|
echo " Weekly: ${KEEP_WEEKLY}"
|
|
echo " Monthly: ${KEEP_MONTHLY}"
|
|
echo
|
|
|
|
# Show timer status
|
|
if systemctl is-enabled "pbs-backup-${target}.timer" &>/dev/null; then
|
|
echo "Next scheduled backup:"
|
|
systemctl list-timers "pbs-backup-${target}.timer" --no-pager 2>/dev/null || true
|
|
else
|
|
warn "Timer not enabled for this target"
|
|
fi
|
|
}
|
|
|
|
# Check if running as root
|
|
check_root() {
|
|
if [ "$EUID" -ne 0 ]; then
|
|
error "This script must be run as root (use sudo)"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Detect Linux distribution
|
|
detect_distro() {
|
|
log "Detecting Linux distribution..."
|
|
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
OS=$ID
|
|
OS_VERSION=$VERSION_ID
|
|
OS_PRETTY=$PRETTY_NAME
|
|
elif [ -f /etc/lsb-release ]; then
|
|
. /etc/lsb-release
|
|
OS=$DISTRIB_ID
|
|
OS_VERSION=$DISTRIB_RELEASE
|
|
OS_PRETTY=$DISTRIB_DESCRIPTION
|
|
else
|
|
error "Cannot detect Linux distribution"
|
|
exit 1
|
|
fi
|
|
|
|
info "Detected: $OS_PRETTY"
|
|
|
|
# Normalize OS names
|
|
OS=$(echo "$OS" | tr '[:upper:]' '[:lower:]')
|
|
}
|
|
|
|
# Install PBS client on Ubuntu
|
|
install_ubuntu() {
|
|
log "Installing Proxmox Backup Client on Ubuntu $OS_VERSION..."
|
|
|
|
# Determine which repository to use
|
|
if [[ "$OS_VERSION" == "24.04" ]] || [[ "$OS_VERSION" > "24" ]]; then
|
|
REPO="bookworm"
|
|
GPG_FILE="proxmox-release-bookworm.gpg"
|
|
elif [[ "$OS_VERSION" == "22.04" ]]; then
|
|
REPO="bullseye"
|
|
GPG_FILE="proxmox-release-bullseye.gpg"
|
|
# Need to add Focal security repo for libssl1.1
|
|
NEED_FOCAL=true
|
|
elif [[ "$OS_VERSION" == "20.04" ]]; then
|
|
REPO="bullseye"
|
|
GPG_FILE="proxmox-release-bullseye.gpg"
|
|
else
|
|
error "Unsupported Ubuntu version: $OS_VERSION"
|
|
exit 1
|
|
fi
|
|
|
|
# Download and install GPG key
|
|
log "Downloading GPG key..."
|
|
wget -q "https://enterprise.proxmox.com/debian/${GPG_FILE}" \
|
|
-O "/etc/apt/trusted.gpg.d/${GPG_FILE}"
|
|
|
|
# Add PBS client repository
|
|
log "Adding PBS client repository..."
|
|
echo "deb [arch=amd64] http://download.proxmox.com/debian/pbs-client ${REPO} main" > \
|
|
/etc/apt/sources.list.d/pbs-client.list
|
|
|
|
# Add Focal security repo if needed (Ubuntu 22.04)
|
|
if [ "$NEED_FOCAL" = true ]; then
|
|
log "Adding Ubuntu Focal security repository for libssl1.1..."
|
|
echo "deb [arch=amd64] http://security.ubuntu.com/ubuntu focal-security main" > \
|
|
/etc/apt/sources.list.d/focal-security.list
|
|
fi
|
|
|
|
# Update and install
|
|
log "Updating package lists..."
|
|
apt-get update -qq
|
|
|
|
log "Installing proxmox-backup-client..."
|
|
apt-get install -y proxmox-backup-client
|
|
|
|
log "PBS client installed successfully"
|
|
}
|
|
|
|
# Install PBS client on Debian
|
|
install_debian() {
|
|
log "Installing Proxmox Backup Client on Debian $OS_VERSION..."
|
|
|
|
# Determine repository based on Debian version
|
|
case "$OS_VERSION" in
|
|
12*)
|
|
REPO="bookworm"
|
|
GPG_FILE="proxmox-release-bookworm.gpg"
|
|
;;
|
|
11*)
|
|
REPO="bullseye"
|
|
GPG_FILE="proxmox-release-bullseye.gpg"
|
|
;;
|
|
10*)
|
|
REPO="buster"
|
|
GPG_FILE="proxmox-release-buster.gpg"
|
|
;;
|
|
*)
|
|
error "Unsupported Debian version: $OS_VERSION"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Download and install GPG key
|
|
log "Downloading GPG key..."
|
|
wget -q "https://enterprise.proxmox.com/debian/${GPG_FILE}" \
|
|
-O "/etc/apt/trusted.gpg.d/${GPG_FILE}"
|
|
|
|
# Add PBS client repository
|
|
log "Adding PBS client repository..."
|
|
echo "deb [arch=amd64] http://download.proxmox.com/debian/pbs-client ${REPO} main" > \
|
|
/etc/apt/sources.list.d/pbs-client.list
|
|
|
|
# Update and install
|
|
log "Updating package lists..."
|
|
apt-get update -qq
|
|
|
|
log "Installing proxmox-backup-client..."
|
|
apt-get install -y proxmox-backup-client
|
|
|
|
log "PBS client installed successfully"
|
|
}
|
|
|
|
# Install PBS client on Arch Linux
|
|
install_arch() {
|
|
log "Installing Proxmox Backup Client on Arch Linux..."
|
|
|
|
# Check if yay is installed
|
|
if ! command -v yay &> /dev/null; then
|
|
error "yay AUR helper is required but not installed"
|
|
info "Install yay first: https://github.com/Jguer/yay"
|
|
exit 1
|
|
fi
|
|
|
|
# Get the user who invoked sudo
|
|
if [ -n "$SUDO_USER" ]; then
|
|
REAL_USER="$SUDO_USER"
|
|
else
|
|
error "Cannot determine original user. Run with sudo."
|
|
exit 1
|
|
fi
|
|
|
|
log "Installing proxmox-backup-client-bin from AUR..."
|
|
sudo -u "$REAL_USER" yay -S --noconfirm proxmox-backup-client-bin
|
|
|
|
log "PBS client installed successfully"
|
|
}
|
|
|
|
# Main installation function
|
|
install_pbs_client() {
|
|
case "$OS" in
|
|
ubuntu)
|
|
install_ubuntu
|
|
;;
|
|
debian)
|
|
install_debian
|
|
;;
|
|
arch|archlinux)
|
|
install_arch
|
|
;;
|
|
*)
|
|
error "Unsupported distribution: $OS"
|
|
info "Supported distributions: Ubuntu, Debian, Arch Linux"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Verify installation
|
|
if command -v proxmox-backup-client &> /dev/null; then
|
|
local version=$(proxmox-backup-client version 2>/dev/null | head -n1)
|
|
log "Installation verified: $version"
|
|
return 0
|
|
else
|
|
error "Installation verification failed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Reconfigure connection only (PBS server and credentials)
|
|
reconfigure_connection() {
|
|
log "Reconfiguring PBS server connection..."
|
|
echo
|
|
echo "======================================"
|
|
echo " PBS Server Connection Setup"
|
|
echo "======================================"
|
|
echo
|
|
|
|
# PBS Server details
|
|
PBS_SERVER=$(prompt "Enter PBS server IP/hostname" "192.168.1.181")
|
|
PBS_PORT=$(prompt "Enter PBS server port" "8007")
|
|
PBS_DATASTORE=$(prompt "Enter datastore name" "backups")
|
|
|
|
echo
|
|
info "Authentication Method:"
|
|
echo " 1) Username + Password"
|
|
echo " 2) API Token (recommended for automation)"
|
|
AUTH_METHOD=$(prompt "Select authentication method [1/2]" "2")
|
|
|
|
if [ "$AUTH_METHOD" = "1" ]; then
|
|
PBS_USERNAME=$(prompt "Enter username" "root")
|
|
PBS_REALM=$(prompt "Enter realm" "pam")
|
|
PBS_PASSWORD=$(prompt_password "Enter password")
|
|
PBS_REPOSITORY="${PBS_USERNAME}@${PBS_REALM}@${PBS_SERVER}:${PBS_PORT}:${PBS_DATASTORE}"
|
|
else
|
|
PBS_USERNAME=$(prompt "Enter username" "backup")
|
|
PBS_REALM=$(prompt "Enter realm" "pam")
|
|
PBS_TOKEN_NAME=$(prompt "Enter token name" "backup-token")
|
|
PBS_TOKEN_SECRET=$(prompt_password "Enter token secret")
|
|
PBS_REPOSITORY="${PBS_USERNAME}@${PBS_REALM}!${PBS_TOKEN_NAME}@${PBS_SERVER}:${PBS_PORT}:${PBS_DATASTORE}"
|
|
PBS_PASSWORD="$PBS_TOKEN_SECRET"
|
|
fi
|
|
|
|
# Test the new connection
|
|
if ! test_connection; then
|
|
error "Connection test failed with new credentials"
|
|
RETRY=$(prompt "Try again? (yes/no)" "yes")
|
|
if [[ "$RETRY" == "yes" ]]; then
|
|
reconfigure_connection
|
|
return
|
|
else
|
|
error "Cannot proceed without successful connection"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Load existing configuration and update only connection details
|
|
if [ -f "$CONFIG_DIR/config" ]; then
|
|
log "Loading existing backup configuration..."
|
|
source "$CONFIG_DIR/config"
|
|
else
|
|
error "No existing configuration found. Please run full configuration."
|
|
exit 1
|
|
fi
|
|
|
|
# Update config file with new connection details
|
|
log "Updating configuration file..."
|
|
|
|
# Strip any trailing newlines from password (defensive fix)
|
|
PBS_PASSWORD_CLEAN=$(echo -n "$PBS_PASSWORD" | tr -d '\n\r')
|
|
|
|
cat > "$CONFIG_DIR/config" <<EOF
|
|
# PBS Client Configuration
|
|
PBS_REPOSITORY="${PBS_REPOSITORY}"
|
|
PBS_PASSWORD="${PBS_PASSWORD_CLEAN}"
|
|
BACKUP_TYPE="${BACKUP_TYPE}"
|
|
BACKUP_PATHS="${BACKUP_PATHS}"
|
|
EXCLUDE_PATTERNS="${EXCLUDE_PATTERNS}"
|
|
BLOCK_DEVICE="${BLOCK_DEVICE}"
|
|
KEEP_LAST=${KEEP_LAST}
|
|
KEEP_DAILY=${KEEP_DAILY}
|
|
KEEP_WEEKLY=${KEEP_WEEKLY}
|
|
KEEP_MONTHLY=${KEEP_MONTHLY}
|
|
EOF
|
|
|
|
chmod 600 "$CONFIG_DIR/config"
|
|
|
|
log "Connection configuration updated successfully!"
|
|
echo
|
|
info "Updated Connection:"
|
|
echo " PBS Server: ${PBS_SERVER}:${PBS_PORT}"
|
|
echo " Datastore: ${PBS_DATASTORE}"
|
|
echo " Repository: ${PBS_REPOSITORY}"
|
|
echo
|
|
info "All other backup settings remain unchanged."
|
|
}
|
|
|
|
# Reconfigure backup settings only (type, schedule, retention)
|
|
reconfigure_backup_settings() {
|
|
log "Reconfiguring backup settings..."
|
|
echo
|
|
|
|
# Load existing configuration to preserve connection details
|
|
if [ -f "$CONFIG_DIR/config" ]; then
|
|
source "$CONFIG_DIR/config"
|
|
else
|
|
error "No existing configuration found. Please run full configuration."
|
|
exit 1
|
|
fi
|
|
|
|
# Preserve connection details
|
|
local SAVED_PBS_REPOSITORY="$PBS_REPOSITORY"
|
|
local SAVED_PBS_PASSWORD="$PBS_PASSWORD"
|
|
|
|
# Backup type selection
|
|
info "Backup Type:"
|
|
echo " 1) File-level only (.pxar) - Fast, efficient, selective restore"
|
|
echo " 2) Block device only (.img) - Full disk image, bootable as VM"
|
|
echo " 3) Both (Hybrid) - Daily files + Weekly block device (recommended)"
|
|
|
|
local CURRENT_BACKUP_TYPE_NUM=""
|
|
case "$BACKUP_TYPE" in
|
|
files) CURRENT_BACKUP_TYPE_NUM="1" ;;
|
|
block) CURRENT_BACKUP_TYPE_NUM="2" ;;
|
|
both) CURRENT_BACKUP_TYPE_NUM="3" ;;
|
|
esac
|
|
|
|
BACKUP_TYPE_CHOICE=$(prompt "Select backup type [1/2/3]" "$CURRENT_BACKUP_TYPE_NUM")
|
|
|
|
case "$BACKUP_TYPE_CHOICE" in
|
|
1)
|
|
BACKUP_TYPE="files"
|
|
;;
|
|
2)
|
|
BACKUP_TYPE="block"
|
|
;;
|
|
3)
|
|
BACKUP_TYPE="both"
|
|
;;
|
|
*)
|
|
warn "Invalid choice, keeping current setting: $BACKUP_TYPE"
|
|
;;
|
|
esac
|
|
|
|
echo
|
|
# Backup configuration
|
|
info "Backup Configuration:"
|
|
|
|
if [[ "$BACKUP_TYPE" == "files" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
BACKUP_PATHS=$(prompt "Enter paths to backup (space-separated)" "$BACKUP_PATHS")
|
|
EXCLUDE_PATTERNS=$(prompt "Enter exclusion patterns (space-separated)" "$EXCLUDE_PATTERNS")
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "block" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo
|
|
info "Block Device Configuration:"
|
|
info "Current device: ${BLOCK_DEVICE:-none}"
|
|
|
|
# Try to auto-detect root device if none set
|
|
if [ -z "$BLOCK_DEVICE" ]; then
|
|
ROOT_DEVICE=$(findmnt -n -o SOURCE / | sed 's/\[.*\]$//' | sed 's/[0-9]*$//' | sed 's/p$//' 2>/dev/null || echo "")
|
|
if [ -n "$ROOT_DEVICE" ] && [ -b "$ROOT_DEVICE" ]; then
|
|
BLOCK_DEVICE="$ROOT_DEVICE"
|
|
fi
|
|
fi
|
|
|
|
BLOCK_DEVICE=$(prompt "Enter block device to backup" "$BLOCK_DEVICE")
|
|
|
|
# Verify it's a block device
|
|
if [ ! -b "$BLOCK_DEVICE" ]; then
|
|
warn "Warning: $BLOCK_DEVICE does not exist or is not a block device"
|
|
info "Available block devices:"
|
|
lsblk -d -n -o NAME,SIZE,TYPE | grep disk | awk '{print " /dev/"$1" ("$2")"}'
|
|
echo
|
|
CONTINUE=$(prompt "Continue anyway? (yes/no)" "no")
|
|
if [[ "$CONTINUE" != "yes" ]]; then
|
|
error "Configuration cancelled"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
info "Backup Schedule:"
|
|
echo " 1) Hourly"
|
|
echo " 2) Daily (recommended)"
|
|
echo " 3) Weekly"
|
|
echo " 4) Custom"
|
|
SCHEDULE_TYPE=$(prompt "Select schedule type [1/2/3/4]" "2")
|
|
|
|
case "$SCHEDULE_TYPE" in
|
|
1)
|
|
TIMER_SCHEDULE="hourly"
|
|
TIMER_ONCALENDAR="hourly"
|
|
;;
|
|
2)
|
|
BACKUP_HOUR=$(prompt "Enter hour for daily backup (0-23)" "2")
|
|
TIMER_SCHEDULE="daily"
|
|
TIMER_ONCALENDAR="*-*-* ${BACKUP_HOUR}:00:00"
|
|
;;
|
|
3)
|
|
BACKUP_DOW=$(prompt "Enter day of week (Mon-Sun)" "Sun")
|
|
BACKUP_HOUR=$(prompt "Enter hour (0-23)" "2")
|
|
TIMER_SCHEDULE="weekly"
|
|
TIMER_ONCALENDAR="${BACKUP_DOW} *-*-* ${BACKUP_HOUR}:00:00"
|
|
;;
|
|
4)
|
|
TIMER_ONCALENDAR=$(prompt "Enter systemd OnCalendar format" "*-*-* 02:00:00")
|
|
TIMER_SCHEDULE="custom"
|
|
;;
|
|
esac
|
|
|
|
echo
|
|
# Retention policy
|
|
info "Retention Policy:"
|
|
KEEP_LAST=$(prompt "Keep last N backups" "${KEEP_LAST:-3}")
|
|
KEEP_DAILY=$(prompt "Keep daily backups for N days" "${KEEP_DAILY:-7}")
|
|
KEEP_WEEKLY=$(prompt "Keep weekly backups for N weeks" "${KEEP_WEEKLY:-4}")
|
|
KEEP_MONTHLY=$(prompt "Keep monthly backups for N months" "${KEEP_MONTHLY:-6}")
|
|
|
|
# Restore connection details
|
|
PBS_REPOSITORY="$SAVED_PBS_REPOSITORY"
|
|
PBS_PASSWORD="$SAVED_PBS_PASSWORD"
|
|
|
|
# Regenerate systemd service with new settings
|
|
echo
|
|
log "Updating systemd service configuration..."
|
|
create_systemd_service
|
|
|
|
# Restart services
|
|
log "Restarting backup timer..."
|
|
systemctl daemon-reload
|
|
systemctl restart pbs-backup.timer
|
|
|
|
echo
|
|
log "Backup settings updated successfully!"
|
|
echo
|
|
info "New Configuration:"
|
|
echo " Backup Type: ${BACKUP_TYPE}"
|
|
|
|
if [[ "$BACKUP_TYPE" == "files" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Backup Paths: ${BACKUP_PATHS}"
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "block" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Block Device: ${BLOCK_DEVICE}"
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Schedule: Files ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR}), Block device weekly (Sunday)"
|
|
else
|
|
echo " Schedule: ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR})"
|
|
fi
|
|
|
|
echo " Retention: Last=${KEEP_LAST} Daily=${KEEP_DAILY} Weekly=${KEEP_WEEKLY} Monthly=${KEEP_MONTHLY}"
|
|
echo
|
|
|
|
# Show service status
|
|
info "Backup Service Status:"
|
|
echo "────────────────────────────────────────────────────────────"
|
|
systemctl status pbs-backup.timer --no-pager -l || true
|
|
echo "────────────────────────────────────────────────────────────"
|
|
echo
|
|
info "Next scheduled backup:"
|
|
systemctl list-timers pbs-backup.timer --no-pager || true
|
|
}
|
|
|
|
# Per-target wrapper functions
|
|
interactive_config_for_target() {
|
|
local target_name="$1"
|
|
TARGET_NAME="$target_name"
|
|
|
|
log "Configuring backup target: $target_name"
|
|
|
|
# Run standard interactive config
|
|
interactive_config
|
|
|
|
# Create systemd service for this target
|
|
create_systemd_service_for_target "$target_name"
|
|
|
|
# Test connection
|
|
if ! test_connection; then
|
|
error "Connection test failed"
|
|
return 1
|
|
fi
|
|
|
|
log "Target '$target_name' configured successfully!"
|
|
}
|
|
|
|
reconfigure_connection_for_target() {
|
|
local target_name="$1"
|
|
TARGET_NAME="$target_name"
|
|
|
|
# Load existing config
|
|
if [ ! -f "$(get_target_config_path "$target_name")" ]; then
|
|
error "Target '$target_name' not found"
|
|
return 1
|
|
fi
|
|
|
|
source "$(get_target_config_path "$target_name")"
|
|
|
|
# Run standard reconfigure_connection
|
|
reconfigure_connection
|
|
|
|
# Update systemd service
|
|
create_systemd_service_for_target "$target_name"
|
|
|
|
# Restart timer
|
|
systemctl daemon-reload
|
|
systemctl restart "pbs-backup-${target_name}.timer"
|
|
|
|
log "Connection reconfigured for target '$target_name'"
|
|
}
|
|
|
|
reconfigure_backup_settings_for_target() {
|
|
local target_name="$1"
|
|
TARGET_NAME="$target_name"
|
|
|
|
# Load existing config
|
|
if [ ! -f "$(get_target_config_path "$target_name")" ]; then
|
|
error "Target '$target_name' not found"
|
|
return 1
|
|
fi
|
|
|
|
source "$(get_target_config_path "$target_name")"
|
|
|
|
# Run standard reconfigure_backup_settings
|
|
reconfigure_backup_settings
|
|
}
|
|
|
|
run_backup_for_target() {
|
|
local target_name="$1"
|
|
|
|
if ! target_exists "$target_name"; then
|
|
error "Target '$target_name' does not exist"
|
|
return 1
|
|
fi
|
|
|
|
# Load target config to check backup type
|
|
local config_file="$(get_target_config_path "$target_name")"
|
|
source "$config_file"
|
|
|
|
# If hybrid mode, ask what to backup
|
|
local BACKUP_MODE="yes" # Default: full backup
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo
|
|
info "This target is configured for hybrid backups (files + block device)"
|
|
echo
|
|
echo "What would you like to backup?"
|
|
echo " 1) Files only (fast ~2-3 minutes)"
|
|
echo " 2) Block device only (slow ~20-30 minutes, enables VM conversion)"
|
|
echo " 3) Both files and block device (full backup)"
|
|
BACKUP_CHOICE=$(prompt "Select option [1/2/3]" "3")
|
|
|
|
case "$BACKUP_CHOICE" in
|
|
1)
|
|
BACKUP_MODE="files"
|
|
info "Running files-only backup for target: $target_name"
|
|
;;
|
|
2)
|
|
BACKUP_MODE="block"
|
|
info "Running block device backup for target: $target_name"
|
|
;;
|
|
3)
|
|
BACKUP_MODE="yes"
|
|
info "Running FULL backup (files + block device) for target: $target_name"
|
|
;;
|
|
*)
|
|
warn "Invalid choice, defaulting to full backup"
|
|
BACKUP_MODE="yes"
|
|
info "Running FULL backup for target: $target_name"
|
|
;;
|
|
esac
|
|
else
|
|
info "Running backup for target: $target_name"
|
|
fi
|
|
|
|
echo
|
|
|
|
# Show real-time progress
|
|
echo "╔════════════════════════════════════════════════════════════╗"
|
|
echo "║ Backup Progress (Live) - Target: $target_name"
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo "║ Mode: $BACKUP_MODE"
|
|
fi
|
|
echo "║ Press Ctrl+C to stop viewing (backup continues) ║"
|
|
echo "╚════════════════════════════════════════════════════════════╝"
|
|
echo
|
|
|
|
# Run backup script directly with the chosen mode
|
|
if "$CONFIG_DIR/backup-${target_name}.sh" "$BACKUP_MODE"; then
|
|
echo
|
|
log "Backup completed successfully!"
|
|
else
|
|
echo
|
|
error "Backup failed!"
|
|
echo
|
|
info "Check logs above for details"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Interactive configuration
|
|
interactive_config() {
|
|
log "Starting interactive configuration..."
|
|
echo
|
|
echo "======================================"
|
|
echo " PBS Client Configuration"
|
|
echo "======================================"
|
|
echo
|
|
|
|
# PBS Server details
|
|
PBS_SERVER=$(prompt "Enter PBS server IP/hostname" "192.168.1.181")
|
|
PBS_PORT=$(prompt "Enter PBS server port" "8007")
|
|
PBS_DATASTORE=$(prompt "Enter datastore name" "backups")
|
|
|
|
echo
|
|
info "Authentication Method:"
|
|
echo " 1) Username + Password"
|
|
echo " 2) API Token (recommended for automation)"
|
|
AUTH_METHOD=$(prompt "Select authentication method [1/2]" "2")
|
|
|
|
if [ "$AUTH_METHOD" = "1" ]; then
|
|
PBS_USERNAME=$(prompt "Enter username" "root")
|
|
PBS_REALM=$(prompt "Enter realm" "pam")
|
|
PBS_PASSWORD=$(prompt_password "Enter password")
|
|
PBS_REPOSITORY="${PBS_USERNAME}@${PBS_REALM}@${PBS_SERVER}:${PBS_PORT}:${PBS_DATASTORE}"
|
|
else
|
|
PBS_USERNAME=$(prompt "Enter username" "backup")
|
|
PBS_REALM=$(prompt "Enter realm" "pam")
|
|
PBS_TOKEN_NAME=$(prompt "Enter token name" "backup-token")
|
|
PBS_TOKEN_SECRET=$(prompt_password "Enter token secret")
|
|
PBS_REPOSITORY="${PBS_USERNAME}@${PBS_REALM}!${PBS_TOKEN_NAME}@${PBS_SERVER}:${PBS_PORT}:${PBS_DATASTORE}"
|
|
PBS_PASSWORD="$PBS_TOKEN_SECRET"
|
|
fi
|
|
|
|
echo
|
|
# Backup type selection
|
|
info "Backup Type:"
|
|
echo " 1) File-level only (.pxar) - Fast, efficient, selective restore"
|
|
echo " 2) Block device only (.img) - Full disk image, bootable as VM"
|
|
echo " 3) Both (Hybrid) - Daily files + Weekly block device (recommended)"
|
|
BACKUP_TYPE_CHOICE=$(prompt "Select backup type [1/2/3]" "3")
|
|
|
|
case "$BACKUP_TYPE_CHOICE" in
|
|
1)
|
|
BACKUP_TYPE="files"
|
|
;;
|
|
2)
|
|
BACKUP_TYPE="block"
|
|
;;
|
|
3)
|
|
BACKUP_TYPE="both"
|
|
;;
|
|
*)
|
|
warn "Invalid choice, defaulting to hybrid (both)"
|
|
BACKUP_TYPE="both"
|
|
;;
|
|
esac
|
|
|
|
echo
|
|
# Backup configuration
|
|
info "Backup Configuration:"
|
|
|
|
if [[ "$BACKUP_TYPE" == "files" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
BACKUP_PATHS=$(prompt "Enter paths to backup (space-separated)" "/")
|
|
EXCLUDE_PATTERNS=$(prompt "Enter exclusion patterns (space-separated)" "/tmp /var/tmp /var/cache /proc /sys /dev /run")
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "block" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo
|
|
info "Block Device Configuration:"
|
|
|
|
# Try to auto-detect root device
|
|
# Strip subvolume notation [/@], partition numbers, and trailing 'p'
|
|
ROOT_DEVICE=$(findmnt -n -o SOURCE / | sed 's/\[.*\]$//' | sed 's/[0-9]*$//' | sed 's/p$//' 2>/dev/null || echo "")
|
|
|
|
if [ -n "$ROOT_DEVICE" ] && [ -b "$ROOT_DEVICE" ]; then
|
|
info "Auto-detected root device: $ROOT_DEVICE"
|
|
BLOCK_DEVICE=$(prompt "Enter block device to backup" "$ROOT_DEVICE")
|
|
else
|
|
if [ -n "$ROOT_DEVICE" ]; then
|
|
warn "Detected device $ROOT_DEVICE is not a valid block device"
|
|
fi
|
|
info "Common devices: /dev/sda, /dev/nvme0n1, /dev/vda"
|
|
BLOCK_DEVICE=$(prompt "Enter block device to backup" "/dev/sda")
|
|
fi
|
|
|
|
# Verify it's a block device
|
|
if [ ! -b "$BLOCK_DEVICE" ]; then
|
|
warn "Warning: $BLOCK_DEVICE does not exist or is not a block device"
|
|
info "Available block devices:"
|
|
lsblk -d -n -o NAME,SIZE,TYPE | grep disk | awk '{print " /dev/"$1" ("$2")"}'
|
|
echo
|
|
CONTINUE=$(prompt "Continue anyway? (yes/no)" "no")
|
|
if [[ "$CONTINUE" != "yes" ]]; then
|
|
error "Installation cancelled"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
info "Backup Schedule:"
|
|
echo " 1) Hourly"
|
|
echo " 2) Daily (recommended)"
|
|
echo " 3) Weekly"
|
|
echo " 4) Custom"
|
|
SCHEDULE_TYPE=$(prompt "Select schedule type [1/2/3/4]" "2")
|
|
|
|
case "$SCHEDULE_TYPE" in
|
|
1)
|
|
TIMER_SCHEDULE="hourly"
|
|
TIMER_ONCALENDAR="hourly"
|
|
;;
|
|
2)
|
|
BACKUP_HOUR=$(prompt "Enter hour for daily backup (0-23)" "2")
|
|
TIMER_SCHEDULE="daily"
|
|
TIMER_ONCALENDAR="*-*-* ${BACKUP_HOUR}:00:00"
|
|
;;
|
|
3)
|
|
BACKUP_DOW=$(prompt "Enter day of week (Mon-Sun)" "Sun")
|
|
BACKUP_HOUR=$(prompt "Enter hour (0-23)" "2")
|
|
TIMER_SCHEDULE="weekly"
|
|
TIMER_ONCALENDAR="${BACKUP_DOW} *-*-* ${BACKUP_HOUR}:00:00"
|
|
;;
|
|
4)
|
|
TIMER_ONCALENDAR=$(prompt "Enter systemd OnCalendar format" "*-*-* 02:00:00")
|
|
TIMER_SCHEDULE="custom"
|
|
;;
|
|
esac
|
|
|
|
# If hybrid mode, ask for block device schedule separately
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo
|
|
info "Block Device Backup Schedule:"
|
|
echo " Block device backups are slower (20-30 min) but enable VM conversion."
|
|
echo " File backups will run on the schedule above."
|
|
echo
|
|
echo " 1) Weekly (recommended - every Sunday)"
|
|
echo " 2) Biweekly (every other Sunday)"
|
|
echo " 3) Monthly (1st of each month)"
|
|
echo " 4) Custom day of week"
|
|
echo " 5) Custom day of month"
|
|
BLOCK_SCHEDULE_TYPE=$(prompt "Select block device schedule [1/2/3/4/5]" "1")
|
|
|
|
case "$BLOCK_SCHEDULE_TYPE" in
|
|
1)
|
|
BLOCK_DEVICE_FREQUENCY="weekly"
|
|
BLOCK_DEVICE_DAY="7" # Sunday
|
|
;;
|
|
2)
|
|
BLOCK_DEVICE_FREQUENCY="biweekly"
|
|
BLOCK_DEVICE_DAY="7" # Sunday
|
|
;;
|
|
3)
|
|
BLOCK_DEVICE_FREQUENCY="monthly"
|
|
BLOCK_DEVICE_DAY="1" # 1st of month
|
|
;;
|
|
4)
|
|
BLOCK_BACKUP_DOW=$(prompt "Enter day of week for block device (Mon-Sun)" "Sun")
|
|
BLOCK_DEVICE_FREQUENCY="weekly"
|
|
# Convert day name to number (1=Mon, 7=Sun)
|
|
case "$BLOCK_BACKUP_DOW" in
|
|
Mon|mon|MON|Monday|1) BLOCK_DEVICE_DAY="1" ;;
|
|
Tue|tue|TUE|Tuesday|2) BLOCK_DEVICE_DAY="2" ;;
|
|
Wed|wed|WED|Wednesday|3) BLOCK_DEVICE_DAY="3" ;;
|
|
Thu|thu|THU|Thursday|4) BLOCK_DEVICE_DAY="4" ;;
|
|
Fri|fri|FRI|Friday|5) BLOCK_DEVICE_DAY="5" ;;
|
|
Sat|sat|SAT|Saturday|6) BLOCK_DEVICE_DAY="6" ;;
|
|
Sun|sun|SUN|Sunday|7) BLOCK_DEVICE_DAY="7" ;;
|
|
*) BLOCK_DEVICE_DAY="7" ;;
|
|
esac
|
|
;;
|
|
5)
|
|
BLOCK_BACKUP_DOM=$(prompt "Enter day of month (1-31)" "1")
|
|
BLOCK_DEVICE_FREQUENCY="monthly"
|
|
BLOCK_DEVICE_DAY="$BLOCK_BACKUP_DOM"
|
|
;;
|
|
*)
|
|
warn "Invalid choice, defaulting to weekly (Sunday)"
|
|
BLOCK_DEVICE_FREQUENCY="weekly"
|
|
BLOCK_DEVICE_DAY="7"
|
|
;;
|
|
esac
|
|
else
|
|
# Not hybrid mode, set defaults (won't be used)
|
|
BLOCK_DEVICE_FREQUENCY="weekly"
|
|
BLOCK_DEVICE_DAY="7"
|
|
fi
|
|
|
|
echo
|
|
# Retention policy
|
|
info "Retention Policy:"
|
|
KEEP_LAST=$(prompt "Keep last N backups" "3")
|
|
KEEP_DAILY=$(prompt "Keep daily backups for N days" "7")
|
|
KEEP_WEEKLY=$(prompt "Keep weekly backups for N weeks" "4")
|
|
KEEP_MONTHLY=$(prompt "Keep monthly backups for N months" "6")
|
|
|
|
# Encryption
|
|
echo
|
|
ENABLE_ENCRYPTION=$(prompt "Enable encryption? (yes/no)" "no")
|
|
}
|
|
|
|
# Create encryption key
|
|
create_encryption_key() {
|
|
if [[ "$ENABLE_ENCRYPTION" == "yes" ]]; then
|
|
log "Creating encryption key..."
|
|
|
|
# Create config directory for root
|
|
mkdir -p /root/.config/proxmox-backup
|
|
|
|
# Generate key without password (for automated backups)
|
|
if proxmox-backup-client key create --kdf none 2>/dev/null; then
|
|
log "Encryption key created successfully"
|
|
|
|
# Create paper backup
|
|
local paper_key="/root/pbs-encryption-key-$(date +%Y%m%d).txt"
|
|
proxmox-backup-client key paperkey --output-format text > "$paper_key"
|
|
|
|
warn "IMPORTANT: Encryption key paper backup saved to: $paper_key"
|
|
warn "Print this file and store it securely. Lost keys = permanent data loss!"
|
|
|
|
# Copy to config dir for service
|
|
cp /root/.config/proxmox-backup/encryption-key.json "$CONFIG_DIR/"
|
|
else
|
|
error "Failed to create encryption key"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Test connection to PBS
|
|
test_connection() {
|
|
log "Testing connection to PBS server..."
|
|
|
|
export PBS_REPOSITORY="$PBS_REPOSITORY"
|
|
export PBS_PASSWORD="$PBS_PASSWORD"
|
|
|
|
# Step 1: Verify server is reachable
|
|
info "Step 1/3: Checking if server is reachable..."
|
|
if ! timeout 5 bash -c "curl -sk --max-time 5 https://${PBS_SERVER}:${PBS_PORT} >/dev/null 2>&1"; then
|
|
echo
|
|
error "Server is not reachable at https://${PBS_SERVER}:${PBS_PORT}"
|
|
error "Possible issues:"
|
|
error " - PBS server is down or unreachable"
|
|
error " - Wrong IP/hostname"
|
|
error " - Firewall blocking port ${PBS_PORT}"
|
|
error " - Network connectivity issues"
|
|
echo
|
|
info "Troubleshooting:"
|
|
echo " 1. Verify server is reachable: ping ${PBS_SERVER}"
|
|
echo " 2. Test HTTPS connection: curl -k https://${PBS_SERVER}:${PBS_PORT}"
|
|
echo " 3. Check if PBS web interface is accessible in browser"
|
|
return 1
|
|
fi
|
|
log "Server is reachable"
|
|
|
|
# Step 2: Test authentication and handle SSL fingerprint
|
|
info "Step 2/3: Testing authentication..."
|
|
|
|
# Use the 'login' command which tests authentication and stores a ticket
|
|
# Automatically accept SSL fingerprint by piping 'y'
|
|
local test_output
|
|
if test_output=$(echo "y" | timeout 15 proxmox-backup-client login 2>&1); then
|
|
# Check if fingerprint was accepted
|
|
if echo "$test_output" | grep -q "fingerprint:"; then
|
|
local fingerprint=$(echo "$test_output" | grep "fingerprint:" | head -1 | awk '{print $2}')
|
|
info "SSL fingerprint accepted: ${fingerprint}"
|
|
fi
|
|
log "Authentication successful"
|
|
else
|
|
local exit_code=$?
|
|
echo
|
|
|
|
if [ $exit_code -eq 124 ]; then
|
|
error "Authentication test timed out"
|
|
error "This might indicate a network issue or server problem"
|
|
else
|
|
error "Authentication failed"
|
|
# Show the actual error from PBS client
|
|
if echo "$test_output" | grep -q "error\|Error"; then
|
|
echo
|
|
echo "$test_output" | grep -i "error" | head -5
|
|
echo
|
|
fi
|
|
fi
|
|
|
|
error "Possible issues:"
|
|
error " - Invalid credentials (username/password/token)"
|
|
error " - Datastore '${PBS_DATASTORE}' does not exist"
|
|
error " - User lacks permissions for the datastore"
|
|
error " - API token is not properly formatted"
|
|
error " - SSL certificate issues"
|
|
echo
|
|
info "Troubleshooting:"
|
|
echo " 1. Verify credentials in PBS web interface"
|
|
echo " 2. Check datastore name: ${PBS_DATASTORE}"
|
|
echo " 3. Verify user has backup permissions"
|
|
echo " 4. For API tokens, format is: username@realm!tokenname"
|
|
echo " 5. Try the test-connection.sh script for detailed diagnostics"
|
|
echo
|
|
info "Debug info:"
|
|
echo " Repository: ${PBS_REPOSITORY}"
|
|
return 1
|
|
fi
|
|
|
|
# Step 3: Verify datastore access by listing backup groups
|
|
info "Step 3/3: Verifying datastore access..."
|
|
if timeout 15 proxmox-backup-client list 2>/dev/null; then
|
|
log "Datastore access verified"
|
|
log "Connection test successful!"
|
|
return 0
|
|
else
|
|
warn "Could not list backup groups (this is normal if no backups exist yet)"
|
|
log "Connection test successful!"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Create systemd service
|
|
create_systemd_service() {
|
|
log "Creating systemd service and timer..."
|
|
|
|
# Create config directory
|
|
mkdir -p "$CONFIG_DIR"
|
|
|
|
# Strip any trailing newlines from password (defensive fix)
|
|
PBS_PASSWORD_CLEAN=$(echo -n "$PBS_PASSWORD" | tr -d '\n\r')
|
|
|
|
# Save configuration
|
|
cat > "$CONFIG_DIR/config" <<EOF
|
|
# PBS Client Configuration
|
|
PBS_REPOSITORY="${PBS_REPOSITORY}"
|
|
PBS_PASSWORD="${PBS_PASSWORD_CLEAN}"
|
|
BACKUP_TYPE="${BACKUP_TYPE}"
|
|
BACKUP_PATHS="${BACKUP_PATHS}"
|
|
EXCLUDE_PATTERNS="${EXCLUDE_PATTERNS}"
|
|
BLOCK_DEVICE="${BLOCK_DEVICE}"
|
|
KEEP_LAST=${KEEP_LAST}
|
|
KEEP_DAILY=${KEEP_DAILY}
|
|
KEEP_WEEKLY=${KEEP_WEEKLY}
|
|
KEEP_MONTHLY=${KEEP_MONTHLY}
|
|
EOF
|
|
|
|
chmod 600 "$CONFIG_DIR/config"
|
|
|
|
# Create backup script
|
|
cat > "$CONFIG_DIR/backup.sh" <<'EOFSCRIPT'
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
# Check if forcing full backup (for manual runs)
|
|
FORCE_FULL="${1:-no}"
|
|
|
|
# Load configuration
|
|
source /etc/proxmox-backup-client/config
|
|
|
|
# Export for PBS client
|
|
export PBS_REPOSITORY
|
|
export PBS_PASSWORD
|
|
|
|
HOSTNAME=$(hostname)
|
|
BACKUP_SUCCESS=true
|
|
|
|
log() {
|
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
|
|
}
|
|
|
|
# Function to create file-level backup
|
|
backup_files() {
|
|
log "Starting file-level backup (.pxar)..."
|
|
|
|
BACKUP_CMD="proxmox-backup-client backup"
|
|
|
|
# Add each path as separate archive
|
|
for path in $BACKUP_PATHS; do
|
|
# Sanitize path for archive name
|
|
archive_name=$(echo "$path" | sed 's/^\///' | sed 's/\//-/g')
|
|
[ -z "$archive_name" ] && archive_name="root"
|
|
|
|
BACKUP_CMD="$BACKUP_CMD ${archive_name}.pxar:${path}"
|
|
|
|
# Add --include-dev for mount points
|
|
if mountpoint -q "$path" 2>/dev/null; then
|
|
BACKUP_CMD="$BACKUP_CMD --include-dev ${path}"
|
|
fi
|
|
done
|
|
|
|
# Add exclusions
|
|
for pattern in $EXCLUDE_PATTERNS; do
|
|
BACKUP_CMD="$BACKUP_CMD --exclude=${pattern}"
|
|
done
|
|
|
|
# Add other options
|
|
BACKUP_CMD="$BACKUP_CMD --skip-lost-and-found --change-detection-mode=metadata"
|
|
|
|
# Execute backup
|
|
if eval $BACKUP_CMD; then
|
|
log "File-level backup completed successfully"
|
|
return 0
|
|
else
|
|
log "File-level backup FAILED" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to create block device backup
|
|
backup_block_device() {
|
|
log "Starting block device backup (.img)..."
|
|
|
|
if [ -z "$BLOCK_DEVICE" ]; then
|
|
log "ERROR: BLOCK_DEVICE not configured" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -b "$BLOCK_DEVICE" ]; then
|
|
log "ERROR: $BLOCK_DEVICE is not a block device" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Get device size
|
|
DEVICE_SIZE=$(blockdev --getsize64 "$BLOCK_DEVICE" 2>/dev/null || echo "0")
|
|
DEVICE_SIZE_GB=$((DEVICE_SIZE / 1024 / 1024 / 1024))
|
|
|
|
log "Backing up device: $BLOCK_DEVICE (${DEVICE_SIZE_GB}GB)"
|
|
log "This may take a while..."
|
|
|
|
# Sanitize device name for archive
|
|
DEVICE_NAME=$(basename "$BLOCK_DEVICE")
|
|
|
|
# Create block device backup
|
|
if proxmox-backup-client backup "${DEVICE_NAME}.img:${BLOCK_DEVICE}"; then
|
|
log "Block device backup completed successfully"
|
|
return 0
|
|
else
|
|
log "Block device backup FAILED" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Main backup execution
|
|
log "Starting backup for ${HOSTNAME}"
|
|
log "Backup type: ${BACKUP_TYPE}"
|
|
|
|
case "$BACKUP_TYPE" in
|
|
files)
|
|
backup_files || BACKUP_SUCCESS=false
|
|
;;
|
|
block)
|
|
backup_block_device || BACKUP_SUCCESS=false
|
|
;;
|
|
both)
|
|
# Do file backup first (faster, more frequent)
|
|
backup_files || BACKUP_SUCCESS=false
|
|
|
|
# Do block device backup on Sunday OR if manually forced
|
|
if [ "$FORCE_FULL" = "yes" ]; then
|
|
log "Manual full backup - including block device"
|
|
backup_block_device || BACKUP_SUCCESS=false
|
|
elif [ "$(date +%u)" -eq 7 ]; then
|
|
log "Weekly block device backup day (Sunday)"
|
|
backup_block_device || BACKUP_SUCCESS=false
|
|
else
|
|
log "Skipping block device backup (runs weekly on Sunday, or use 'Run backup now' for immediate full backup)"
|
|
fi
|
|
;;
|
|
*)
|
|
log "ERROR: Invalid BACKUP_TYPE: ${BACKUP_TYPE}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Prune old backups
|
|
if [ "$BACKUP_SUCCESS" = true ]; then
|
|
log "Pruning old backups..."
|
|
proxmox-backup-client prune "host/${HOSTNAME}" \
|
|
--keep-last $KEEP_LAST \
|
|
--keep-daily $KEEP_DAILY \
|
|
--keep-weekly $KEEP_WEEKLY \
|
|
--keep-monthly $KEEP_MONTHLY
|
|
|
|
log "Backup and prune completed successfully"
|
|
else
|
|
log "Backup FAILED" >&2
|
|
exit 1
|
|
fi
|
|
EOFSCRIPT
|
|
|
|
chmod 700 "$CONFIG_DIR/backup.sh"
|
|
|
|
# Create systemd service file (for scheduled backups)
|
|
cat > /etc/systemd/system/pbs-backup.service <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Backup
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$CONFIG_DIR/backup.sh
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=pbs-backup
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create systemd service file for manual full backups
|
|
cat > /etc/systemd/system/pbs-backup-manual.service <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Manual Full Backup
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$CONFIG_DIR/backup.sh yes
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=pbs-backup
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create systemd timer file
|
|
cat > /etc/systemd/system/pbs-backup.timer <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Backup Timer
|
|
Requires=pbs-backup.service
|
|
|
|
[Timer]
|
|
OnCalendar=${TIMER_ONCALENDAR}
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
|
|
# Reload systemd and enable timer
|
|
systemctl daemon-reload
|
|
systemctl enable pbs-backup.timer
|
|
systemctl start pbs-backup.timer
|
|
|
|
log "Systemd service and timer created successfully"
|
|
|
|
# Show timer status
|
|
info "Timer status:"
|
|
systemctl status pbs-backup.timer --no-pager || true
|
|
}
|
|
|
|
# Create systemd service for a named target
|
|
create_systemd_service_for_target() {
|
|
local target_name="$1"
|
|
|
|
log "Creating systemd service and timer for target: $target_name"
|
|
|
|
# Create targets directory
|
|
mkdir -p "$TARGETS_DIR"
|
|
|
|
# Strip any trailing newlines from password (defensive fix)
|
|
PBS_PASSWORD_CLEAN=$(echo -n "$PBS_PASSWORD" | tr -d '\n\r')
|
|
|
|
# Save configuration for this target
|
|
cat > "$(get_target_config_path "$target_name")" <<EOF
|
|
# PBS Client Configuration - Target: $target_name
|
|
PBS_SERVER="${PBS_SERVER}"
|
|
PBS_PORT="${PBS_PORT}"
|
|
PBS_DATASTORE="${PBS_DATASTORE}"
|
|
PBS_REPOSITORY="${PBS_REPOSITORY}"
|
|
PBS_PASSWORD="${PBS_PASSWORD_CLEAN}"
|
|
BACKUP_TYPE="${BACKUP_TYPE}"
|
|
BACKUP_PATHS="${BACKUP_PATHS}"
|
|
EXCLUDE_PATTERNS="${EXCLUDE_PATTERNS}"
|
|
BLOCK_DEVICE="${BLOCK_DEVICE}"
|
|
BLOCK_DEVICE_FREQUENCY="${BLOCK_DEVICE_FREQUENCY}"
|
|
BLOCK_DEVICE_DAY="${BLOCK_DEVICE_DAY}"
|
|
TIMER_SCHEDULE="${TIMER_SCHEDULE}"
|
|
TIMER_ONCALENDAR="${TIMER_ONCALENDAR}"
|
|
KEEP_LAST=${KEEP_LAST}
|
|
KEEP_DAILY=${KEEP_DAILY}
|
|
KEEP_WEEKLY=${KEEP_WEEKLY}
|
|
KEEP_MONTHLY=${KEEP_MONTHLY}
|
|
EOF
|
|
|
|
chmod 600 "$(get_target_config_path "$target_name")"
|
|
|
|
# Create backup script for this target
|
|
cat > "$CONFIG_DIR/backup-${target_name}.sh" <<EOFSCRIPT
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
# Check if forcing full backup (for manual runs)
|
|
FORCE_FULL="\${1:-no}"
|
|
|
|
# Load configuration for target: $target_name
|
|
source $(get_target_config_path "$target_name")
|
|
|
|
# Export for PBS client
|
|
export PBS_REPOSITORY
|
|
export PBS_PASSWORD
|
|
|
|
HOSTNAME=\$(hostname)
|
|
BACKUP_SUCCESS=true
|
|
|
|
log() {
|
|
echo "[\$(date +'%Y-%m-%d %H:%M:%S')] \$1"
|
|
}
|
|
|
|
# Function to create file-level backup
|
|
backup_files() {
|
|
log "Starting file-level backup (.pxar) for target: $target_name"
|
|
|
|
BACKUP_CMD="proxmox-backup-client backup"
|
|
|
|
# Add each path as separate archive
|
|
for path in \$BACKUP_PATHS; do
|
|
# Sanitize path for archive name
|
|
archive_name=\$(echo "\$path" | sed 's/^\///' | sed 's/\//-/g')
|
|
[ -z "\$archive_name" ] && archive_name="root"
|
|
|
|
BACKUP_CMD="\$BACKUP_CMD \${archive_name}.pxar:\${path}"
|
|
|
|
# Add --include-dev for mount points
|
|
if mountpoint -q "\$path" 2>/dev/null; then
|
|
BACKUP_CMD="\$BACKUP_CMD --include-dev \${path}"
|
|
fi
|
|
done
|
|
|
|
# Add exclusions
|
|
for pattern in \$EXCLUDE_PATTERNS; do
|
|
BACKUP_CMD="\$BACKUP_CMD --exclude=\${pattern}"
|
|
done
|
|
|
|
# Add other options
|
|
BACKUP_CMD="\$BACKUP_CMD --skip-lost-and-found --change-detection-mode=metadata"
|
|
|
|
# Execute backup
|
|
if eval \$BACKUP_CMD; then
|
|
log "File-level backup completed successfully"
|
|
return 0
|
|
else
|
|
log "File-level backup FAILED" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to create block device backup
|
|
backup_block_device() {
|
|
log "Starting block device backup (.img) for target: $target_name"
|
|
|
|
if [ -z "\$BLOCK_DEVICE" ]; then
|
|
log "ERROR: BLOCK_DEVICE not configured" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -b "\$BLOCK_DEVICE" ]; then
|
|
log "ERROR: \$BLOCK_DEVICE is not a block device" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Get device size
|
|
DEVICE_SIZE=\$(blockdev --getsize64 "\$BLOCK_DEVICE" 2>/dev/null || echo "0")
|
|
DEVICE_SIZE_GB=\$((DEVICE_SIZE / 1024 / 1024 / 1024))
|
|
|
|
log "Backing up device: \$BLOCK_DEVICE (\${DEVICE_SIZE_GB}GB)"
|
|
log "This may take a while..."
|
|
|
|
# Sanitize device name for archive
|
|
DEVICE_NAME=\$(basename "\$BLOCK_DEVICE")
|
|
|
|
# Create block device backup
|
|
if proxmox-backup-client backup "\${DEVICE_NAME}.img:\${BLOCK_DEVICE}"; then
|
|
log "Block device backup completed successfully"
|
|
return 0
|
|
else
|
|
log "Block device backup FAILED" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Main backup execution
|
|
log "Starting backup for \${HOSTNAME} - Target: $target_name"
|
|
log "Backup type: \${BACKUP_TYPE}"
|
|
|
|
case "\$BACKUP_TYPE" in
|
|
files)
|
|
backup_files || BACKUP_SUCCESS=false
|
|
;;
|
|
block)
|
|
backup_block_device || BACKUP_SUCCESS=false
|
|
;;
|
|
both)
|
|
# Determine what to backup
|
|
SHOULD_RUN_FILES=true
|
|
SHOULD_RUN_BLOCK=false
|
|
|
|
if [ "\$FORCE_FULL" = "yes" ]; then
|
|
log "Manual full backup - running both files and block device"
|
|
SHOULD_RUN_FILES=true
|
|
SHOULD_RUN_BLOCK=true
|
|
elif [ "\$FORCE_FULL" = "files" ]; then
|
|
log "Manual files-only backup"
|
|
SHOULD_RUN_FILES=true
|
|
SHOULD_RUN_BLOCK=false
|
|
elif [ "\$FORCE_FULL" = "block" ]; then
|
|
log "Manual block-only backup"
|
|
SHOULD_RUN_FILES=false
|
|
SHOULD_RUN_BLOCK=true
|
|
else
|
|
# Scheduled backup - check if it's time based on frequency
|
|
CURRENT_DOW=\$(date +%u) # 1=Mon, 7=Sun
|
|
CURRENT_DOM=\$(date +%d | sed 's/^0//') # Remove leading zero
|
|
|
|
case "\$BLOCK_DEVICE_FREQUENCY" in
|
|
weekly)
|
|
if [ "\$CURRENT_DOW" -eq "\$BLOCK_DEVICE_DAY" ]; then
|
|
log "Weekly block device backup day (day \$BLOCK_DEVICE_DAY)"
|
|
SHOULD_RUN_BLOCK=true
|
|
fi
|
|
;;
|
|
biweekly)
|
|
WEEK_OF_YEAR=\$(date +%V)
|
|
if [ "\$CURRENT_DOW" -eq "\$BLOCK_DEVICE_DAY" ] && [ \$((WEEK_OF_YEAR % 2)) -eq 0 ]; then
|
|
log "Biweekly block device backup day (even week, day \$BLOCK_DEVICE_DAY)"
|
|
SHOULD_RUN_BLOCK=true
|
|
fi
|
|
;;
|
|
monthly)
|
|
if [ "\$CURRENT_DOM" -eq "\$BLOCK_DEVICE_DAY" ]; then
|
|
log "Monthly block device backup day (\$BLOCK_DEVICE_DAY of month)"
|
|
SHOULD_RUN_BLOCK=true
|
|
fi
|
|
;;
|
|
*)
|
|
log "Unknown BLOCK_DEVICE_FREQUENCY: \$BLOCK_DEVICE_FREQUENCY, defaulting to weekly Sunday"
|
|
if [ "\$CURRENT_DOW" -eq 7 ]; then
|
|
SHOULD_RUN_BLOCK=true
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Execute backups based on decisions
|
|
if [ "\$SHOULD_RUN_FILES" = true ]; then
|
|
backup_files || BACKUP_SUCCESS=false
|
|
else
|
|
log "Skipping file backup (block-only mode)"
|
|
fi
|
|
|
|
if [ "\$SHOULD_RUN_BLOCK" = true ]; then
|
|
backup_block_device || BACKUP_SUCCESS=false
|
|
else
|
|
log "Skipping block device backup (next: \${BLOCK_DEVICE_FREQUENCY} on day \${BLOCK_DEVICE_DAY})"
|
|
fi
|
|
;;
|
|
*)
|
|
log "ERROR: Invalid BACKUP_TYPE: \${BACKUP_TYPE}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Prune old backups
|
|
if [ "\$BACKUP_SUCCESS" = true ]; then
|
|
log "Pruning old backups..."
|
|
proxmox-backup-client prune "host/\${HOSTNAME}" \\
|
|
--keep-last \$KEEP_LAST \\
|
|
--keep-daily \$KEEP_DAILY \\
|
|
--keep-weekly \$KEEP_WEEKLY \\
|
|
--keep-monthly \$KEEP_MONTHLY
|
|
|
|
log "Backup and prune completed successfully"
|
|
else
|
|
log "Backup FAILED" >&2
|
|
exit 1
|
|
fi
|
|
EOFSCRIPT
|
|
|
|
chmod 700 "$CONFIG_DIR/backup-${target_name}.sh"
|
|
|
|
# Create systemd service file (for scheduled backups)
|
|
cat > "/etc/systemd/system/pbs-backup-${target_name}.service" <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Backup - Target: $target_name
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$CONFIG_DIR/backup-${target_name}.sh
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=pbs-backup
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create systemd service file for manual full backups
|
|
cat > "/etc/systemd/system/pbs-backup-${target_name}-manual.service" <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Manual Full Backup - Target: $target_name
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$CONFIG_DIR/backup-${target_name}.sh yes
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=pbs-backup
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create systemd timer file
|
|
cat > "/etc/systemd/system/pbs-backup-${target_name}.timer" <<EOF
|
|
[Unit]
|
|
Description=Proxmox Backup Client Backup Timer - Target: $target_name
|
|
Requires=pbs-backup-${target_name}.service
|
|
|
|
[Timer]
|
|
OnCalendar=${TIMER_ONCALENDAR}
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
|
|
# Reload systemd and enable timer
|
|
systemctl daemon-reload
|
|
systemctl enable "pbs-backup-${target_name}.timer"
|
|
systemctl start "pbs-backup-${target_name}.timer"
|
|
|
|
log "Systemd service and timer created successfully for target: $target_name"
|
|
info "Timer status:"
|
|
systemctl status "pbs-backup-${target_name}.timer" --no-pager -l || true
|
|
}
|
|
|
|
# Run backup immediately
|
|
run_backup_now() {
|
|
echo
|
|
RUN_NOW=$(prompt "Do you want to run a backup now? (yes/no)" "no")
|
|
|
|
if [[ "$RUN_NOW" == "yes" ]]; then
|
|
log "Starting immediate FULL backup (files + block device)..."
|
|
echo
|
|
|
|
# Show real-time progress
|
|
echo "╔════════════════════════════════════════════════════════════╗"
|
|
echo "║ Backup Progress (Live) ║"
|
|
echo "║ Press Ctrl+C to exit (backup continues in background) ║"
|
|
echo "╚════════════════════════════════════════════════════════════╝"
|
|
echo
|
|
|
|
# Start the manual backup (forces full backup)
|
|
systemctl start pbs-backup-manual.service &
|
|
|
|
# Wait a brief moment for service to register
|
|
sleep 0.5
|
|
|
|
# Monitor backup in background and kill journalctl when done
|
|
(
|
|
while systemctl is-active --quiet pbs-backup-manual.service; do
|
|
sleep 2
|
|
done
|
|
# Service finished, kill the journal follow
|
|
pkill -P $$ journalctl 2>/dev/null
|
|
) &
|
|
MONITOR_PID=$!
|
|
|
|
# Follow logs in foreground from the start (will be killed by monitor when backup completes)
|
|
journalctl -u pbs-backup-manual.service -f --since "2 seconds ago" || true
|
|
|
|
# Wait for monitor to finish
|
|
wait $MONITOR_PID 2>/dev/null
|
|
|
|
# Clear any leftover output
|
|
echo
|
|
echo "════════════════════════════════════════════════════════════"
|
|
|
|
# Check final status
|
|
if systemctl status pbs-backup-manual.service | grep -q "Active: failed"; then
|
|
echo
|
|
error "Backup failed!"
|
|
else
|
|
echo
|
|
log "Backup completed successfully!"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Show summary and next steps
|
|
show_summary() {
|
|
echo
|
|
echo "======================================"
|
|
echo " Installation Complete!"
|
|
echo "======================================"
|
|
echo
|
|
info "Configuration Summary:"
|
|
echo " PBS Server: ${PBS_SERVER}:${PBS_PORT}"
|
|
echo " Datastore: ${PBS_DATASTORE}"
|
|
echo " Repository: ${PBS_REPOSITORY}"
|
|
echo " Backup Type: ${BACKUP_TYPE}"
|
|
|
|
if [[ "$BACKUP_TYPE" == "files" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Backup Paths: ${BACKUP_PATHS}"
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "block" ]] || [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Block Device: ${BLOCK_DEVICE}"
|
|
fi
|
|
|
|
if [[ "$BACKUP_TYPE" == "both" ]]; then
|
|
echo " Schedule: Files ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR}), Block device weekly (Sunday)"
|
|
else
|
|
echo " Schedule: ${TIMER_SCHEDULE} (${TIMER_ONCALENDAR})"
|
|
fi
|
|
echo
|
|
info "Useful Commands:"
|
|
echo " Check timer status: sudo systemctl status pbs-backup.timer"
|
|
echo " Check service logs: sudo journalctl -u pbs-backup.service"
|
|
echo " Run backup now: sudo systemctl start pbs-backup.service"
|
|
echo " List backups: sudo -E proxmox-backup-client snapshot list"
|
|
echo " Disable backups: sudo systemctl disable pbs-backup.timer"
|
|
echo
|
|
info "Configuration Files:"
|
|
echo " Config: $CONFIG_DIR/config"
|
|
echo " Backup script: $CONFIG_DIR/backup.sh"
|
|
echo " Service: /etc/systemd/system/pbs-backup.service"
|
|
echo " Timer: /etc/systemd/system/pbs-backup.timer"
|
|
|
|
if [[ "$ENABLE_ENCRYPTION" == "yes" ]]; then
|
|
echo
|
|
warn "IMPORTANT: Backup your encryption key!"
|
|
echo " Key location: /root/.config/proxmox-backup/encryption-key.json"
|
|
echo " Paper backup: /root/pbs-encryption-key-*.txt"
|
|
fi
|
|
|
|
echo
|
|
log "Setup completed successfully!"
|
|
}
|
|
|
|
# Main script execution
|
|
main() {
|
|
echo
|
|
echo "╔════════════════════════════════════════╗"
|
|
echo "║ Proxmox Backup Client Installer ║"
|
|
echo "║ Version: ${SCRIPT_VERSION} ║"
|
|
echo "╚════════════════════════════════════════╝"
|
|
echo
|
|
|
|
# Create log file
|
|
touch "$LOG_FILE"
|
|
|
|
# Check root
|
|
check_root
|
|
|
|
# Detect distribution
|
|
detect_distro
|
|
|
|
# Migrate legacy configuration if needed
|
|
migrate_legacy_config
|
|
|
|
# Check if PBS client is already installed
|
|
if command -v proxmox-backup-client &> /dev/null; then
|
|
warn "Proxmox Backup Client is already installed"
|
|
|
|
# Check if any targets exist
|
|
if list_targets >/dev/null 2>&1; then
|
|
show_targets_list
|
|
echo
|
|
echo "════════════════════════════════════════"
|
|
echo " Multi-Target Backup Management"
|
|
echo "════════════════════════════════════════"
|
|
echo
|
|
echo "What would you like to do?"
|
|
echo " 1) List all backup targets"
|
|
echo " 2) Add new backup target"
|
|
echo " 3) Edit existing target"
|
|
echo " 4) Delete target"
|
|
echo " 5) Run backup now (select target)"
|
|
echo " 6) View target details"
|
|
echo " 7) Reinstall PBS client"
|
|
echo " 8) Exit"
|
|
ACTION=$(prompt "Select option [1-8]" "1")
|
|
|
|
case "$ACTION" in
|
|
1)
|
|
show_targets_list
|
|
exit 0
|
|
;;
|
|
2)
|
|
add_target
|
|
exit 0
|
|
;;
|
|
3)
|
|
edit_target
|
|
exit 0
|
|
;;
|
|
4)
|
|
delete_target
|
|
exit 0
|
|
;;
|
|
5)
|
|
echo
|
|
echo "Available targets:"
|
|
list_targets | nl
|
|
echo
|
|
TARGET_NAME=$(prompt "Enter target name to backup" "")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
exit 1
|
|
fi
|
|
|
|
if ! target_exists "$TARGET_NAME"; then
|
|
error "Target '$TARGET_NAME' does not exist"
|
|
exit 1
|
|
fi
|
|
|
|
run_backup_for_target "$TARGET_NAME"
|
|
exit 0
|
|
;;
|
|
6)
|
|
echo
|
|
echo "Available targets:"
|
|
list_targets | nl
|
|
echo
|
|
TARGET_NAME=$(prompt "Enter target name to view" "")
|
|
|
|
if validate_target_name "$TARGET_NAME"; then
|
|
show_target_detail "$TARGET_NAME"
|
|
fi
|
|
exit 0
|
|
;;
|
|
7)
|
|
info "Reinstalling PBS client..."
|
|
install_pbs_client
|
|
log "PBS client reinstalled successfully"
|
|
exit 0
|
|
;;
|
|
8)
|
|
info "Exiting"
|
|
exit 0
|
|
;;
|
|
*)
|
|
error "Invalid option"
|
|
exit 1
|
|
;;
|
|
esac
|
|
else
|
|
# PBS client installed but no targets configured
|
|
info "No backup targets configured"
|
|
echo
|
|
info "Let's create your first backup target!"
|
|
echo
|
|
|
|
TARGET_NAME=$(prompt "Enter name for first target (e.g., 'primary', 'local', 'offsite')" "default")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
error "Invalid target name"
|
|
exit 1
|
|
fi
|
|
|
|
if target_exists "$TARGET_NAME"; then
|
|
error "Target '$TARGET_NAME' already exists"
|
|
exit 1
|
|
fi
|
|
|
|
interactive_config_for_target "$TARGET_NAME"
|
|
|
|
echo
|
|
log "First target '$TARGET_NAME' created successfully!"
|
|
echo
|
|
info "You can add more targets by running this script again."
|
|
exit 0
|
|
fi
|
|
else
|
|
# PBS client not installed
|
|
info "Installing Proxmox Backup Client..."
|
|
install_pbs_client
|
|
|
|
echo
|
|
info "Let's create your first backup target!"
|
|
echo
|
|
|
|
TARGET_NAME=$(prompt "Enter name for first target (e.g., 'primary', 'local', 'offsite')" "default")
|
|
|
|
if ! validate_target_name "$TARGET_NAME"; then
|
|
error "Invalid target name"
|
|
exit 1
|
|
fi
|
|
|
|
interactive_config_for_target "$TARGET_NAME"
|
|
|
|
echo
|
|
log "First target '$TARGET_NAME' created successfully!"
|
|
echo
|
|
info "You can add more targets by running this script again."
|
|
exit 0
|
|
fi
|
|
|
|
# All paths should exit above, this should never be reached
|
|
error "Unexpected code path - please report this bug"
|
|
exit 1
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|