Specification reference
A specification is a TOML document. Unknown keys are an error, so a typo is reported rather than silently ignored.
Validate one with:
$ rpi-provision validate pi.toml
Secret values
Fields marked secret below take a source, not a literal:
password_hash = { env = "RPI_PASSWORD_HASH" }
password_hash = { file = "secrets/pw.hash" } # relative to the spec file
password_hash = { value = "$6$..." } # discouraged
Exactly one of env, file or value must be present. A bare string is
rejected. Values read from a file have their trailing newline removed.
Override the source at run time with
--set-secret <path>=env:NAME|file:PATH|value:LITERAL.
[meta]
| Key | Type | Default | Notes |
|---|---|---|---|
schema_version |
integer | 1 |
Must equal the version this build understands |
target |
string | "pi5" |
Only pi5 is supported |
description |
string | β | Free text, ignored by the tool |
[system]
| Key | Type | Default | Notes |
|---|---|---|---|
hostname |
string | required | RFC 1123 label: letters, digits and hyphens, 1β63 characters, no leading or trailing hyphen |
timezone |
string | β | e.g. Asia/Tokyo; applied with raspi-config nonint do_change_timezone |
locale |
string | β | e.g. en_US.UTF-8 |
keymap |
string | β | e.g. jp |
[user]
| Key | Type | Default | Notes |
|---|---|---|---|
name |
string | required | Lowercase letters, digits, _ and -; 1β32 characters |
password_hash |
secret | β | A crypt(3) hash. Generate with openssl passwd -6 or mkpasswd --method=yescrypt. A plain-text password is rejected |
authorized_keys |
array of strings | [] |
Full ssh-ed25519 AAAA... comment lines |
authorized_keys_files |
array of strings | [] |
Paths relative to the spec file; comments and blank lines are skipped |
groups |
array of strings | [] |
Supplementary groups. A group that does not exist on the device is skipped with a warning rather than created |
shell |
string | /bin/bash |
Absolute path |
sudo |
string | nopasswd |
nopasswd, password or none. The first two add the account to sudo; nopasswd also installs /etc/sudoers.d/010-rpi-provision-<user> |
At least one of password_hash and authorized_keys must be present.
[ssh]
| Key | Type | Default | Notes |
|---|---|---|---|
enabled |
boolean | true |
false disables and masks ssh.service |
port |
integer | 22 |
1β65535 |
password_authentication |
boolean | false |
When false, at least one authorized key is required |
permit_root_login |
string | no |
no, yes, prohibit-password, forced-commands-only |
extra_config |
array of strings | [] |
Appended verbatim to /etc/ssh/sshd_config.d/10-rpi-provision.conf |
[network]
| Key | Type | Default | Notes |
|---|---|---|---|
wifi_country |
string | β | Two uppercase letters, ISO 3166-1 alpha-2. Required when any [[network.wifi]] is present: the radio stays blocked without a regulatory domain |
[[network.ethernet]] and [[network.wifi]]
Shared keys:
| Key | Type | Default | Notes |
|---|---|---|---|
id |
string | required | Becomes the keyfile name; unique across all connections |
interface |
string | eth0 / wlan0 |
Linux interface name |
method |
string | auto |
auto (DHCP), manual or disabled |
address |
string | β | CIDR, e.g. 192.168.1.50/24. Required when method = "manual", rejected otherwise |
gateway |
string | β | Only with method = "manual". A gateway outside the subnet produces a warning |
dns |
array of strings | [] |
IPv4 addresses |
ignore_auto_dns |
boolean | derived | Defaults to true when dns is set and method = "auto" |
ipv6 |
string | auto |
auto or disabled |
autoconnect |
boolean | true |
Β |
autoconnect_priority |
integer | 0 |
β999 to 999; higher wins |
Ethernet only:
| Key | Type | Notes |
|---|---|---|
mac |
string | Sets cloned-mac-address |
Wi-Fi only:
| Key | Type | Default | Notes |
|---|---|---|---|
ssid |
string | required | 1β32 bytes, no control characters |
security |
string | wpa-psk |
wpa-psk (WPA2), sae (WPA3) or open |
psk |
secret | β | 8β63 character passphrase or 64 hex digits. Required unless security = "open" |
hidden |
boolean | false |
Β |
A statically addressed wired connection is written with may-fail=false, so
network-online.target waits for it. Everything else uses may-fail=true.
[network.usb_gadget]
| Key | Type | Default | Notes |
|---|---|---|---|
enabled |
boolean | false |
Β |
function |
string | ecm |
ecm (Linux, macOS), ncm (higher throughput, also recent Windows) or rndis (older Windows; Microsoft OS descriptors are emitted) |
interface |
string | usb0 |
Β |
address |
string | 10.55.0.1/24 |
The address the Pi takes on the link |
peer_address |
string | β | The hostβs address; must be inside address and differ from it. Informational, used for documentation and validation |
device_mac |
string | derived | Derived from the host name and interface, locally administered |
host_mac |
string | derived | As above |
vendor_id |
integer | 0x1d6b |
Β |
product_id |
integer | 0x0104 |
Β |
manufacturer |
string | Raspberry Pi |
Β |
product |
string | rpi-provision USB gadget |
Β |
serial |
string | the host name | Β |
The link is configured with never-default=true and may-fail=true: it must
never become the default route, and an unplugged cable must not delay boot.
No DHCP server is installed on the device. Give the host a static address in the same subnet, or rely on IPv4 link-local.
[hardware]
| Key | Type | Default | Notes |
|---|---|---|---|
pcie_gen |
integer | β | 1β3; emits dtparam=pciex1_gen= |
usb_max_current |
boolean | false |
usb_max_current_enable=1. Forces the high USB current limit; see Raspberry Pi 5 notes before setting it |
fan_thresholds |
array of integers | [] |
Up to four, ascending, in millidegrees Celsius. Emits dtparam=fan_temp0= upwards, one per entry. 55000 is 55 Β°C |
overlays |
array of strings | [] |
Each becomes dtoverlay=<value> |
dtparams |
array of strings | [] |
Each becomes dtparam=<value> |
config_extra |
array of strings | [] |
Emitted verbatim into the managed block |
cmdline_append |
array of strings | [] |
Whitespace-free tokens appended to cmdline.txt |
cmdline_remove |
array of strings | [] |
Exact tokens removed from cmdline.txt |
[hardware.uart]
| Key | Type | Default | Notes |
|---|---|---|---|
enabled |
boolean | false |
dtparam=uart0=on: UART0 on GPIO 14/15, /dev/ttyAMA0 |
console |
boolean | false |
Attach a kernel console to /dev/ttyAMA0. Requires enabled |
baudrate |
integer | 115200 |
Used for the console |
debug_connector |
boolean | false |
enable_uart=1: the dedicated three-pin debug connector, /dev/ttyAMA10 |
With console = false and debug_connector = false, the stock
console=serial0,115200 is removed so that GPIO 14/15 is a plain data port.
[hardware.i2c], [hardware.spi], [hardware.one_wire]
| Section | Key | Type | Default |
|---|---|---|---|
i2c |
enabled |
boolean | false |
i2c |
baudrate |
integer | 100000 (10 000β1 000 000) |
spi |
enabled |
boolean | false |
one_wire |
enabled |
boolean | false |
one_wire |
gpio |
integer | 4 (0β27) |
Enabling a bus does not grant the account access to it; add i2c, spi or
gpio to user.groups for that.
[[files]]
Files copied onto the root filesystem during the first boot, in addition to everything the tool generates.
Repeat the [[files]] header for each entry. It is a TOML array of
tables, so as many as you like may be declared; the double brackets are what
make it a list rather than a single table.
| Key | Type | Default | Notes |
|---|---|---|---|
source |
string | required | A file or a directory, relative to the specification file. A directory is copied recursively, keeping its layout |
destination |
string | required | Absolute path on the device. No . or .., no trailing /. For a directory source this is the directory the tree lands in |
mode |
string | "0644" |
Octal, quoted. For a directory source it applies to every file below it |
owner |
string | root |
Β |
group |
string | root |
Β |
[[files]]
source = "files/motd"
destination = "/etc/motd"
[[files]]
source = "files/vimrc"
destination = "/etc/vim/vimrc.local"
[[files]]
source = "files/scripts" # a directory, copied recursively
destination = "/opt/scripts"
mode = "0755"
owner = "engineer"
group = "engineer"
The contents are staged on the boot partition and installed by step
30-payload, so they are in place before the commands in [[run]].
Entries are installed in order of destination rather than in the order they are written. Nothing depends on the order β each entry writes exactly one path, and two entries may not write the same one β so the two orders are interchangeable in practice.
Two entries may not write the same destination, and a destination that
rpi-provision generates itself β a NetworkManager profile, the sshd drop-in,
the sudo rule, the gadget script or unit β is rejected rather than silently
overwritten by one or the other.
The boot partition is small and FAT formatted, and everything declared here has to fit on it alongside the payload. Fetching a large archive on the device is the better shape; a specification whose transfers exceed 64 MiB is loaded with a warning.
[[run]]
Commands run at the end of the first boot, as step 80-run. Everything
else β the account, the transferred files, networking β is in place by then.
Repeat the [[run]] header for each command, as with [[files]]. They
run in the order they are written, top to bottom.
| Key | Type | Default | Notes |
|---|---|---|---|
command |
string | required | A single line, run by /bin/sh on the device |
description |
string | β | Printed to the log before the command runs |
ignore_failure |
boolean | false |
Report a failure and carry on instead of stopping the step |
[[run]]
description = "refresh the package index"
command = "apt-get update"
ignore_failure = true
[[run]]
description = "install the editors this bench expects"
command = "apt-get install -y --no-install-recommends vim tmux"
[[run]]
command = "systemctl enable my-daemon"
command is the one place a specification contributes code rather than a
value: it is emitted into the generated script verbatim, not quoted, because
it is meant to be interpreted by the shell. Everything else a specification
provides is quoted before it reaches a script.
It must be a single line. Anything longer belongs in a script transferred with
[[files]] and invoked here, which also keeps it under review as a file
rather than as a string inside a TOML document:
[[files]]
source = "files/setup.sh"
destination = "/usr/local/sbin/setup.sh"
mode = "0755"
[[run]]
command = "/usr/local/sbin/setup.sh"
Unless ignore_failure is set, a failing command stops the step, and the
first-boot runner records status=failed. It still removes its own hooks from
cmdline.txt, so a failing command cannot produce a boot loop.
[provisioning]
| Key | Type | Default | Notes |
|---|---|---|---|
boot_mount |
string | /boot/firmware |
Where the boot partition is mounted on the device |
runner_dir |
string | rpi-provision |
Directory below the boot partition holding the payload |
wipe_payload |
boolean | true |
Delete the payload after a successful first boot. Leaving it in place keeps the Wi-Fi key and password hash readable by anyone with the card |
reboot_after |
boolean | true |
Β |
log_path |
string | /var/log/rpi-provision.log |
Β |
Command line overrides
--set <path>=<value> assigns into the parsed document before validation.
The value is parsed as a TOML scalar when possible and as a string otherwise,
so --set ssh.port=2222 gives an integer and --set system.hostname=dev-pi-07
gives a string. Array elements are indexed:
--set 'network.ethernet[0].address=192.168.1.57/24'
Missing intermediate tables are created; a missing array element is an error.
Overrides change the specification digest, which is recorded in config.txt,
in firstrun.sh and in /var/lib/rpi-provision/status on the device.