Seed cloud-config keys#
A seed is either an answer file or a cloud-config document
with a wheelhouse: mapping — the shape people already write when they seed machines
with cloud-init. Either way, seed-to-answers.py turns it into KEY=VALUE lines before
the installer sees it, so the mapping below is the whole of what a cloud-config seed can
say.
#cloud-config
wheelhouse:
disk: auto
hostname: edge
timezone: America/Los_Angeles
wan:
interface: eth1
addressing: dhcp
lan:
interface: eth0
address: 10.0.0.1/16
dhcp_server: true
nat: true
firewall: true
management_source: 203.0.113.0/24
passwords:
admin: "the web UI password"
console: "the console password"
config:
commands: config.commands
ssh_authorized_keys:
- ssh-ed25519 AAAA… you@laptop
reboot: true
on_error: haltThe mapping, key by key#
| Cloud-config key | Becomes | Default |
|---|---|---|
disk | DISK | auto — the one disk that is not the boot medium. Refuses when there is none or more than one. |
hostname | HOSTNAME | wheelhouse |
timezone | TIMEZONE | UTC |
wan.interface | WAN_IFACE | empty, which skips the WAN |
wan.addressing | WAN_ADDRESSING | dhcp |
wan.address | WAN_ADDRESS | empty |
wan.gateway | WAN_GATEWAY | empty |
wan.dns | WAN_DNS | empty. A list is joined with commas. |
lan.interface | LAN_IFACE | empty, which skips the LAN |
lan.address | LAN_ADDRESS | empty |
lan.dhcp_server | LAN_DHCP | yes if a LAN interface is named, otherwise no |
firewall (or lan.firewall) | FIREWALL | no when the seed carries a configuration, otherwise yes |
lan.nat | NAT | yes if a LAN interface is named, otherwise no |
management_source | MGMT_SOURCE | empty |
passwords.admin | ADMIN_PASSWORD | required |
passwords.console | CONSOLE_PASSWORD | required |
reboot | REBOOT | yes |
poweroff | POWEROFF | no |
keep_previous | KEEP_PREVIOUS | no |
overwrite | OVERWRITE | no |
on_error | ON_ERROR | halt |
config.commands | CONFIG_COMMANDS | — |
config.boot, or config.file | CONFIG | — |
ssh_authorized_keys | SSH_KEYS | — |
Any key whose value comes out empty is left out of the answer file entirely, so the installer's own default applies.
Booleans accept yes, true, on and 1 — anything else is false.
Two defaults that are not what they look like#
firewall follows the WAN, not the LAN. A seed with a WAN and no LAN describes a
router facing the internet with nothing behind it, which is the one that most needs a
closed front door. lan: {firewall: …} still works, because seeds already written say it
that way, and the top-level firewall: wins when both are present.
A seed that carries a configuration gets no firewall by default. When
config.commands, config.boot or config.file is set, that configuration decides what
the firewall is; the installer writing its own default-deny ruleset first would leave two
writers numbering the same rules differently. Set firewall: true explicitly if you
want both.
Payloads#
config.commands, config.boot and ssh_authorized_keys may each be given three ways:
| Written as | What happens |
|---|---|
| A file name | Resolved next to the seed and copied into /run/wheelhouse-seed/, so the installer can still read it after the medium is unmounted. |
| Inline text (any value containing a newline) | Written into the work directory as a file. |
| A URL | Passed through unchanged; the installer fetches it. http:// and ftp:// are refused unless the machine was booted with wheelhouse.insecure=1. |
A named file that is not beside the seed produces a warning on stderr —
seed names X, which is not next to the seed — and the key is dropped rather than
guessed at.
ssh_authorized_keys accepts a list or a single string, and is written out as an
authorized_keys file installed on both console accounts.
What config.commands and config.boot are#
| Key | Content |
|---|---|
config.commands | A file of set lines — the same lines show configuration commands prints, and what the OPNsense importer writes. Refused if it holds no set or delete line. |
config.boot (or config.file) | A whole configuration file, which the router loads and validates itself. |
Both are 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.
The YAML the installer can parse#
The seed is parsed with PyYAML when the image has it, and with a small subset parser when it does not — so a seed still works on an image without PyYAML rather than failing at the worst possible moment. The subset is exactly what a Wheelhouse seed uses:
- nested mappings by indentation;
- inline mappings,
{a: b, c: d}; - inline and block lists of scalars;
- block scalars introduced with
|; true/yesandfalse/noas booleans;- quoted scalars, where a
#is just a character; - unquoted scalars, which end at the first whitespace-then-
#, the way YAML reads them.
Anchors, aliases, multi-document streams and flow-style nesting beyond one level are not in the subset. If your seed needs those, make sure the image has PyYAML — or write the answer-file form, which has no parser at all.
Plain answers instead of YAML#
A seed that does not begin #cloud-config and contains at least one ^[A-Z_]+= line is
passed straight through as an answer file. Both forms work on the same medium and in the
same places.
Checking a seed before you boot with it#
# What the seed converts to:
python3 packaging/seed-to-answers.py user-data /tmp/work
# What those answers would do to the router:
sudo wheelhouse-install --answers /tmp/work/answers.conf --commandsSee also#
- Answer-file keys — what each converted key does.
- Where the installer looks for a seed
wheelhouse-seed.py— writes this document for you.opnsense-import.py- Unattended install
- Build a seed
Checked against#
packaging/seed-to-answers.py (main, as_bool,
load_yaml, copy_payload),
packaging/wheelhouse-install (fetch_payload,
apply_config, apply_ssh_keys),
packaging/wheelhouse-autoinstall,
tools/wheelhouse-seed.py,
docs/unattended-install.md.