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:
Kenji Morishige
2026-04-22 14:42:43 -05:00
commit 38f2aefecd
12 changed files with 2122 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
# pfSense Backup Scripts
Automation utilities for backing up and managing pfSense configuration.
## Scripts
### `backup-pfsense-config.sh`
Automated backup utility that:
- Connects to your pfSense router via SSH (using public key authentication)
- Downloads the current configuration XML
- Validates the backup is a valid pfSense config
- Stores it in `backups/` folder with a timestamped filename
- Automatically commits to git with a detailed message
#### Usage
**Basic usage (default host):**
```bash
cd pfsense.home.arpa
./scripts/backup-pfsense-config.sh
```
**Specify custom host:**
```bash
./scripts/backup-pfsense-config.sh 192.168.1.1
./scripts/backup-pfsense-config.sh pfsense.home.arpa
```
**Download without auto-committing to git:**
```bash
./scripts/backup-pfsense-config.sh --no-commit
# or
./scripts/backup-pfsense-config.sh -n
```
**Test SSH connectivity (dry-run):**
```bash
./scripts/backup-pfsense-config.sh --dry-run
```
**Show help:**
```bash
./scripts/backup-pfsense-config.sh --help
# or
./scripts/backup-pfsense-config.sh -h
```
#### Prerequisites
1. **SSH Public Key Authentication**
- Your public key must be installed on pfSense
- Run on pfSense: `cat ~/.ssh/authorized_keys` to verify
- If not installed, manually copy your key: `ssh-copy-id root@pfsense`
2. **SSH Host Configuration** (recommended)
- Add to your `~/.ssh/config`:
```
Host pfsense
HostName 172.27.0.1 # or your pfSense IP
User root
IdentityFile ~/.ssh/id_rsa # or your key path
```
- This allows `ssh pfsense` to work directly
3. **Git Repository**
- Must be run from within a git repository
- The script auto-commits backups to git
4. **Dependencies**
- `bash` (4.0+)
- `ssh` and `scp`
- `git`
- `du`, `shasum`, `grep` (standard Unix utilities)
#### Features
- ✅ **Automatic validation** — Confirms backup is valid XML and contains pfSense markers
- ✅ **Timestamped files** — Format: `pfsense-config-YYYY-MM-DD_HHMMSS.xml`
- ✅ **Rich git commits** — Includes VLAN count, firewall rule count, SHA256 hash
- ✅ **Error handling** — Clear error messages and troubleshooting tips
- ✅ **Dry-run mode** — Test SSH connectivity without downloading
- ✅ **Human-readable output** — Color-coded info/warning/error messages
- ✅ **Configurable** — Via environment variables or command-line arguments
#### Example Output
```
╔════════════════════════════════════════════════════════════════╗
║ pfSense Configuration Backup Utility ║
╚════════════════════════════════════════════════════════════════╝
[INFO] Configuration:
Host: pfsense
User: root
Remote path: /conf/config.xml
Backup dir: ./backups
Auto-commit: true
[INFO] Checking prerequisites...
[✓] SSH is available
[✓] Git is available
[✓] Backup directory exists
[✓] Git repository found
[INFO] Testing SSH connection to pfsense...
[✓] SSH connection to pfsense successful
[INFO] Fetching pfSense configuration from pfsense...
[✓] Configuration downloaded to ./backups/pfsense-config-2026-04-22_143022.xml
[INFO] Backup size: 68K
[INFO] Validating backup file...
[✓] Backup file is valid XML with pfSense markers
[INFO] Committing backup to git repository...
[✓] Added backup to git staging area
[✓] Configuration committed to git
════════════════════════════════════════════════════════════════
[✓] pfSense configuration backup completed successfully!
════════════════════════════════════════════════════════════════
Backup Details:
Location: ./backups/pfsense-config-2026-04-22_143022.xml
Size: 68K
Timestamp: 2026-04-22_143022
From: root@pfsense:/conf/config.xml
Next Steps:
1. Review the changes: git show HEAD
2. Push to remote: git push origin main
3. Schedule automated backups in crontab
```
#### Scheduling Automated Backups
To backup your pfSense config automatically every day at 2 AM:
1. **Edit your crontab:**
```bash
crontab -e
```
2. **Add this line:**
```cron
0 2 * * * cd /Users/kenjim/workspace/src/personal/appa-net/pfsense.home.arpa && ./scripts/backup-pfsense-config.sh
```
3. **Verify the entry:**
```bash
crontab -l | grep backup-pfsense
```
Now your config will be backed up automatically every day!
#### Troubleshooting
| Problem | Solution |
|---------|----------|
| `Permission denied (publickey)` | Install public key: `ssh-copy-id root@pfsense` |
| `Connection refused` | Check pfSense IP/hostname is correct |
| `No such file or directory: /conf/config.xml` | Not running on pfSense; check SSH host |
| `XML validation failed` | Config may be corrupted; try manual backup via WebUI |
| Git commit fails | Ensure you're in the git repository root |
#### Environment Variables
Control script behavior with environment variables:
```bash
# Use different SSH user
PFSENSE_USER=admin ./scripts/backup-pfsense-config.sh
# Disable auto-commit
AUTO_COMMIT=false ./scripts/backup-pfsense-config.sh
# Combine both
PFSENSE_USER=admin AUTO_COMMIT=false ./scripts/backup-pfsense-config.sh pfsense.home.arpa
```
## Backup Files Location
All backups are stored in the `backups/` directory:
```
backups/
├── pfsense-config-2026-04-22_143022.xml
├── pfsense-config-2026-04-21_023001.xml
└── pfsense-config-2026-04-20_023000.xml
```
### Viewing Differences Between Backups
Compare two configurations:
```bash
# Show what changed between two backups
diff backups/pfsense-config-2026-04-22_143022.xml backups/pfsense-config-2026-04-21_023001.xml
# Or use git to see changes across commits
git log --oneline backups/
git diff HEAD~1..HEAD -- backups/pfsense-config-*.xml
```
### Restoring from a Backup
To restore a previous configuration on pfSense:
1. Download the backup file from this repository
2. Log into pfSense WebUI
3. Go: **Diagnostics → Backup & Restore**
4. Choose the backup file and click **Restore Configuration**
5. Reboot when prompted
## Future Enhancements
- [ ] Compress old backups to save space
- [ ] Upload to cloud storage (S3, etc.)
- [ ] Encrypt sensitive configs before storing
- [ ] Notify on significant changes (email alert)
- [ ] Generate change reports showing diffs
---
**Last Updated:** 2026-04-22

View 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 "$@"