Main repository for working with the DMOD ecosystem
Downloads a Raspberry Pi OS image and writes it to an SD card (Linux).
source setup_venv.shThis creates (if needed) a .venv virtualenv in the repo root, installs the
dependencies from requirements.txt, and activates it in the current shell.
# List connected SD cards / removable USB drives
python3 prepare_rpi_sd.py --list-devices
# Download the default image (Raspberry Pi OS Lite 64-bit), auto-pick a
# customize tier that fits the target card's size (see below), and flash it,
# auto-detecting the SD card (works when exactly one removable device is
# connected)
sudo python3 prepare_rpi_sd.py
# Headless: configure Wi-Fi + a login user too (mini always applies these
# immediately; here paired with the default auto tier)
sudo python3 prepare_rpi_sd.py \
--wifi-ssid MyNetwork --wifi-password mywifipass --wifi-country PL \
--pi-username pi --pi-password raspberry
# Force a specific tier instead of auto-detecting one from the card size
sudo python3 prepare_rpi_sd.py --customize full --pi-username pi --pi-password raspberry
sudo python3 prepare_rpi_sd.py --customize basic --pi-username pi --pi-password raspberry
sudo python3 prepare_rpi_sd.py --customize mini --pi-username pi --pi-password raspberry
# Skip customization entirely - just download and flash a plain image
sudo python3 prepare_rpi_sd.py --customize none
# Pick a different OS variant and an explicit device
sudo python3 prepare_rpi_sd.py --os full64 --device /dev/sdb --customize full
# Use a custom image (.img, .img.xz or .img.zip)
sudo python3 prepare_rpi_sd.py --image-url https://example.com/custom.img.xz
# Only download the image, without customizing or flashing it
python3 prepare_rpi_sd.py --download-only
# Use your own customize script instead of one of the built-in tiers
sudo python3 prepare_rpi_sd.py --customize-script ./customize-scripts/example.shBefore flashing, a shell script can be run inside the image's root filesystem - useful for preinstalling packages, enabling SSH, dropping config files, building and installing software, etc.
There are three built-in tiers, each building on the previous one:
| Tier | Script | What it does |
|---|---|---|
mini |
customize-scripts/mini.sh | Enables SSH, and optionally configures Wi-Fi and/or a login user (see --wifi-*/--pi-* below). Fits any card, applies immediately. |
basic |
customize-scripts/basic.sh | mini + compiles and installs dmod (native, no embedded toolchains). |
full |
customize-scripts/full.sh | basic + embedded toolchains (arm-none-eabi, Xtensa/ESP-IDF), Renode, dmffs, Node.js + Claude Code CLI - see details below. |
--customize picks the tier (mini/basic/full/none). The default,
auto, picks the richest tier that comfortably fits the target SD card:
- card size < 8 GB →
mini - 8-64 GB →
basic(e.g. a 16 GB card) - ≥ 64 GB →
full
auto needs a target device to size, so with --download-only it's a no-op
(nothing is customized) unless --customize is set explicitly. Use
--customize-script instead to run your own script in place of a tier.
Building dmod (let alone the full embedded toolchain) is heavy - lots of
downloads and a real compile. Doing that here, in a loop-mounted image, under
qemu-user-static emulation (needed whenever the image's architecture
doesn't match the machine running prepare_rpi_sd.py, e.g. flashing an
aarch64 image from an x86_64 desktop) is slow and prone to network flakiness
inside the emulated environment.
So only mini's own work (SSH, Wi-Fi, user account) runs immediately, in the
chroot. For basic/full, mini.sh also stages the dmod/dmod-boot
source and the customize-scripts/ themselves onto the image, and installs a
oneshot systemd service (dmod-first-boot.service) that runs basic.sh or
full.sh natively, the first time the Pi actually boots - after
network-online.target, so it has real internet access. This is both much
faster (real ARM CPU, no emulation) and more reliable (real network stack).
After first boot, check on it over SSH with:
journalctl -u dmod-first-boot -f
# or
tail -f /var/log/dmod-first-boot.logIt disables itself (and drops a /var/lib/dmod-first-boot.done marker) once
it's run, successfully or not - it won't re-run on later boots.
Since basic/full's real work now happens after the Pi boots for real (and
even for mini, you need a way to actually reach a headless Pi), you'll
generally want to configure network + a login account:
--wifi-ssid SSID --wifi-country CC [--wifi-password PASS]- writes a NetworkManager connection profile directly (Raspberry Pi OS's network stack since Bookworm) and sets the Wi-Fi regulatory domain viaraspi-config nonint do_wifi_country(needed for the radio to come up in most regions). Omit--wifi-passwordfor an open network.--wifi-countryis a 2-letter ISO code (e.g.PL,US,GB).--pi-username USER --pi-password PASS- creates the account withuseradd/chpasswd(in thesudogroup). Recent Raspberry Pi OS images ship no default user at all, so without this you may not be able to log in even with SSH enabled -prepare_rpi_sd.pyprints a warning in that case.
Both are optional and independent of --customize - e.g. --customize none --wifi-ssid ... doesn't apply (customize none skips mini entirely); to just
set up Wi-Fi/user without the dmod build, use --customize mini explicitly.
Under the hood, customize_image.sh does what people used
to do by hand: attach the .img file as a loop device (losetup -P), mount
its boot and root partitions, bind-mount /dev, /proc, /sys, and
chroot into it to run the script as root. It cleans up (unmount, detach the
loop device) automatically, even on failure. It also bind-mounts this repo
read-only at /mnt/host-repo inside the chroot, so a script can use local
sources (e.g. modules/dmod) or invoke sibling customize-scripts/*.sh
files without needing git credentials inside the chroot.
Notes:
- Requires root (
sudo), and forces the image to be fully decompressed to a local.imgfile first (loop-mounting a compressed.img.xzisn't possible) - seeimages/. - The working image is grown a little (
customize_image.sh --grow-mb 512: resizes the partition withpartedand the filesystem withresize2fsbefore mounting) since shipped Raspberry Pi OS images leave almost no free space until their first real boot expands the filesystem -mini's own files plus a stageddmod/dmod-bootsource copy easily fit in that. Requirespartedande2fsprogs(resize2fs,e2fsck) on the host runningprepare_rpi_sd.py. - If you're customizing an ARM image on an x86_64 host, you'll need
qemu-user-staticinstalled (sudo apt install qemu-user-static binfmt-support) so the chroot can execute the image's binaries (needed formini's own work -useradd,raspi-config, etc. - the deferredbasic/fullbuild itself runs later, natively, on the Pi). - See customize-scripts/example.sh for a
template of your own (not part of the mini/basic/full chain) - copy it and
adapt it to your needs, then point
--customize-scriptat your copy. - You can also run any of these standalone, without going through
prepare_rpi_sd.py, e.g.:sudo ./customize_image.sh path/to/image.img customize-scripts/mini.sh -- --wifi-ssid ... --wifi-country PL --defer-tier basic.
Turns the SD card into a full DMOD development environment - equivalent to
chocotechnologies/dmod:1.0.4 plus the Renode/dmffs tooling from
dmod-boot's dev image and the Claude Code CLI - so you can build and debug
dmod and its modules directly on the Pi.
On top of what basic.sh already does (see its own package list, mirroring
modules/dmod/scripts/setup-linux-env.sh), it installs (mirroring
modules/dmod/Docker/Dockerfile.env, modules/dmboot/docker/Dockerfile.env,
modules/dmboot/scripts/setup-linux-env.sh and
modules/dmod/Docker/Dockerfile.claude):
- the
arm-none-eabiand Xtensa (ESP32) toolchains, plus ESP-IDF (which also pulls in the Espressif OpenOCD fork needed for ESP32-S3 JTAG) - both
aarch64-linux-gnuandarm-linux-gnueabihfcross-compilers (whichever one matches the image is already installed bybasic.sh) - Renode (x86_64 host only - no arm64 package exists; real hardware debugging via OpenOCD works on any host)
- Node.js + the Claude Code CLI
- dmffs, via
dmf-get make_dmffs --type dmf(best-effort: a network/registry hiccup here is logged as a warning rather than failing the whole build) - an editable copy of
modules/dmod-bootunder/opt/dmod-src/(not built - it's firmware, built per-target once you're on the board)
basic.sh builds dmod itself, from the staged source at /opt/dmod-src/dmod
(originally copied from this repo's modules/dmod checkout by mini.sh),
with -DDMOD_TOOLS_NAME set to the matching configs/arch/... entry for the
Pi's own architecture (e.g. arch/aarch64/cortex-a53 for a 64-bit Pi 3B),
giving you dmf-get, dmfc, etc. under /usr/local. Both scripts also work
if run directly in the image-prep chroot instead of at first boot (they fall
back to copying straight from the /mnt/host-repo bind-mount when it's
present); toolchains go to /opt/dmod-tools/ (added to PATH for every
login shell via /etc/profile.d/dmod-dev.sh).
| Value | Image | Description |
|---|---|---|
lite64 |
Raspberry Pi OS Lite, 64-bit | No desktop environment, boots to a console. Default. Recommended for headless setups (servers, IoT). Requires a 64-bit capable Pi (Pi 3 or newer). |
full64 |
Raspberry Pi OS, 64-bit | Full desktop environment with bundled apps. Requires a 64-bit capable Pi (Pi 3 or newer). |
lite32 |
Raspberry Pi OS Lite, 32-bit | No desktop environment, boots to a console. For older/32-bit-only boards (Pi 1, Pi 2, Pi Zero non-W2). |
full32 |
Raspberry Pi OS, 32-bit | Full desktop environment. For older/32-bit-only boards (Pi 1, Pi 2, Pi Zero non-W2). |
Downloaded images go into the images/ directory (ignored by git).
Before writing, the script asks for confirmation (unless -y/--yes is
used) and refuses to write to a device that looks like the system disk.