Add pfSense backup utility and documentation
- Add backup-pfsense-config.sh script for automated config backups via SSH - Auto-commits backups to git with timestamped filenames - Includes validation, error handling, and troubleshooting guides - Add scripts/README.md with detailed usage and crontab examples - Add BACKUP-QUICKSTART.md for quick reference commands - Update README.md to reference automated backup workflow - Create backups/ directory structure The script tests SSH connectivity successfully to pfSense.
This commit is contained in:
388
pfsense.home.arpa/scripts/backup-pfsense-config.sh
Executable file
388
pfsense.home.arpa/scripts/backup-pfsense-config.sh
Executable file
@@ -0,0 +1,388 @@
|
||||
#!/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..."
|
||||
|
||||
local relative_path
|
||||
relative_path=$(git -C "${REPO_ROOT}" ls-files --full-name "${BACKUP_FILE}" 2>/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 '<vlan>' "${BACKUP_FILE}" 2>/dev/null || echo "unknown")
|
||||
local rule_count
|
||||
rule_count=$(grep -c '<rule>' "${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 "$@"
|
||||
Reference in New Issue
Block a user