Wheelhouse docs

Check a running router against the configuration it should have#

You will end up with a command that answers one question: does this router still have everything the OPNsense configuration said it should? It prints every line the import expects and the router does not have, prints anything set beyond the import in the areas the import covers, and exits non-zero when something is missing — which makes it something you can run from a scheduled task after a migration until you trust the result.

Before you start#

  • The original OPNsense config.xml. If you built the seed with --include-source it is already on the machine's seed medium as opnsense-config.xml.
  • Shell access to the router: the console, or SSH if you have turned it on.
  • The same --map you used for the import. A different mapping compares against a different router.

Step 1 — Get the running configuration as set commands#

On the router:

bash
show configuration commands > /tmp/live

Over SSH, show configuration commands is a shell function of the operational CLI rather than a program, so it needs the wrapper:

bash
ssh wheelhouse@<router> '/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands' > live.txt

You can also take it through the agent's API, which does not need SSH at all:

bash
R=https://<router>:8443
T=wh_...                                    # an API token with the viewer role
curl -sk -H "Authorization: Bearer $T" "$R/api/config/commands" > live.json

Step 2 — Compare#

bash
tools/opnsense-import.py config.xml --map lan=eth0,wan=eth1 --check-against /tmp/live
what it prints
missing: set nat destination rule 150 destination port 8443
missing: set nat destination rule 150 translation address 192.0.2.20
also set: set service dns forwarding name-server 1.1.1.1
147 expected, 2 missing, 1 set beyond the import in the areas it covers

Two kinds of line:

missing: — the import expects this and the router does not have it. Either somebody deleted it, or it was never applied. This is what makes the command exit 1.

also set: — the router has this and the import does not expect it, within an area the import has an opinion about. That is usually fine and often correct: a resolver added later, a firewall rule you wrote after the migration. It is there so a change you did not make gets noticed.

The comparison is deliberately loose in two places, so that it reports differences rather than punctuation:

  • Quoting is ignored. A router quotes the values it prints (inet '192.0.2.10'); the import quotes only what has to be quoted. Both sides are compared as words.
  • Settings VyOS writes for itself are ignored. Anything whose last two words include offload, hw-id, mtu, duplex or speed appears on every interface on a running router and never in an import.

And it only reports extras in areas the import covers, worked out from the first two words of every expected line. A router has plenty of settings the imported file never mentions; listing all of them would bury the two lines that matter.

Step 3 — On the router itself#

On a machine installed from a seed built with --include-source, the whole check is two commands with nothing to fetch:

bash
sudo mount /dev/sr1 /mnt
/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands > /tmp/live
sudo wheelhouse-opnsense-import /mnt/opnsense-config.xml --map lan=eth1,wan=eth0 \
    --hostname "$(hostname)" --check-against /tmp/live

--hostname "$(hostname)" keeps the host-name line from being reported as missing when you renamed the box during the install.

Step 4 — Run it on a schedule, for a while#

The command exits non-zero when something is missing, so it works as a check. Put the config.xml somewhere readable only by root, and use a scheduled task:

set system task-scheduler task migration-check crontab-spec '17 6 * * *'
set system task-scheduler task migration-check executable path /config/scripts/migration-check.sh
/config/scripts/migration-check.sh
#!/bin/sh
/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands > /tmp/live
wheelhouse-opnsense-import /config/migration/opnsense-config.xml \
    --map lan=eth0,wan=eth1 --hostname "$(hostname)" --check-against /tmp/live \
    || logger -t migration-check "the router no longer matches the imported configuration"

/config/scripts is on the persistent partition, so the script survives an image upgrade. More on this in Run something on a schedule.

Turn it off once the migration has settled. A check that has passed every day for two months and will pass for ever is noise, and the comparison drifts as you make real changes the old router never had.

What this check does not tell you#

  • Whether it works. It compares configuration to configuration. A port forward can be present and still not reachable because the provider blocks the port. For that, publish a service and test from outside.
  • Anything on the Not translated list. The import never expected those lines, so their absence is not a difference. That list is the report's, and you work through it by hand.
  • The state of anything that is not configuration. Leases, sessions, routes the kernel installed, container images. Those are on the dashboard and the pages that own them.

Undoing it#

Nothing to undo — the check reads and prints. Delete the scheduled task when you are done with it:

delete system task-scheduler task migration-check

See also#


Checked against tools/opnsense-import.py · docs/unattended-install.md · ui/src/pages/SystemSettings.tsx · docs/security.md

Updated 2026-09-02 migration opnsense verification