rpi-provision Declarative first-boot provisioning for Raspberry Pi 5 SD cards Source on GitHub

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.