- Convert backup file path to absolute path before git add/commit - Fixes 'pathspec did not match any files' error when using ../ - Use absolute_backup_path consistently in commit_to_git() - Tested successfully: backups now commit to git without errors
391 lines
12 KiB
Bash
Executable File
391 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
################################################################################
|
|
# pfSense Configuration Backup Utility
|
|
#
|
|
# This script automatically backs up your pfSense router configuration and
|
|
# stores it in the repository with a timestamped filename.
|
|
#
|
|
# Prerequisites:
|
|
# - SSH access to pfSense router (with public key authentication)
|
|
# - SSH host entry configured (default: 'pfsense' in ~/.ssh/config)
|
|
# - Git repository initialized in the current working directory
|
|
# - Sufficient disk space for configuration backup (typically < 1MB)
|
|
#
|
|
# Usage:
|
|
# ./backup-pfsense-config.sh # Uses default host 'pfsense'
|
|
# ./backup-pfsense-config.sh pfsense.home.arpa # Specify custom host
|
|
# ./backup-pfsense-config.sh -h # Show help
|
|
#
|
|
################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration (set defaults)
|
|
PFSENSE_HOST="pfsense" # SSH host (from ~/.ssh/config or IP)
|
|
PFSENSE_USER="${PFSENSE_USER:-root}" # SSH user
|
|
PFSENSE_CONFIG_PATH="/conf/config.xml"
|
|
BACKUP_DIR="$(dirname "$0")/../backups"
|
|
TIMESTAMP="$(date +%Y-%m-%d_%H%M%S)"
|
|
BACKUP_FILE="${BACKUP_DIR}/pfsense-config-${TIMESTAMP}.xml"
|
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
|
|
AUTO_COMMIT="${AUTO_COMMIT:-true}"
|
|
|
|
# Color codes for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
################################################################################
|
|
# Helper Functions
|
|
################################################################################
|
|
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[✓]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
}
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") [OPTIONS] [HOST]
|
|
|
|
Backup pfSense router configuration to repository.
|
|
|
|
ARGUMENTS:
|
|
HOST SSH host or IP address (default: 'pfsense')
|
|
Should be an entry in ~/.ssh/config or IP address
|
|
Example: pfsense, 192.168.1.1, pfsense.home.arpa
|
|
|
|
OPTIONS:
|
|
-h, --help Show this help message
|
|
-n, --no-commit Download config without auto-committing to git
|
|
--dry-run Show what would be done without making changes
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
PFSENSE_USER SSH user (default: 'root')
|
|
AUTO_COMMIT Whether to auto-commit (default: 'true')
|
|
|
|
EXAMPLES:
|
|
# Backup using default host and auto-commit
|
|
$ ./backup-pfsense-config.sh
|
|
|
|
# Backup specific host
|
|
$ ./backup-pfsense-config.sh 172.27.0.1
|
|
|
|
# Backup without auto-commit
|
|
$ ./backup-pfsense-config.sh -n
|
|
|
|
# Dry run (test connectivity)
|
|
$ ./backup-pfsense-config.sh --dry-run
|
|
|
|
CONFIGURATION:
|
|
SSH host entry: ${PFSENSE_HOST}
|
|
SSH user: ${PFSENSE_USER}
|
|
Backup directory: ${BACKUP_DIR}
|
|
Config file: ${PFSENSE_CONFIG_PATH}
|
|
|
|
NOTES:
|
|
- Requires SSH public key authentication to pfSense
|
|
- First run may prompt to accept SSH key fingerprint
|
|
- Backup files are stored with timestamps (YYYY-MM-DD_HHMMSS)
|
|
- Backups are committed to git with descriptive messages
|
|
- Each backup is human-readable XML for easy diffs
|
|
|
|
EOF
|
|
}
|
|
|
|
check_prerequisites() {
|
|
log_info "Checking prerequisites..."
|
|
|
|
# Check SSH connectivity
|
|
if ! command -v ssh &> /dev/null; then
|
|
log_error "SSH is not installed or not in PATH"
|
|
return 1
|
|
fi
|
|
log_success "SSH is available"
|
|
|
|
# Check git
|
|
if ! command -v git &> /dev/null; then
|
|
log_error "Git is not installed or not in PATH"
|
|
return 1
|
|
fi
|
|
log_success "Git is available"
|
|
|
|
# Check backup directory exists
|
|
if [[ ! -d "${BACKUP_DIR}" ]]; then
|
|
log_warning "Backup directory does not exist: ${BACKUP_DIR}"
|
|
log_info "Creating backup directory..."
|
|
mkdir -p "${BACKUP_DIR}" || {
|
|
log_error "Failed to create backup directory"
|
|
return 1
|
|
}
|
|
log_success "Backup directory created"
|
|
fi
|
|
|
|
# Verify we're in a git repository
|
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
log_error "Not in a git repository (checked from $(pwd))"
|
|
return 1
|
|
fi
|
|
log_success "Git repository found"
|
|
|
|
return 0
|
|
}
|
|
|
|
test_ssh_connection() {
|
|
log_info "Testing SSH connection to ${PFSENSE_HOST}..."
|
|
|
|
if ssh -o ConnectTimeout=10 "${PFSENSE_USER}@${PFSENSE_HOST}" "echo 'SSH connection successful'" > /dev/null 2>&1; then
|
|
log_success "SSH connection to ${PFSENSE_HOST} successful"
|
|
return 0
|
|
else
|
|
log_error "Failed to connect to ${PFSENSE_HOST} via SSH"
|
|
log_info "Troubleshooting tips:"
|
|
log_info " 1. Verify SSH host is configured: ssh-keygen -F ${PFSENSE_HOST}"
|
|
log_info " 2. Test SSH manually: ssh ${PFSENSE_USER}@${PFSENSE_HOST}"
|
|
log_info " 3. Check firewall allows SSH (port 22) from your machine"
|
|
log_info " 4. Verify public key is installed on pfSense: /root/.ssh/authorized_keys"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
fetch_config() {
|
|
log_info "Fetching pfSense configuration from ${PFSENSE_HOST}..."
|
|
|
|
# Use scp to copy the remote file
|
|
if ! scp "${PFSENSE_USER}@${PFSENSE_HOST}:${PFSENSE_CONFIG_PATH}" "${BACKUP_FILE}" 2>/dev/null; then
|
|
log_error "Failed to download configuration from ${PFSENSE_HOST}"
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -f "${BACKUP_FILE}" ]]; then
|
|
log_error "Backup file was not created: ${BACKUP_FILE}"
|
|
return 1
|
|
fi
|
|
|
|
log_success "Configuration downloaded to ${BACKUP_FILE}"
|
|
|
|
# Show file size
|
|
local file_size
|
|
file_size=$(du -h "${BACKUP_FILE}" | cut -f1)
|
|
log_info "Backup size: ${file_size}"
|
|
|
|
return 0
|
|
}
|
|
|
|
validate_config() {
|
|
log_info "Validating backup file..."
|
|
|
|
# Check if file is valid XML
|
|
if ! grep -q '<?xml' "${BACKUP_FILE}"; then
|
|
log_error "Backup file does not appear to be valid XML"
|
|
log_warning "Removing invalid backup: ${BACKUP_FILE}"
|
|
rm -f "${BACKUP_FILE}"
|
|
return 1
|
|
fi
|
|
|
|
# Check for pfSense-specific markers
|
|
if ! grep -q '<pfsense' "${BACKUP_FILE}"; then
|
|
log_warning "Backup file may not be a valid pfSense configuration"
|
|
log_info "File contents (first 20 lines):"
|
|
head -20 "${BACKUP_FILE}"
|
|
return 1
|
|
fi
|
|
|
|
log_success "Backup file is valid XML with pfSense markers"
|
|
return 0
|
|
}
|
|
|
|
commit_to_git() {
|
|
log_info "Committing backup to git repository..."
|
|
|
|
# Convert to absolute path to avoid issues with relative paths containing ../
|
|
local absolute_backup_path
|
|
absolute_backup_path="$(cd "$(dirname "${BACKUP_FILE}")" && pwd)/$(basename "${BACKUP_FILE}")"
|
|
|
|
if [[ ! -f "${absolute_backup_path}" ]]; then
|
|
log_error "Backup file not found at: ${absolute_backup_path}"
|
|
return 1
|
|
fi
|
|
|
|
# Add the backup file to git using absolute path
|
|
if git -C "${REPO_ROOT}" add "${absolute_backup_path}"; then
|
|
log_success "Added backup to git staging area"
|
|
else
|
|
log_error "Failed to add backup to git"
|
|
return 1
|
|
fi
|
|
|
|
# Create a meaningful commit message
|
|
local short_date
|
|
short_date=$(date +%Y-%m-%d\ %H:%M:%S)
|
|
local commit_msg="pfSense: Backup configuration (${short_date})"
|
|
|
|
# Get configuration summary from XML
|
|
local vlan_count
|
|
vlan_count=$(grep -c '<vlan>' "${absolute_backup_path}" 2>/dev/null || echo "0")
|
|
local rule_count
|
|
rule_count=$(grep -c '<rule>' "${absolute_backup_path}" 2>/dev/null || echo "0")
|
|
|
|
# Enhanced commit message with details
|
|
local detailed_msg="${commit_msg}
|
|
|
|
Contains:
|
|
- VLANs: ${vlan_count}
|
|
- Firewall rules: ${rule_count}
|
|
- Backup timestamp: ${TIMESTAMP}
|
|
|
|
File: $(basename "${BACKUP_FILE}")
|
|
SHA256: $(shasum -a 256 "${absolute_backup_path}" | awk '{print $1}')
|
|
"
|
|
|
|
# Commit the backup
|
|
if git -C "${REPO_ROOT}" commit -m "${detailed_msg}" > /dev/null 2>&1; then
|
|
log_success "Configuration committed to git"
|
|
log_info "Commit message:"
|
|
echo "${detailed_msg}" | sed 's/^/ /'
|
|
return 0
|
|
else
|
|
log_warning "No changes to commit (file may be identical to previous backup)"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
show_summary() {
|
|
echo ""
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
log_success "pfSense configuration backup completed successfully!"
|
|
echo "════════════════════════════════════════════════════════════════"
|
|
echo ""
|
|
echo "Backup Details:"
|
|
echo " Location: ${BACKUP_FILE}"
|
|
echo " Size: $(du -h "${BACKUP_FILE}" | cut -f1)"
|
|
echo " Timestamp: ${TIMESTAMP}"
|
|
echo " From: ${PFSENSE_USER}@${PFSENSE_HOST}:${PFSENSE_CONFIG_PATH}"
|
|
echo ""
|
|
echo "Next Steps:"
|
|
echo " 1. Review the changes: git show HEAD"
|
|
echo " 2. Push to remote: git push origin main"
|
|
echo " 3. Schedule automated backups in crontab"
|
|
echo ""
|
|
echo "To schedule daily backups, add to crontab:"
|
|
echo " 0 2 * * * cd $(pwd) && ./scripts/backup-pfsense-config.sh"
|
|
echo ""
|
|
}
|
|
|
|
################################################################################
|
|
# Main Execution
|
|
################################################################################
|
|
|
|
main() {
|
|
# Parse arguments properly (flags first, then positional)
|
|
local dry_run=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-n|--no-commit)
|
|
AUTO_COMMIT="false"
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
dry_run=true
|
|
shift
|
|
;;
|
|
-*)
|
|
log_error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
*)
|
|
# This is a positional argument (hostname)
|
|
PFSENSE_HOST="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ pfSense Configuration Backup Utility ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# Show configuration
|
|
log_info "Configuration:"
|
|
echo " Host: ${PFSENSE_HOST}"
|
|
echo " User: ${PFSENSE_USER}"
|
|
echo " Remote path: ${PFSENSE_CONFIG_PATH}"
|
|
echo " Backup dir: ${BACKUP_DIR}"
|
|
echo " Auto-commit: ${AUTO_COMMIT}"
|
|
echo ""
|
|
|
|
# Check prerequisites
|
|
if ! check_prerequisites; then
|
|
log_error "Prerequisites check failed"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Test SSH connection
|
|
if ! test_ssh_connection; then
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
if [[ "${dry_run}" == "true" ]]; then
|
|
log_success "Dry-run completed successfully. SSH connection is working."
|
|
echo "To perform actual backup, run: $(basename "$0") ${PFSENSE_HOST}"
|
|
exit 0
|
|
fi
|
|
|
|
# Fetch configuration
|
|
if ! fetch_config; then
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Validate the backup
|
|
if ! validate_config; then
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Commit to git (if enabled)
|
|
if [[ "${AUTO_COMMIT}" == "true" ]]; then
|
|
if ! commit_to_git; then
|
|
log_warning "Backup was downloaded but git commit may have failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
log_info "Skipping git commit (AUTO_COMMIT=false)"
|
|
fi
|
|
|
|
# Show summary
|
|
echo ""
|
|
show_summary
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|