Wheelhouse docs

Coming from OPNsense#

tools/opnsense-import.py turns an OPNsense config.xml into VyOS set commands plus a report of what it did. The point is a migration you can read before you run it: nothing is applied by the importer, and the commands are in the order a person would type them. From there you either build a seed that installs the replacement machine unattended, or you apply the commands to a router you installed by hand. Afterwards the same tool compares the running router against the configuration it was supposed to get.

Six steps, in order.

1. Export the configuration#

Take a config.xml off the OPNsense box. The importer only reads it; it never talks to the router.

2. Import it#

bash
tools/opnsense-import.py config.xml --map lan=eth0,wan=eth1 --out ./out

That writes two files into ./out: config.commands, the set lines, and report.md, what came across and what did not.

--map matters more than any other flag. FreeBSD calls a port igc0 and Linux calls it eth0, and nothing in the file says which cable is in which socket. Pass the mapping you want, either by role (lan=eth0) or by device (igc0=eth0). Without it, ports are taken in the order the router lists them, which is a coin toss you will pay for later.

FlagWhat it does
--map lan=eth0,wan=eth1interface mapping, by role or by device
--hostname <name>override the host name from the file
--out <dir>write config.commands and report.md there; without it, the commands go to standard output
--answers <file>also write a Wheelhouse installer answers file
--no-firewall, --no-dhcp, --no-dns, --no-qosskip that part of the translation
--check-against <file>compare a live router's commands with the ones this import expects

What comes across#

The host name, domain, resolvers and time zone; interface addresses, static and DHCP; static routes, resolving OPNsense gateway names to their next hop; outbound NAT and every port forward, with host and port aliases resolved; a default-drop firewall that admits exactly what those forwards need; the DHCP server with its pool, options, PXE handoff and every reservation; DNS forwarding with its static host entries; and the WAN egress shaper.

3. Read the report before you boot anything#

report.md names what could not come across rather than dropping it silently: FreeBSD-only plugins, UPnP, wildcard DNS records, per-reservation DNS servers, and anything the router had disabled.

Two things in particular tend to matter to an OPNsense operator:

  • There is no UPnP or NAT-PMP answer in Wheelhouse. Not a translation gap — the feature does not exist here. Anything on your network that opened its own ports will stop being able to.
  • A 1:1 NAT editor exists as a read-only table. The nat static table reads rules made elsewhere; the editor is switched off, because committing nat static through this build's VyOS HTTP API stops the API process. The CLI accepts the same lines.

4. Build a seed#

The seed is what makes the replacement machine install itself with your configuration already in it.

bash
tools/wheelhouse-seed.py --opnsense config.xml --map lan=eth0,wan=eth1 \
    --admin-password-file admin.pw --console-password-file console.pw \
    --out seed.iso

That runs the importer for you, puts config.commands on the seed, copies the report on as import-report.md, and writes a cloud-config that installs the machine and restores the configuration. It keeps the router's own host name and time zone from the imported configuration unless you override them.

It also decides who writes the firewall, and says so in the seed in as many words: if the imported configuration brings a default-drop ruleset, the installer's own firewall is turned off, because two writers numbering the same rules differently produces a configuration the router refuses. If nothing came across, the installer writes its default-deny ruleset instead.

--include-source puts the config.xml on the seed as opnsense-config.xml, so the machine can check itself against the router it replaced months later with nothing else to fetch.

5. Install#

Write the seed to a stick or attach it as a second disk, boot the Wheelhouse image with it present, and the machine installs itself. Installing without a keyboard has the seed model in full: where the installer looks, the once-per-boot rule, and the two guards that stop a seed erasing a disk twice.

The configuration is applied after the passwords are set, so a restored firewall can be as closed as you like without locking the installer out of the agent.

6. Check the new router against the old one#

On a running machine the importer ships as wheelhouse-opnsense-import, and --check-against compares a live router with the configuration it should have:

bash
show configuration commands > /tmp/live
wheelhouse-opnsense-import config.xml --map lan=eth0,wan=eth1 --check-against /tmp/live

It prints every command the import expects and the router is missing, and exits non-zero if there are any — which makes it a reasonable thing to run from a cron job after a migration until you trust it. Quoting is ignored, because a router quotes the values it prints, as are the settings VyOS writes for itself on every interface.

show configuration commands is a shell function of the operational CLI, so over SSH it needs the wrapper:

bash
/opt/vyatta/bin/vyatta-op-cmd-wrapper show configuration commands > /tmp/live

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

What to expect to be different#

Wheelhouse is not an OPNsense clone with a different logo, and the vocabulary differs. Nothing on any page applies itself: an editor stages set lines into a working set, and one Commit Bar commits the lot. What OPNsense calls a plugin is an app here, and installing one declares a container in the router's configuration tree — so it is a diff you review, a commit you can roll back, and state that survives an image upgrade.

The two things most likely to change how you work:

  • IPv6 is configurable but not observable. No v6 route view, no v6 concept in the uplink model, and the firewall page is v4-first. The installer does write a v6 ruleset.
  • There is no packet capture in the UI. POST /api/capture answers 501 and hands back the monitor traffic command to run at the console.

See also#


Checked against tools/opnsense-import.py, tools/wheelhouse-seed.py, tools/tests/test_opnsense_import.py, docs/unattended-install.md, docs/apps.md, PLAN.md, README.md.

Updated 2026-09-02 migration opnsense importer seed