Skip to content

Firewalla Router Migration

Runbook for replacing the primary Firewalla router with a new unit using the same external /extdata USB disk.

Scope and Assumptions

  • Source router goes offline before target router is brought online.
  • The /extdata USB disk is physically moved from source to target.
  • Router remains at 192.168.20.1 and inventory host router continues to resolve/reach correctly.
  • Router services are managed by ansible/router-playbook.yml.

Known-Good Baseline (Captured 2026-02-21)

Capture these values before cutover and compare after migration.

Check Current Value
Capture time (local) 2026-02-21 09:09:40 EST
/extdata mount /dev/sda1 mounted as ext4 at /extdata
USB label firewalla_extdat
Boot hooks present 0000-a-remount-root.sh, 0000-ensure-dhcpcd6-duid.sh, 0000-mount-extdata.sh, 0001-ipv6-ula.sh, 0002-dhcp6-vzw-fix.sh, 0050-start-docker.sh, 0100-install-docker-compose.sh, 0125-start-alloy.sh, 0150-start-tailscale.sh, 0151-start-router-hosts.sh, 0200-start-vault-unseal.sh
Service health docker-compose@router-hosts, docker-compose@alloy, kopia-backup.timer are active
Container health tailscale and vault-unseal are running
Vault DNS via router DNS (@192.168.20.1) vault*.fzymgc.house -> 192.168.20.145
Backup source paths /extdata/tailscale-data, /extdata/router-hosts/data, /extdata/router-hosts/hosts
Latest snapshot: /extdata/tailscale-data 2026-02-21T13:30:08.756177782Z
Latest snapshot: /extdata/router-hosts/data 2026-02-21T13:30:16.865653328Z
Latest snapshot: /extdata/router-hosts/hosts 2026-02-21T13:30:24.465340575Z
Kopia timer (next / last trigger) Sun 2026-02-22 03:04:12 EST / Sat 2026-02-21 03:06:38 EST

Phase 1: Pre-Cutover (Source Router Still Online)

  1. Confirm Vault secrets exist:
vault kv get secret/fzymgc-house/infrastructure/router/dhcpv6
vault kv get secret/fzymgc-house/infrastructure/router/kopia-r2
  1. Record current IPv6 global addresses:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a "ip -6 addr show | grep -E 'inet6.*global' || true" -b
  1. Run a final manual backup:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "/extdata/tools/kopia-backup" -b
  1. Verify backup source coverage:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a \
  "export KOPIA_CONFIG_PATH=/extdata/kopia/config/repository.config; /extdata/tools/kopia snapshot list --json | python3 -c \"import sys,json; print('\n'.join(sorted({x.get('source',{}).get('path','') for x in json.load(sys.stdin)})))\"" -b
  1. Save current runbook-baseline outputs for comparison after cutover.

Phase 2: Physical Cutover

  1. Power off source Firewalla.
  2. Move WAN/LAN cabling to target Firewalla.
  3. Move external USB disk to target Firewalla.
  4. Power on target Firewalla.
  5. Complete minimum Firewalla app onboarding needed for:
  6. LAN gateway function on 192.168.20.1
  7. SSH access as pi

Phase 3: Bootstrap Access on Target

  1. Remove stale SSH host keys from workstation:
ssh-keygen -R router
ssh-keygen -R 192.168.20.1
  1. Confirm SSH and Ansible reachability:
ssh router "hostname"
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.ping
  1. Verify USB disk mounted as /extdata:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a "lsblk -o NAME,LABEL,FSTYPE,MOUNTPOINT | grep -E 'sda|extdata|firewalla_extdat'" -b
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a "mount | grep '/extdata'" -b

Phase 4: DNS Bootstrap Guard (Before Full Ansible Run)

Because DNS interception can hide resolver behavior on this network, explicitly validate Vault hostnames from the target router.

  1. Check Vault DNS resolution via local router DNS:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a \
  "for h in vault.fzymgc.house vault-0.fzymgc.house vault-1.fzymgc.house vault-2.fzymgc.house; do echo -n \"$h \"; dig +short @192.168.20.1 $h | paste -sd, -; done" -b
  1. If any entry is missing, add temporary bootstrap DNS records and reload firerouter_dns:
ssh router "cat > /home/pi/.firewalla/config/dnsmasq_local/00-bootstrap-vault.conf <<'EOF'
address=/vault.fzymgc.house/192.168.20.145
address=/vault-0.fzymgc.house/192.168.20.145
address=/vault-1.fzymgc.house/192.168.20.145
address=/vault-2.fzymgc.house/192.168.20.145
EOF
sudo killall -HUP firerouter_dns"

Phase 5: Converge Configuration with Ansible

  1. Export VAULT_TOKEN in the same shell as ansible-playbook:
export VAULT_TOKEN="$(vault token lookup -format=json | jq -r '.data.id')"
  1. Run full router convergence:
cd ansible
ansible-playbook -i inventory/hosts.yml router-playbook.yml --limit router
  1. Confirm play recap has failed=0.

Phase 6: Post-Cutover Validation

  1. Validate key services:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "systemctl is-active docker-compose@router-hosts docker-compose@alloy kopia-backup.timer" -b
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "docker inspect tailscale --format {{\"{{\"}}.State.Status{{\"}}\"}}" -b
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "docker inspect vault-unseal --format {{\"{{\"}}.State.Status{{\"}}\"}}" -b
  1. Validate DNS integration:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "cat /home/pi/.firewalla/config/dnsmasq_local/local-hosts.conf" -b
dig @192.168.20.1 vault.fzymgc.house +short
  1. Validate backup operation on target:
cd ansible
ansible router -i inventory/hosts.yml -m ansible.builtin.command -a "/extdata/tools/kopia-backup" -b
ansible router -i inventory/hosts.yml -m ansible.builtin.shell -a \
  "export KOPIA_CONFIG_PATH=/extdata/kopia/config/repository.config; /extdata/tools/kopia snapshot list --json | python3 -c \"import sys,json; print('\n'.join(sorted({x.get('source',{}).get('path','') for x in json.load(sys.stdin)})))\"" -b
  1. Compare IPv6 global addresses/prefix behavior to pre-cutover capture.

Rollback

If routing, DNS, or service health cannot be recovered quickly:

  1. Power off target Firewalla.
  2. Reconnect source Firewalla with original WAN/LAN and USB disk.
  3. Power on source Firewalla and verify internet/DNS.
  4. Investigate and retry cutover in a new maintenance window.

References

  • ansible/router-playbook.yml
  • ansible/roles/router-kopia-backup/defaults/main.yml
  • docs/operations/router-hosts.md
  • docs/reference/secrets.md