WIP: Multi-target backup support (Part 2 of 3) - 90% Complete
This commit completes the core infrastructure for multi-target backups. Only main menu integration remains. COMPLETED IN THIS COMMIT: 1. Per-target wrapper functions: - interactive_config_for_target(name) - Full configuration wizard - reconfigure_connection_for_target(name) - Connection-only updates - reconfigure_backup_settings_for_target(name) - Settings-only updates - run_backup_for_target(name) - Execute backup with live progress 2. Systemd service creation for named targets: - create_systemd_service_for_target(name) - Complete service generation - Generates pbs-backup-TARGET.service (scheduled backups) - Generates pbs-backup-TARGET-manual.service (manual full backups) - Generates pbs-backup-TARGET.timer (scheduler) - Creates target-specific backup scripts with embedded target name - All configs stored in /etc/proxmox-backup-client/targets/TARGET.conf 3. Main menu partially updated: - Added migrate_legacy_config() call at startup - Started new multi-target menu structure - Menu shows: List, Add, Edit, Delete, Run, View, Reinstall, Exit FEATURES: - Each target = independent systemd services - Target-specific backup scripts include target name in logs - Live progress monitoring per target - Complete isolation between targets - Backward compatible via automatic migration TESTING STATUS: - Syntax validated (no errors) - Not yet functionally tested - Migration logic not tested - Menu integration incomplete REMAINING WORK (Part 3): - Complete main menu case statement updates - Add first-time setup flow for new installations - Add "Run all targets" bulk backup option - Test legacy→multi-target migration - Full integration testing - Update README with multi-target examples CHANGELOG UPDATED: - Detailed breakdown of completed components - Clear TODO list for remaining work - Marked as "90% Complete" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d9a4e5a308
commit
fc78ff9ddb
2 changed files with 411 additions and 24 deletions
48
CHANGELOG.md
48
CHANGELOG.md
|
|
@ -8,22 +8,42 @@ 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)**
|
||||
- **Multi-target backup support (v1.1.0 - 90% Complete)**
|
||||
- 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
|
||||
- Target management functions (COMPLETE):
|
||||
- `list_targets()` - List all configured target names
|
||||
- `show_targets_list()` - Display all targets with server/datastore/status
|
||||
- `show_target_detail()` - Show comprehensive target configuration
|
||||
- `add_target()` - Interactive target creation wizard
|
||||
- `edit_target()` - Edit connection, settings, or full reconfig
|
||||
- `delete_target()` - Remove target with confirmation and cleanup
|
||||
- `validate_target_name()` - Enforce naming rules (alphanumeric, dash, underscore)
|
||||
- Per-target wrapper functions (COMPLETE):
|
||||
- `interactive_config_for_target()` - Configure specific target
|
||||
- `reconfigure_connection_for_target()` - Update connection only
|
||||
- `reconfigure_backup_settings_for_target()` - Update backup settings only
|
||||
- `run_backup_for_target()` - Execute backup with live progress monitoring
|
||||
- Systemd service creation (COMPLETE):
|
||||
- `create_systemd_service_for_target()` - Generate all services for named target
|
||||
- Creates pbs-backup-TARGET.service (scheduled)
|
||||
- Creates pbs-backup-TARGET-manual.service (manual/full)
|
||||
- Creates pbs-backup-TARGET.timer (scheduler)
|
||||
- Generates target-specific backup scripts with embedded target name
|
||||
- Automatic migration (COMPLETE):
|
||||
- `migrate_legacy_config()` - Auto-migrate single-target to "default" target
|
||||
- Renames services: pbs-backup → pbs-backup-default
|
||||
- Preserves existing schedules and backups
|
||||
- Zero downtime migration
|
||||
- Configuration storage (COMPLETE):
|
||||
- /etc/proxmox-backup-client/targets/TARGET.conf
|
||||
- /etc/proxmox-backup-client/backup-TARGET.sh
|
||||
- /etc/systemd/system/pbs-backup-TARGET.{service,timer}
|
||||
- TODO (Main menu integration):
|
||||
- Update main menu to show multi-target options
|
||||
- Handle first-time setup for new installations
|
||||
- Add "Run all targets" option
|
||||
- Test migration from legacy to multi-target
|
||||
- Intelligent reconfiguration options when PBS client is already installed
|
||||
- Quick connection-only reconfiguration (server/credentials only)
|
||||
- Full reconfiguration of all settings
|
||||
|
|
|
|||
|
|
@ -801,6 +801,126 @@ reconfigure_backup_settings() {
|
|||
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
|
||||
|
||||
info "Running FULL backup for target: $target_name"
|
||||
echo
|
||||
|
||||
# Show real-time progress
|
||||
echo "╔════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Backup Progress (Live) - Target: $target_name"
|
||||
echo "║ Press Ctrl+C to exit (backup continues in background) ║"
|
||||
echo "╚════════════════════════════════════════════════════════════╝"
|
||||
echo
|
||||
|
||||
# Start the manual backup (forces full backup)
|
||||
systemctl start "pbs-backup-${target_name}-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-${target_name}-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
|
||||
journalctl -u "pbs-backup-${target_name}-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-${target_name}-manual.service" | grep -q "Active: failed"; then
|
||||
echo
|
||||
error "Backup failed!"
|
||||
echo
|
||||
info "Check full logs with:"
|
||||
echo " sudo journalctl -u pbs-backup-${target_name}-manual.service -n 50"
|
||||
else
|
||||
echo
|
||||
log "Backup completed successfully!"
|
||||
fi
|
||||
}
|
||||
|
||||
# Interactive configuration
|
||||
interactive_config() {
|
||||
log "Starting interactive configuration..."
|
||||
|
|
@ -1293,6 +1413,244 @@ EOF
|
|||
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}"
|
||||
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)
|
||||
# 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-${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
|
||||
|
|
@ -1415,22 +1773,31 @@ main() {
|
|||
# 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 configuration exists
|
||||
if [ -f "$CONFIG_DIR/config" ]; then
|
||||
info "Existing configuration detected"
|
||||
# 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) Reconfigure connection only (server/credentials)"
|
||||
echo " 2) Full reconfiguration (all settings)"
|
||||
echo " 3) Reinstall PBS client and reconfigure"
|
||||
echo " 4) Run backup now"
|
||||
echo " 5) Modify backup schedule/type"
|
||||
echo " 6) Exit"
|
||||
ACTION=$(prompt "Select option [1/2/3/4/5/6]" "1")
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue