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,68 @@
# Quick Backup Reference
One-liner commands to backup your pfSense configuration.
## Basic Commands
```bash
# Navigate to the pfsense folder
cd /Users/kenjim/workspace/src/personal/appa-net/pfsense.home.arpa
# Test SSH connection (no download)
./scripts/backup-pfsense-config.sh --dry-run
# Backup with auto-commit to git (default)
./scripts/backup-pfsense-config.sh
# Backup without auto-commit
./scripts/backup-pfsense-config.sh -n
# Backup from different host
./scripts/backup-pfsense-config.sh 192.168.1.1
```
## Schedule Daily Backups
Add to your crontab to run at 2 AM every day:
```bash
crontab -e
```
Then add this line:
```cron
0 2 * * * cd /Users/kenjim/workspace/src/personal/appa-net/pfsense.home.arpa && ./scripts/backup-pfsense-config.sh > /tmp/pfsense-backup.log 2>&1
```
Verify it was added:
```bash
crontab -l | grep backup-pfsense
```
## View Backups
```bash
# List all backups
ls -lh backups/
# Show latest backup
ls -lh backups/ | tail -1
# Check git history
git log --oneline backups/
# See what changed in latest backup
git show HEAD:backups/
```
## Restore from Backup
From pfSense WebUI:
1. **Diagnostics → Backup & Restore**
2. **Choose File** → select backup from `backups/` folder
3. **Restore Configuration**
4. Reboot when prompted
---
**Last Updated**: 2026-04-22

150
pfsense.home.arpa/INDEX.md Normal file
View File

@@ -0,0 +1,150 @@
# pfsense.home.arpa Documentation Index
Quick reference to all configuration files and guides in this folder.
## Files in This Directory
### 📘 **README.md** (Main Configuration Guide)
Comprehensive reference for managing your pfSense router. Covers:
- VLAN structure and definitions
- Step-by-step configuration instructions
- DHCP setup
- Firewall rules and access control
- Troubleshooting guide
**When to read**: When you want detailed explanations or reference material
**Length**: ~400 lines (detailed)
### ⚡ **VLAN-QUICKSTART.md** (Implementation Checklist)
Step-by-step checklist to implement VLANs. Use this for hands-on setup.
- Pre-implementation checklist
- 6 phases with checkbox items
- Testing procedures
- Backup & documentation steps
**When to read**: When actively configuring your pfSense
**Length**: ~300 lines (action-oriented)
### 📊 **VLAN-CONFIG.md** (Configuration Reference)
YAML-formatted configuration definitions and quick lookup.
- VLAN definitions (IDs, subnets, purposes)
- Firewall rule summary (matrix view)
- DHCP configuration
- Device assignments
- Implementation checklist
- Design rationale
**When to read**: For quick lookup of VLAN IDs, subnet ranges, firewall rules
**Length**: ~150 lines (reference)
### 🗺️ **VLAN-TOPOLOGY.md** (Visual Architecture)
Network diagrams and traffic flow visualization.
- High-level topology ASCII diagrams
- Detailed dataflow examples
- Firewall rule chain visualization
- Port connectivity diagrams
- Traffic examples (allowed and blocked)
- Isolation guarantees
**When to read**: To understand network architecture or debug traffic issues
**Length**: ~250 lines (visual/conceptual)
## Quick Navigation
**I want to...**
| Task | File | Section |
|------|------|---------|
| Set up VLANs for the first time | VLAN-QUICKSTART.md | Phase 1-6 |
| Understand VLAN architecture | VLAN-TOPOLOGY.md | High-Level Topology |
| Look up a VLAN subnet | VLAN-CONFIG.md | VLAN Definitions |
| Configure firewall rules | README.md | Step 4: Configure Firewall Rules |
| Set up DHCP | README.md | Step 3: Configure DHCP |
| Troubleshoot a problem | README.md | Troubleshooting section |
| Debug traffic | VLAN-TOPOLOGY.md | Traffic Examples |
| Backup my config | README.md | Backup & Recovery |
| Add a new device to a VLAN | VLAN-CONFIG.md | Device Assignments |
## File Relationships
```
README.md
├─ Detailed explanation of all features
├─ References: VLAN-CONFIG.md, VLAN-TOPOLOGY.md
└─ Use with: VLAN-QUICKSTART.md for hands-on setup
VLAN-QUICKSTART.md
├─ Step-by-step checklist
├─ References: VLAN-CONFIG.md
└─ Use with: README.md for details
VLAN-CONFIG.md
├─ Quick reference data
├─ Subnet/VLAN ID lookup
└─ Use with: README.md, VLAN-TOPOLOGY.md for context
VLAN-TOPOLOGY.md
├─ Visual architecture
├─ Traffic flow examples
└─ Use with: README.md for firewall rule explanations
```
## Setup Workflow Recommended
1. **Read** VLAN-TOPOLOGY.md (understand the architecture)
2. **Review** VLAN-CONFIG.md (familiarize yourself with IDs and subnets)
3. **Follow** VLAN-QUICKSTART.md (step-by-step implementation)
4. **Reference** README.md (for detailed explanations during setup)
5. **Store** backups/ folder (save pfSense XML configs here)
## Directory Structure
```
pfsense.home.arpa/
├── README.md # Main configuration guide
├── VLAN-QUICKSTART.md # Hands-on setup checklist
├── VLAN-CONFIG.md # VLAN reference data
├── VLAN-TOPOLOGY.md # Network diagrams
├── INDEX.md # This file
├── backups/ # Store pfSense backups here
│ └── pfsense-config-YYYY-MM-DD.xml
└── scripts/ # Optional: Automation scripts
└── (future: Ansible, Terraform, etc.)
```
## Backup Location
All pfSense configuration exports should be saved to `backups/` folder:
```bash
# After exporting from pfSense WebUI:
mv ~/Downloads/config.xml backups/pfsense-config-2026-04-22.xml
git add backups/
git commit -m "pfSense: Backup after VLAN configuration"
```
## Future Additions
As your network grows, consider adding:
- `FIREWALL-RULES.md` — Detailed firewall rule documentation
- `DHCP-RESERVATIONS.md` — Static IP assignments for devices
- `DNS-CONFIG.md` — DNS resolver and record configuration
- `SCRIPTS/` — Ansible playbooks, Terraform configs, or backup scripts
- `MIGRATION-GUIDE.md` — How to restore from backup or migrate to new pfSense instance
## Version History
| Date | Version | Changes |
|------|---------|---------|
| 2026-04-22 | 1.0 | Initial VLAN configuration (3 VLANs: Secure, AIWorkload, IoT) |
## Related Documents
- [Root README.md](../README.md) — Project overview
- [zet.home.arpa/PROXY-SETUP.md](../zet.home.arpa/PROXY-SETUP.md) — Squid proxy on VLAN (future: will reference this VLAN config)
---
**Last Updated**: 2026-04-22
**Current Configuration Version**: 1.0

296
pfsense.home.arpa/README.md Normal file
View File

@@ -0,0 +1,296 @@
# pfSense Router Configuration
Central hub for your home network. Manages DHCP, DNS, routing, firewalling, and network segmentation via VLANs.
## Overview
**Device**: pfSense Router
**Primary IP**: 172.27.0.1 (LAN default gateway)
**Role**: Router, Firewall, DHCP server, DNS resolver, VLAN orchestration
## Network Architecture
Your home network is segmented into security zones using VLANs. Each VLAN has:
- Isolated broadcast domain
- Separate IP subnet
- Controlled routing and firewall rules between VLANs
- Dedicated DHCP scope (if needed)
### VLAN Structure
| VLAN ID | Name | Purpose | Subnet | Gateway | Notes |
|---------|------|---------|--------|---------|-------|
| 1 | `LAN_SECURE` | Trusted personal devices | 172.27.0.0/24 | 172.27.0.1 | Primary network (default) |
| 2 | `VLAN_AIWORKLOAD` | AI/ML dangerous workloads (openclaw) | 172.27.2.0/24 | 172.27.2.1 | Isolated, minimal internet access |
| 3 | `VLAN_IOT` | IoT devices (cameras, smart home) | 172.27.3.0/24 | 172.27.3.1 | Limited trust, blocked from LAN_SECURE |
## Configuration Steps
### Prerequisites
- Access to pfSense WebUI or SSH
- Understanding of your network hardware (which ports support VLANs)
### Step 1: Create VLANs on Physical Interface
1. Navigate: **Interfaces → VLANs**
2. Click **+ Add**
3. Create `VLAN_AIWORKLOAD`:
- **Parent Interface**: `em0` (or your LAN NIC)
- **VLAN Tag**: `2`
- **VLAN Priority**: `0` (default)
- **Description**: `VLAN_AIWORKLOAD`
- Click **Save**
4. Create `VLAN_IOT`:
- **Parent Interface**: `em0`
- **VLAN Tag**: `3`
- **VLAN Priority**: `0`
- **Description**: `VLAN_IOT`
- Click **Save**
5. Click **Apply Changes**
### Step 2: Create Virtual Interfaces
1. Navigate: **Interfaces → Assignments**
2. Click **+ Add** next to `VLAN_AIWORKLOAD_2`:
- A new interface (e.g., `OPT1`) is created automatically
- Click the pencil icon to configure it
3. Configure `OPT1` (VLAN_AIWORKLOAD):
- **Enable Interface**: ✓ Checked
- **Description**: `VLAN_AIWORKLOAD`
- **IPv4 Configuration Type**: `Static IPv4`
- **IPv4 Address**: `172.27.2.1`
- **IPv4 Subnet Mask**: `255.255.255.0` (/24)
- Click **Save**
4. Repeat for `VLAN_IOT_3`:
- Configure as `OPT2`
- **Description**: `VLAN_IOT`
- **IPv4 Address**: `172.27.3.1`
- **IPv4 Subnet Mask**: `255.255.255.0` (/24)
- Click **Save**
5. Click **Apply Changes**
### Step 3: Configure DHCP for Each VLAN
#### VLAN_AIWORKLOAD DHCP
1. Navigate: **Services → DHCP Server**
2. Click the **VLAN_AIWORKLOAD** tab
3. **Enable DHCP server on VLAN_AIWORKLOAD interface**: ✓ Checked
4. **Range**: `172.27.2.100` to `172.27.2.200`
5. **Gateway**: `172.27.2.1`
6. **Servers**:
- DNS 1: `172.27.0.1` (pfSense resolver)
- DNS 2: `8.8.8.8` (optional fallback)
7. Click **Save**
#### VLAN_IOT DHCP
1. Click the **VLAN_IOT** tab
2. **Enable DHCP server on VLAN_IOT interface**: ✓ Checked
3. **Range**: `172.27.3.100` to `172.27.3.200`
4. **Gateway**: `172.27.3.1`
5. **Servers**:
- DNS 1: `172.27.0.1`
- DNS 2: `8.8.8.8`
6. Click **Save**
### Step 4: Configure Firewall Rules
#### Allow WAN → All VLANs
Navigate: **Firewall → Rules → WAN**
- Default rule should allow established connections
#### Default LAN → VLAN Rules
Navigate: **Firewall → Rules → LAN_SECURE**
1. **Allow LAN_SECURE → VLAN_AIWORKLOAD** (if needed):
- Action: `Pass`
- Interface: `LAN`
- Direction: `in`
- Source: `LAN_SECURE subnet`
- Destination: `VLAN_AIWORKLOAD subnet`
- Click **Save**
2. **Block LAN_SECURE ↔ VLAN_IOT** (by default, implicit deny):
- *No rule needed* — VLANs are isolated by default
- Optionally add explicit block rule for security
#### VLAN_AIWORKLOAD Rules
Navigate: **Firewall → Rules → VLAN_AIWORKLOAD**
1. **Allow VLAN_AIWORKLOAD → WAN** (for outbound internet):
- Action: `Pass`
- Source: `VLAN_AIWORKLOAD subnet`
- Destination: `any`
- Protocol: `TCP/UDP`
- Click **Save**
2. **Block VLAN_AIWORKLOAD → LAN_SECURE**:
- Action: `Block`
- Source: `VLAN_AIWORKLOAD subnet`
- Destination: `LAN_SECURE subnet`
- Click **Save**
#### VLAN_IOT Rules
Navigate: **Firewall → Rules → VLAN_IOT**
1. **Allow VLAN_IOT → WAN** (for NTP, updates, cloud APIs):
- Action: `Pass`
- Source: `VLAN_IOT subnet`
- Destination: `any`
- Protocol: `TCP/UDP`
- Click **Save**
2. **Block VLAN_IOT → LAN_SECURE**:
- Action: `Block`
- Source: `VLAN_IOT subnet`
- Destination: `LAN_SECURE subnet`
- Click **Save**
3. **Block VLAN_IOT → VLAN_AIWORKLOAD** (optional):
- Action: `Block`
- Source: `VLAN_IOT subnet`
- Destination: `VLAN_AIWORKLOAD subnet`
- Click **Save**
### Step 5: Configure Port Assignments (If Hardware Supports)
If your switch/NIC supports physical VLAN tagging:
Navigate: **Interfaces → Physical Ports** (varies by pfSense version)
Example configuration:
- **Port 1**: LAN_SECURE (VLAN 1, untagged)
- **Port 2**: VLAN_AIWORKLOAD (VLAN 2, tagged)
- **Port 3**: VLAN_IOT (VLAN 3, tagged)
- **Port 4**: WAN
*This step depends on your hardware. If using a managed switch, configure VLAN tagging there instead.*
## Network Access Matrix
Shows which VLANs can reach which destinations:
```
FROM TO_LAN_SECURE TO_AIWORKLOAD TO_IOT TO_WAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LAN_SECURE ✓ (same) ✗ BLOCK ✗ BLOCK ✓
VLAN_AIWORKLOAD ✗ BLOCK ✓ (same) ✗ BLOCK ✓
VLAN_IOT ✗ BLOCK ✗ BLOCK ✓ (same) ✓
```
## Device Assignment
Assign devices to VLANs via:
1. **DHCP reservations** (Recommended):
- Navigate: **Services → DHCP Server**
- Tab: Desired VLAN
- Scroll to **DHCP Static Mappings**
- Add device MAC → static IP in that VLAN
- Example: Openclaw server → `172.27.2.50` (VLAN_AIWORKLOAD)
2. **Manual static configuration**:
- Configure device IP in the target VLAN subnet
- Set gateway to VLAN gateway (172.27.2.1, 172.27.3.1, etc.)
3. **Switch port assignment** (if hardware supports):
- Assign physical switch ports to VLANs
- Devices connected to those ports get VLAN membership
## DNS Configuration
Navigate: **Services → DNS Resolver**
1. **Enable DNS Resolver**: ✓ Checked
2. **Network Interfaces**: Select all interfaces (LAN, VLAN_AIWORKLOAD, VLAN_IOT)
3. **Forward Mode**: Check if needed for external DNS
4. **Access Lists**:
- Ensure all VLANs can query DNS on their gateways
This allows devices in each VLAN to resolve hostnames locally.
## Backup & Recovery
### Automated Backup (Recommended)
Use the included backup utility script for automated, versioned backups:
```bash
cd pfsense.home.arpa
./scripts/backup-pfsense-config.sh
```
This script:
- Connects to pfSense via SSH (using your public key)
- Downloads the current configuration XML
- Validates it's a valid pfSense config
- Stores it in `backups/` with a timestamped filename
- Automatically commits to git with configuration details
**Schedule automated daily backups:**
```bash
# Add to crontab (backups every day at 2 AM)
0 2 * * * cd /path/to/appa-net/pfsense.home.arpa && ./scripts/backup-pfsense-config.sh
```
For more details, see: [scripts/README.md](scripts/README.md)
### Manual Backup
If you prefer manual backups or SSH isn't available:
1. Navigate: **Diagnostics → Backup & Restore**
2. Click **Download configuration as XML**
3. Save to: `pfsense.home.arpa/backups/pfsense-config-YYYY-MM-DD.xml`
4. Commit manually:
```bash
git add backups/pfsense-config-2026-04-22.xml
git commit -m "pfSense: Backup configuration (manual)"
git push
```
### Restoring Configuration
1. Navigate: **Diagnostics → Backup & Restore**
2. Click **Choose File** and select XML backup
3. Click **Restore Configuration**
4. Reboot when prompted
## Maintenance Tasks
- **Monthly**: Export and backup pfSense configuration
- **Quarterly**: Review firewall rules and DHCP assignments
- **As needed**: Adjust rules based on new devices or requirements
## Troubleshooting
### VLANs not working:
- Verify VLAN tags are correct in Interfaces → VLANs
- Ensure virtual interfaces are enabled (Interfaces → Assignments)
- Check physical switch VLAN configuration if using managed switch
### Devices can't get DHCP:
- Verify DHCP is enabled for the VLAN
- Check DHCP range is correct
- Inspect DHCP leases: **Status → DHCP Leases**
### Can't ping between VLANs (expected):
- Verify firewall rules are allowing or blocking as desired
- Check rule order (first match wins)
- Use **Diagnostics → Packet Capture** to debug
## References
- [pfSense VLAN Documentation](https://docs.netgate.com/pfsense/en/latest/vlan/index.html)
- [pfSense Firewall Rules](https://docs.netgate.com/pfsense/en/latest/firewall/index.html)
- RFC 2644 — VLAN tagging
- RFC 5735 — Special Use IPv4 Addresses
---
**Last Updated:** 2026-04-22
**Configuration Version:** 1.0

View File

@@ -0,0 +1,173 @@
# VLAN Configuration Reference
Network segmentation configuration for pfsense.home.arpa router.
## VLAN Definitions
```yaml
vlans:
lan_secure:
vlan_id: 1
description: "Main trusted network"
subnet: "172.27.0.0/24"
gateway: "172.27.0.1"
dhcp_start: "172.27.0.100"
dhcp_end: "172.27.0.200"
purpose: "Primary network for personal/trusted devices"
isolation: "Gateway to WAN, can access VLANs as configured"
firewall_default: "allow_outbound"
vlan_aiworkload:
vlan_id: 2
description: "AI/ML Workload (Dangerous/OpenClaw)"
subnet: "172.27.2.0/24"
gateway: "172.27.2.1"
dhcp_start: "172.27.2.100"
dhcp_end: "172.27.2.200"
purpose: "Isolated workload for AI/ML experiments, sandbox for untrusted code"
isolation: "Blocked from LAN_SECURE, can access WAN"
firewall_default: "deny_incoming, allow_outbound_to_wan"
access_from_secure: "none" # LAN_SECURE cannot reach this VLAN
vlan_iot:
vlan_id: 3
description: "IoT Devices"
subnet: "172.27.3.0/24"
gateway: "172.27.3.1"
dhcp_start: "172.27.3.100"
dhcp_end: "172.27.3.200"
purpose: "Smart home devices (cameras, sensors, thermostats, etc.)"
isolation: "Blocked from LAN_SECURE, can access WAN for updates/APIs"
firewall_default: "deny_incoming, allow_outbound_to_wan"
access_from_secure: "none" # LAN_SECURE cannot reach this VLAN
```
## Firewall Rule Summary
### From LAN_SECURE (172.27.0.0/24)
- ✓ To Internet (WAN)
- ✗ To VLAN_AIWORKLOAD (blocked)
- ✗ To VLAN_IOT (blocked)
- ✓ Internal (same subnet)
### From VLAN_AIWORKLOAD (172.27.2.0/24)
- ✓ To Internet (WAN)
- ✗ To LAN_SECURE (blocked)
- ✗ To VLAN_IOT (blocked)
- ✓ Internal (same subnet)
### From VLAN_IOT (172.27.3.0/24)
- ✓ To Internet (WAN)
- ✗ To LAN_SECURE (blocked)
- ✗ To VLAN_AIWORKLOAD (blocked)
- ✓ Internal (same subnet)
## DHCP Configuration
Each VLAN has its own DHCP server:
```
VLAN_SECURE: 172.27.0.100 - 172.27.0.200 (Gateway: 172.27.0.1)
VLAN_AIWORKLOAD: 172.27.2.100 - 172.27.2.200 (Gateway: 172.27.2.1)
VLAN_IOT: 172.27.3.100 - 172.27.3.200 (Gateway: 172.27.3.1)
```
**DNS Server** (for all VLANs): 172.27.0.1 (pfSense resolver)
## Physical Switch Configuration (If Applicable)
If using a managed switch, configure VLAN tagging:
```
Port 1 (LAN_SECURE):
- Mode: Access
- VLAN: 1 (untagged, native)
- Devices: Personal computers, laptops
Port 2 (VLAN_AIWORKLOAD):
- Mode: Access
- VLAN: 2 (untagged)
- Devices: Openclaw server, GPU workstations
- OR: Trunk (if pfSense applies tags)
Port 3 (VLAN_IOT):
- Mode: Access
- VLAN: 3 (untagged)
- Devices: Smart home devices, cameras, sensors
- OR: Trunk (if pfSense applies tags)
Port 4 (Uplink to pfSense):
- Mode: Trunk
- VLANs: 1, 2, 3
- Tagged: 2, 3 (VLAN 1 typically untagged on trunk)
```
## Device Assignments
Assign devices to VLANs using DHCP static mappings or by setting up switch port VLANs.
### Planned Devices
**VLAN_SECURE (LAN_SECURE):**
- [ ] Your personal laptop/desktop
- [ ] Network printer (if any)
- [ ] Home automation controller (if trusted)
**VLAN_AIWORKLOAD (VLAN_AIWORKLOAD):**
- [ ] Openclaw server / AI workstation
- [ ] GPU compute server
- [ ] Experimental machine learning environment
**VLAN_IOT (VLAN_IOT):**
- [ ] Smart home cameras
- [ ] Temperature/humidity sensors
- [ ] Smart thermostat
- [ ] IoT gateway (if not trusted)
- [ ] Smart switches/outlets
## Implementation Checklist
- [ ] Create VLAN 2 (VLAN_AIWORKLOAD) on parent interface
- [ ] Create VLAN 3 (VLAN_IOT) on parent interface
- [ ] Apply VLAN changes
- [ ] Create virtual interface for VLAN_AIWORKLOAD (OPT1)
- [ ] Set IP: 172.27.2.1/24
- [ ] Enable interface
- [ ] Apply changes
- [ ] Create virtual interface for VLAN_IOT (OPT2)
- [ ] Set IP: 172.27.3.1/24
- [ ] Enable interface
- [ ] Apply changes
- [ ] Configure DHCP for VLAN_AIWORKLOAD
- [ ] Configure DHCP for VLAN_IOT
- [ ] Configure firewall rules for LAN_SECURE
- [ ] Configure firewall rules for VLAN_AIWORKLOAD
- [ ] Configure firewall rules for VLAN_IOT
- [ ] Test DHCP on each VLAN
- [ ] Test inter-VLAN isolation
- [ ] Backup pfSense configuration
- [ ] Commit configuration to git
## Notes & Decisions
### Why These Subnets?
- **172.27.x.x/16**: Private RFC 1918 range (172.16.0.0 - 172.31.255.255)
- Each VLAN gets a /24 subnet (254 usable IPs per VLAN)
- Easy to route and remember (VLAN ID = third octet)
### Why This Isolation?
- **LAN_SECURE** ↔ **VLAN_AIWORKLOAD**: Complete isolation prevents compromised AI workload from reaching trusted devices
- **LAN_SECURE** ↔ **VLAN_IOT**: IoT devices have broader vulnerabilities; isolation prevents lateral movement
- **VLAN_AIWORKLOAD** ↔ **VLAN_IOT**: Reduces attack surface between untrusted zones
- All VLANs → WAN: Allows devices to update, phone home, or reach cloud services
### Future Enhancements
- Add guest VLAN for visitors
- Configure VPN access to VLAN_SECURE only
- Implement QoS rules per VLAN
- Add Intrusion Detection (Suricata) on VLAN boundaries
- Monitor inter-VLAN traffic in firewall logs
---
**Last Updated:** 2026-04-22

View File

@@ -0,0 +1,291 @@
# VLAN Implementation Quickstart
Step-by-step checklist for configuring VLANs on pfSense. Use this guide to implement your 3-VLAN network.
## Pre-Implementation
Before you start, gather this information:
- [ ] pfSense WebUI URL (usually `https://192.168.1.1` or similar)
- [ ] Admin credentials for pfSense
- [ ] Your WAN/LAN interface names (check: **Interfaces → Assignments**)
- [ ] Backup your current pfSense config (download before making changes)
- [ ] Physical switch info (if you have one) — check if it supports VLAN tagging
## Phase 1: Create VLANs (5 minutes)
1. **Log into pfSense WebUI**
- [ ] Open `https://[pfSense-IP]`
- [ ] Enter admin credentials
2. **Navigate to VLAN Creation**
- [ ] Go: **Interfaces → VLANs**
- [ ] Click **Display Advanced**
3. **Create VLAN_AIWORKLOAD**
- [ ] Click **+ Add**
- [ ] Parent Interface: `em0` (or your LAN NIC name)
- [ ] VLAN Tag: `2`
- [ ] VLAN Priority: `0`
- [ ] Description: `VLAN_AIWORKLOAD`
- [ ] Click **Save**
4. **Create VLAN_IOT**
- [ ] Click **+ Add**
- [ ] Parent Interface: `em0`
- [ ] VLAN Tag: `3`
- [ ] VLAN Priority: `0`
- [ ] Description: `VLAN_IOT`
- [ ] Click **Save**
5. **Apply Changes**
- [ ] Click **Apply Changes** button
- [ ] Wait for reboot/apply to complete
## Phase 2: Assign Virtual Interfaces (5 minutes)
1. **Navigate to Assignments**
- [ ] Go: **Interfaces → Assignments**
2. **Note down the OPT interfaces created**
- [ ] You should see two new entries: e.g., `em0.2` and `em0.3`
- [ ] These will be assigned as `OPT1` and `OPT2` (or similar)
3. **Click the OPT1 link** (VLAN_AIWORKLOAD)
- [ ] Description: `VLAN_AIWORKLOAD`
- [ ] IPv4 Configuration Type: `Static IPv4`
- [ ] IPv4 Address: `172.27.2.1`
- [ ] IPv4 Subnet Mask: `255.255.255.0`
- [ ] IPv6 Configuration Type: `None`
- [ ] **Enable Interface**: ✓ Check this box
- [ ] Scroll down and click **Save**
4. **Repeat for OPT2** (VLAN_IOT)
- [ ] Description: `VLAN_IOT`
- [ ] IPv4 Configuration Type: `Static IPv4`
- [ ] IPv4 Address: `172.27.3.1`
- [ ] IPv4 Subnet Mask: `255.255.255.0`
- [ ] **Enable Interface**: ✓ Check this box
- [ ] Scroll down and click **Save**
5. **Apply Changes**
- [ ] Click **Apply Changes** button
## Phase 3: Configure DHCP (10 minutes)
1. **Navigate to DHCP Server**
- [ ] Go: **Services → DHCP Server**
2. **Configure VLAN_AIWORKLOAD DHCP**
- [ ] Click **VLAN_AIWORKLOAD** tab
- [ ] **Enable DHCP server on VLAN_AIWORKLOAD interface**: ✓ Check
- [ ] **Range Start**: `172.27.2.100`
- [ ] **Range End**: `172.27.2.200`
- [ ] Scroll down to **Servers** section
- [ ] **DNS 1**: `172.27.0.1` (pfSense)
- [ ] **DNS 2**: `8.8.8.8` (optional backup)
- [ ] **Gateway**: Should auto-populate as `172.27.2.1`
- [ ] Scroll down and click **Save**
3. **Configure VLAN_IOT DHCP**
- [ ] Click **VLAN_IOT** tab
- [ ] **Enable DHCP server on VLAN_IOT interface**: ✓ Check
- [ ] **Range Start**: `172.27.3.100`
- [ ] **Range End**: `172.27.3.200`
- [ ] Scroll down to **Servers** section
- [ ] **DNS 1**: `172.27.0.1`
- [ ] **DNS 2**: `8.8.8.8`
- [ ] **Gateway**: Should auto-populate as `172.27.3.1`
- [ ] Scroll down and click **Save**
4. **Verify LAN DHCP**
- [ ] Click **LAN** tab
- [ ] Confirm **Enable DHCP server on LAN interface** is ✓ checked
- [ ] Verify gateway is `172.27.0.1`
- [ ] Click **Save**
5. **Apply Changes**
- [ ] Click **Apply Changes** button
## Phase 4: Configure Firewall Rules (15 minutes)
### LAN → VLAN Rules
1. **Go to LAN rules**
- [ ] **Firewall → Rules → LAN**
2. **Add rule: Block LAN → VLAN_AIWORKLOAD**
- [ ] Click **+ Add** (at bottom)
- [ ] Action: `Block`
- [ ] Interface: `LAN`
- [ ] Direction: `in`
- [ ] Address Family: `IPv4`
- [ ] Protocol: `any`
- [ ] Source: `LAN subnet` (or specify `172.27.0.0/24`)
- [ ] Destination: `VLAN_AIWORKLOAD subnet` (specify `172.27.2.0/24`)
- [ ] Description: `Block LAN → VLAN_AIWORKLOAD`
- [ ] Click **Save**
3. **Add rule: Block LAN → VLAN_IOT**
- [ ] Click **+ Add**
- [ ] Action: `Block`
- [ ] Interface: `LAN`
- [ ] Source: `172.27.0.0/24`
- [ ] Destination: `172.27.3.0/24`
- [ ] Description: `Block LAN → VLAN_IOT`
- [ ] Click **Save**
### VLAN_AIWORKLOAD Rules
1. **Go to VLAN_AIWORKLOAD rules**
- [ ] **Firewall → Rules → VLAN_AIWORKLOAD** (or OPT1)
2. **Add rule: Block VLAN_AIWORKLOAD → LAN**
- [ ] Click **+ Add**
- [ ] Action: `Block`
- [ ] Interface: `VLAN_AIWORKLOAD`
- [ ] Source: `VLAN_AIWORKLOAD subnet` (specify `172.27.2.0/24`)
- [ ] Destination: `LAN subnet` (specify `172.27.0.0/24`)
- [ ] Description: `Block VLAN_AIWORKLOAD → LAN`
- [ ] Click **Save**
3. **Add rule: Allow VLAN_AIWORKLOAD → WAN**
- [ ] Click **+ Add**
- [ ] Action: `Pass`
- [ ] Interface: `VLAN_AIWORKLOAD`
- [ ] Source: `VLAN_AIWORKLOAD subnet` (specify `172.27.2.0/24`)
- [ ] Destination: `any`
- [ ] Protocol: `any`
- [ ] Description: `Allow VLAN_AIWORKLOAD → Internet`
- [ ] Click **Save**
### VLAN_IOT Rules
1. **Go to VLAN_IOT rules**
- [ ] **Firewall → Rules → VLAN_IOT** (or OPT2)
2. **Add rule: Block VLAN_IOT → LAN**
- [ ] Click **+ Add**
- [ ] Action: `Block`
- [ ] Interface: `VLAN_IOT`
- [ ] Source: `VLAN_IOT subnet` (specify `172.27.3.0/24`)
- [ ] Destination: `LAN subnet` (specify `172.27.0.0/24`)
- [ ] Description: `Block VLAN_IOT → LAN`
- [ ] Click **Save**
3. **Add rule: Block VLAN_IOT → VLAN_AIWORKLOAD**
- [ ] Click **+ Add**
- [ ] Action: `Block`
- [ ] Source: `172.27.3.0/24`
- [ ] Destination: `172.27.2.0/24`
- [ ] Description: `Block VLAN_IOT → VLAN_AIWORKLOAD`
- [ ] Click **Save**
4. **Add rule: Allow VLAN_IOT → WAN**
- [ ] Click **+ Add**
- [ ] Action: `Pass`
- [ ] Source: `VLAN_IOT subnet` (specify `172.27.3.0/24`)
- [ ] Destination: `any`
- [ ] Protocol: `any`
- [ ] Description: `Allow VLAN_IOT → Internet`
- [ ] Click **Save**
### Apply Firewall Changes
- [ ] Click **Apply Changes** button (usually at top of rules)
## Phase 5: Testing (10 minutes)
### Test DHCP
1. **Connect a test device to VLAN_AIWORKLOAD**
- [ ] Assign a device to this VLAN (via switch port or manually)
- [ ] Check if device gets IP in range 172.27.2.100-200
- [ ] Verify gateway shows 172.27.2.1
- [ ] Test ping to gateway: `ping 172.27.2.1`
2. **Connect a test device to VLAN_IOT**
- [ ] Assign a device to this VLAN
- [ ] Check if device gets IP in range 172.27.3.100-200
- [ ] Verify gateway shows 172.27.3.1
- [ ] Test ping to gateway: `ping 172.27.3.1`
### Test Inter-VLAN Isolation
1. **Test VLAN_AIWORKLOAD cannot reach LAN**
- [ ] From device on VLAN_AIWORKLOAD (172.27.2.x)
- [ ] Try ping to LAN device (172.27.0.x)
- [ ] Should timeout/fail ✗ (expected)
2. **Test VLAN_IOT cannot reach LAN**
- [ ] From device on VLAN_IOT (172.27.3.x)
- [ ] Try ping to LAN device (172.27.0.x)
- [ ] Should timeout/fail ✗ (expected)
3. **Test LAN cannot reach VLANs**
- [ ] From LAN device (172.27.0.x)
- [ ] Try ping to VLAN_AIWORKLOAD device (172.27.2.x)
- [ ] Should timeout/fail ✗ (expected)
### Test Internet Access
1. **Test VLAN_AIWORKLOAD → Internet**
- [ ] From device on VLAN_AIWORKLOAD
- [ ] Test DNS: `nslookup google.com`
- [ ] Test internet: `ping 8.8.8.8`
2. **Test VLAN_IOT → Internet**
- [ ] From device on VLAN_IOT
- [ ] Test DNS: `nslookup google.com`
- [ ] Test internet: `ping 8.8.8.8`
## Phase 6: Backup & Documentation (5 minutes)
1. **Backup pfSense Configuration**
- [ ] Go: **Diagnostics → Backup & Restore**
- [ ] Click **Download configuration as XML**
- [ ] Save as: `pfsense-config-vlan-setup-2026-04-22.xml`
2. **Commit to Git**
```bash
cd /Users/kenjim/workspace/src/personal/appa-net
git add pfsense.home.arpa/
git commit -m "pfSense: Initial VLAN configuration (VLAN_AIWORKLOAD, VLAN_IOT)"
git push
```
- [ ] Commit completed
3. **Document Completion**
- [ ] Update this file with completion date
- [ ] Note any deviations from plan
- [ ] Record interface names if different from expected
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Device not getting DHCP | Check DHCP is enabled for that VLAN in **Services → DHCP Server** |
| Can't ping gateway | Verify virtual interface is enabled (**Interfaces → Assignments**) |
| Can't reach internet | Check WAN allow rules in firewall |
| Still can reach between VLANs | Check firewall rules order (first match wins); rules may be in wrong order |
| Switch not forwarding VLAN traffic | Verify trunk port on switch is tagged for all VLANs |
## Post-Implementation
Once everything is working:
1. **Assign your devices** to VLANs via DHCP static mappings
- See [VLAN-CONFIG.md](VLAN-CONFIG.md) for device list
2. **Monitor firewall logs** for unexpected traffic
- Go: **Status → System Logs → Firewall**
3. **Update your documentation** as you add more devices
4. **Schedule regular backups**
- Monthly: Export pfSense config to `backups/pfsense-config-YYYY-MM-DD.xml`
---
**Estimated Total Time**: 45 minutes
**Last Updated**: 2026-04-22

View File

@@ -0,0 +1,262 @@
# VLAN Network Topology
Visual representation of your segmented home network architecture.
## High-Level Topology
```
┌─────────────────┐
│ Internet │
│ (WAN) │
└────────┬────────┘
┌──────────┴──────────┐
│ │
┌────▼─────────────────────▼────┐
│ pfSense Router │
│ (172.27.0.1) │
│ │
│ • DHCP Server │
│ • DNS Resolver │
│ • Firewall │
│ • VLAN Gateway │
└────┬────────┬────────┬────────┘
│ │ │
┌───────────┘ │ └──────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ VLAN 1 │ │ VLAN 2 │ │ VLAN 3 │
│ LAN_SECURE │ │ AIWORKLOAD │ │ IOT │
│ 172.27.0.0 │ │ 172.27.2.0 │ │ 172.27.3.0 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ │ │
┌──────▼────────┐ ┌──────▼────────┐ ┌──────▼────────┐
│ │ │ │ │ │
│ Trusted Devices│ │ Openclaw │ │ IoT Devices │
│ │ │ GPU Workload │ │ │
│ • Laptop │ │ │ │ • Cameras │
│ • Desktop │ │ (Sandbox/ │ │ • Sensors │
│ • Phone │ │ Experiment) │ │ • Thermostat │
│ │ │ │ │ • Smart Outlets│
└────────────────┘ └────────────────┘ └────────────────┘
```
## Detailed Dataflow
### Device to Internet (All VLANs)
```
Device (VLAN X)
Gateway (172.27.X.1)
pfSense Firewall
WAN Interface
Internet Router/Modem
Internet ✓
```
### Trusted to Untrusted (Blocked)
```
LAN_SECURE Device (172.27.0.100)
Request to VLAN_AIWORKLOAD (172.27.2.X)
pfSense Firewall Rule: BLOCK
X Connection Refused
```
## Firewall Rule Chain
```
┌─────────────────────────────────────────────────────┐
│ Inbound Packet on Interface (e.g., LAN_SECURE) │
└────────────────────┬────────────────────────────────┘
┌────────────────────────┐
│ Source IP in subnet? │
│ (172.27.0.0/24) │
└────────┬───────┬────────┘
│ │
YES│ │NO → Block (rule 1)
│ │
▼ │
┌────────────────────────┐
│ Destination Subnet? │
└────────┬───────┬────────┘
│ │
172.27.0.0/24 172.27.2.0/24 172.27.3.0/24
│ │ │
▼ ▼ ▼
Same VLAN ALLOW (rule 2) BLOCK (rule 3)
│ │ │
▼ ▼ ▼
✓ PASS ✓ PASS ✗ BLOCK
```
## Port Connectivity (Example with 4-Port Switch)
```
┌──────────────────────────────────────────┐
│ Managed Network Switch │
│ (or pfSense internal if no switch) │
├──────────────────────────────────────────┤
│ │
│ Port 1 (Access, VLAN 1) │
│ ├─ Trusted Device 1 │
│ └─ Trusted Device 2 │
│ │
│ Port 2 (Access, VLAN 2) │
│ ├─ Openclaw Server │
│ └─ GPU Workstation │
│ │
│ Port 3 (Access, VLAN 3) │
│ ├─ Smart Camera 1 │
│ ├─ Smart Camera 2 │
│ ├─ IoT Sensor │
│ └─ Smart Thermostat │
│ │
│ Port 4 (Trunk - All VLANs Tagged) │
│ └─ pfSense Router │
│ (Receives VLAN-tagged frames) │
│ │
└──────────────────────────────────────────┘
```
## Traffic Examples
### ✓ Allowed Traffic Paths
```
1. Trusted Device → Internet
172.27.0.100 → 8.8.8.8:53
Gateway: 172.27.0.1 → pfSense → WAN → Internet ✓
2. AI Workload → Internet
172.27.2.50 → updates.example.com:443
Gateway: 172.27.2.1 → pfSense → WAN → Internet ✓
3. IoT Device → NTP Server
172.27.3.102 → pool.ntp.org:123
Gateway: 172.27.3.1 → pfSense → WAN → Internet ✓
4. Trusted Device → Trusted Device (same VLAN)
172.27.0.100 → 172.27.0.150 (same broadcast domain) ✓
```
### ✗ Blocked Traffic Paths
```
1. Trusted → AI Workload
172.27.0.100 → 172.27.2.50
Firewall Rule: BLOCK ✗
(Prevents lateral movement if AI workload is compromised)
2. Trusted → IoT Device
172.27.0.100 → 172.27.3.100
Firewall Rule: BLOCK ✗
(Prevents IoT compromise affecting trusted devices)
3. AI Workload → Trusted Device (reverse)
172.27.2.50 → 172.27.0.100
Firewall Rule: BLOCK ✗
(Prevents compromised workload from scanning trusted network)
4. IoT → AI Workload
172.27.3.102 → 172.27.2.50
Firewall Rule: BLOCK ✗
(Reduces attack surface between untrusted zones)
```
## VLAN 802.1Q Tagging (Switch-Level)
If using a managed switch with VLAN support:
```
Frame from pfSense Port 4 (Trunk):
┌─────────────────────────────────────────────────────┐
│ Ethernet Header │
├─────────────────────────────────────────────────────┤
│ Destination MAC | Source MAC | 802.1Q Tag | Type │
├─────────────────────────────────────────────────────┤
│ AA:BB:CC:DD:EE | 11:22:33:44:55:66 | VLAN: 2 | IPv4│
├─────────────────────────────────────────────────────┤
│ IPv4 Payload (IP Header + Data) │
└─────────────────────────────────────────────────────┘
When frame arrives at Port 2 (Access, VLAN 2):
├─ Switch removes 802.1Q tag
├─ Delivers untagged frame to device
└─ Device sees: AA:BB:CC:DD:EE → 11:22:33:44:55:66 [IPv4 Data]
```
## Isolation Guarantees
```
┌─────────────────────────────────────────────────────┐
│ VLAN Isolation Mechanisms │
├─────────────────────────────────────────────────────┤
│ │
│ 1. Layer 2 (Link Layer) │
│ └─ VLANs have separate broadcast domains │
│ └─ ARP packets don't cross VLAN boundaries │
│ └─ Broadcast storms are contained │
│ │
│ 2. Layer 3 (Network Layer) │
│ └─ Different subnets per VLAN │
│ └─ Devices can't directly route between VLANs│
│ └─ Must go through Layer 3 gateway (pfSense) │
│ │
│ 3. Firewall Rules (pfSense) │
│ └─ Explicit deny between VLANs (unless allowed) │
│ └─ Stateful inspection prevents spoofing │
│ └─ Rate limiting and IDS possible │
│ │
│ 4. Switch-Level Isolation (if applicable) │
│ └─ 802.1Q VLAN tags ensure switch-level routing │
│ └─ Malformed frames or tag injection blocked │
│ │
└─────────────────────────────────────────────────────┘
```
## Routing Summary
```
Routing Table on pfSense:
Destination Next Hop Interface Metric
────────────────────────────────────────────────────────────
0.0.0.0/0 192.168.1.1 WAN 1
172.27.0.0/24 direct LAN (VLAN 1) 0
172.27.2.0/24 direct OPT1 (VLAN 2) 0
172.27.3.0/24 direct OPT2 (VLAN 3) 0
```
### How Routing Works
1. **Packet from LAN_SECURE to VLAN_AIWORKLOAD:**
- Source: 172.27.0.100, Dest: 172.27.2.50
- pfSense checks routing table
- Destination 172.27.2.0/24 → exists on OPT1
- pfSense checks firewall rule for LAN → OPT1
- Rule says: BLOCK
- Packet is dropped ✗
2. **Packet from VLAN_AIWORKLOAD to Internet:**
- Source: 172.27.2.50, Dest: 8.8.8.8
- pfSense checks routing table
- Destination 8.8.8.8 → matches 0.0.0.0/0 (default route)
- Next hop: WAN gateway (192.168.1.1)
- pfSense checks firewall rule for OPT1 → WAN
- Rule says: ALLOW
- Packet forwarded to WAN ✓
---
**Last Updated:** 2026-04-22

View File

@@ -0,0 +1,11 @@
# Backups Directory
pfSense configuration backups are stored here with timestamped filenames.
**DO NOT delete backup files without understanding the implications.**
Format: `pfsense-config-YYYY-MM-DD_HHMMSS.xml`
All backup files are committed to git, so previous versions can be restored from git history if needed.
For backup automation, see: [scripts/README.md](../scripts/README.md)

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