WIP: Multi-target backup support (Part 1 of 2)
This commit introduces the foundation for multiple backup targets, allowing users to backup to multiple PBS servers for redundancy. ADDED: - Multi-target configuration structure - TARGETS_DIR: /etc/proxmox-backup-client/targets/ - Each target stored as TARGET.conf - Independent systemd services per target (pbs-backup-TARGET.service) - Target management functions: - list_targets() - List all configured target names - validate_target_name() - Validate target naming rules - target_exists() - Check if target is configured - get_target_config_path() - Get path to target config file - show_targets_list() - Display all targets with server/status - show_target_detail() - Show detailed target configuration - Target CRUD operations: - add_target() - Interactive target creation - edit_target() - Edit connection, settings, or full reconfig - delete_target() - Remove target with confirmation - Migration system: - migrate_legacy_config() - Auto-migrate single-target to "default" target - Renames old services: pbs-backup.service -> pbs-backup-default.service - Preserves existing backups and schedules CHANGED: - Script version: 1.0.0 -> 1.1.0 - Updated CHANGELOG with multi-target progress and recent fixes WORK IN PROGRESS (Part 2 needed): - Update create_systemd_service() to accept target name parameter - Create wrapper functions for per-target configuration - Update main menu with multi-target options - Add schedule coordination (all-at-once, alternating, individual) - Add "Run backup now" with target selection TESTING STATUS: Not yet tested, foundation only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e333839c8b
commit
d9a4e5a308
2 changed files with 338 additions and 1 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
|
@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Multi-target backup support (IN PROGRESS - v1.1.0)**
|
||||
- Support for multiple backup destinations (different PBS servers for redundancy)
|
||||
- Named backup targets (e.g., "offsite", "local", "backup1")
|
||||
- Target management functions:
|
||||
- List all configured targets with status
|
||||
- Add new backup targets
|
||||
- Edit existing targets (connection, settings, or full reconfig)
|
||||
- Delete targets with confirmation
|
||||
- View detailed target information
|
||||
- Automatic migration from legacy single-target configuration to "default" target
|
||||
- Independent systemd services per target (pbs-backup-TARGET.service)
|
||||
- Configuration stored in /etc/proxmox-backup-client/targets/TARGET.conf
|
||||
- Schedule coordination options (planned):
|
||||
- All targets run at same time
|
||||
- Alternating schedule across targets
|
||||
- Individual schedules per target
|
||||
- Intelligent reconfiguration options when PBS client is already installed
|
||||
- Quick connection-only reconfiguration (server/credentials only)
|
||||
- Full reconfiguration of all settings
|
||||
|
|
@ -52,6 +68,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **Default realm changed from "pbs" to "pam"** (more common for root authentication)
|
||||
- **Default encryption setting changed from "yes" to "no"** (user can opt-in if needed)
|
||||
- Main menu now includes "Run backup now" option for immediate backup testing
|
||||
- Main menu now includes "Modify backup schedule/type" option (option 5)
|
||||
- Repository renamed from PBSClientInstaller to PBSClientTool
|
||||
- **Manual backups now force full backup (files + block device) regardless of day**
|
||||
- Created separate pbs-backup-manual.service for manual runs
|
||||
- Scheduled backups still follow daily/weekly pattern
|
||||
- Manual backups always include block device even on non-Sunday
|
||||
- Script version bumped to 1.1.0 for multi-target support
|
||||
|
||||
### Fixed
|
||||
- **CRITICAL**: SSL fingerprint prompt no longer causes authentication timeout
|
||||
|
|
@ -71,6 +94,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Shows actual PBS client error messages when authentication fails
|
||||
- Authentication test now uses correct `login` command instead of non-existent `status` command
|
||||
- Connection test no longer times out due to using wrong PBS client commands
|
||||
- **Backup progress logs now display properly during manual backup runs**
|
||||
- Fixed journalctl following wrong service (was following pbs-backup.service instead of pbs-backup-manual.service)
|
||||
- Removed stderr suppression (2>/dev/null) that was hiding output
|
||||
- Added --since flag to capture logs from service start, not just new entries
|
||||
- Now shows verbose progress: "processed 65.6 GiB in 18m, uploaded 64.4 GiB"
|
||||
- Backup logs now close automatically when backup completes (no more blank screen)
|
||||
|
||||
## [1.0.0] - 2025-11-01
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ BLUE='\033[0;34m'
|
|||
NC='\033[0m' # No Color
|
||||
|
||||
# Script configuration
|
||||
SCRIPT_VERSION="1.0.0"
|
||||
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
|
||||
|
|
@ -55,6 +56,313 @@ prompt_password() {
|
|||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue