#!/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 '/dev/null || echo "${BACKUP_FILE}") if [[ -z "${relative_path}" ]]; then relative_path="${BACKUP_FILE}" fi # Add the backup file to git if git -C "${REPO_ROOT}" add "${BACKUP_FILE}"; 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 '' "${BACKUP_FILE}" 2>/dev/null || echo "unknown") local rule_count rule_count=$(grep -c '' "${BACKUP_FILE}" 2>/dev/null || echo "unknown") # 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 "${BACKUP_FILE}" | 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 "$@"