Skip to content

Pi Image Setup Guide

Jasper van Loenen edited this page May 15, 2026 · 29 revisions

Little Printer Zigbee Bridge - Pi Image Setup Guide

This page describes how I create the image file used on the Raspberry Pi. Regular users don't need this, but I've put this here for reference and because others might find this useful.

All steps are performed on a Linux machine with the Raspberry Pi SD card mounted via a card reader. The root partition is assumed to be mounted at /media/$USER/rootfs and the boot partition at /media/$USER/bootfs.

Requirements: This guide targets Raspberry Pi OS Trixie Lite. Both 32-bit and 64-bit are supported. Use the 32-bit image for the Pi Zero W, and the 64-bit image for the Pi Zero 2W, Pi 3, Pi 4, or Pi 5.


Support

Did you find this tool useful? Feel free to support my open source tools - especially when using them commercially:

GitHub Sponsor BMC

How it works

Trixie includes cloud-init, which handles first-boot configuration via two files on the boot partition:

  • user-data - creates the user, sets the hostname, installs Python dependencies, and starts our bridge service
  • network-config - configures WiFi

Users only need to edit network-config to enter their WiFi credentials before first boot. Both files live on the FAT32 boot partition, so they are editable on Windows, Mac, and Linux without needing root.


1. Find The SD Card Mount Points

lsblk

Identify the device (e.g. /dev/sda) and note where the partitions are mounted, for example:

  • Root partition -> /media/$USER/rootfs
  • Boot partition -> /media/$USER/bootfs

If the partitions are not mounted yet:

sudo mkdir -p /media/$USER/rootfs /media/$USER/bootfs
sudo mount /dev/sda2 /media/$USER/rootfs
sudo mount /dev/sda1 /media/$USER/bootfs

2. Write the cloud-init Configuration Files

These two files replace the wifi-setup service, user creation, and first-boot service from the old approach. They go on the boot partition and are processed automatically on first boot by cloud-init.

2a. network-config - WiFi credentials (user-editable)

This is the file end users edit to connect to their WiFi network. Replace the existing network-config on the boot partition:

cat > /media/$USER/bootfs/network-config << 'EOF'
network:
  version: 2
  wifis:
    wlan0:
      dhcp4: true
      # Change this to your country code (ISO 3166-1 alpha-2)
      regulatory-domain: "NL"
      access-points:
        "YourNetworkName":
          password: "YourPassword"
      optional: false
EOF

2b. user-data - system configuration

This creates the bridge user, sets the hostname, installs Python dependencies on first boot, enables the bridge service, and disables cloud-init after it has run. Replace the existing user-data on the boot partition:

(generate the hashed password using: echo 'littleprinterlives' | openssl passwd -6 -stdin)

cat > /media/$USER/bootfs/user-data << 'EOF'
#cloud-config

hostname: little-printer-zigbee-bridge
manage_etc_hosts: true

timezone: Europe/Amsterdam

# 1. Create bridge user
users:
  - name: bridge
    groups: users,adm,dialout,audio,netdev,video,plugdev,cdrom,games,input,gpio,spi,i2c,render,sudo
    shell: /bin/bash
    lock_passwd: false
    passwd: "$6$boqkDuR6gaVxXbFA$glw/miVRIqHulbFK0Bebyyov0NKQ3z7tlyUS7RYkXGjDozcEiSG.jSw2IDSI/ghItMpaJx/92Zajr.v6ebqmU1"

# Uncomment to start ssh
# runcmd:
#  - systemctl enable ssh
#  - systemctl start ssh

enable_ssh: false

rpi:
  i2c: true
  spi: true

# 2. Update system packages
package_update: true
packages:
  - libopenjp2-7
  - python3-pip
  - git
  - libfreetype-dev

# 3. Create service file
write_files:
  - path: /etc/systemd/system/little-printer-zigbee-bridge.service
    permissions: '0644'
    owner: root:root
    content: |
      [Unit]
      Description=Little Printer Zigbee Bridge Script
      After=network-online.target
      Wants=network-online.target

      [Service]
      Type=simple
      User=bridge
      WorkingDirectory=/opt/little-printer-zigbee-bridge
      ExecStartPre=/bin/sh -c 'until ping -c1 1.1.1.1 > /dev/null 2>&1; do sleep 2; done'
      ExecStart=/usr/bin/env python3 -m bridge.main --sirius
      Restart=on-failure
      RestartSec=10

      [Install]
      WantedBy=multi-user.target

runcmd:
  # Block execution until network is online
  - until ping -c1 1.1.1.1 > /dev/null 2>&1; do sleep 2; done
  
  # 4. Clone the repository into /opt
  - git clone https://github.com /opt/little-printer-zigbee-bridge
  
  # 5. Transfer folder ownership to the bridge user so it can generate config.json
  - chown -R bridge:bridge /opt/little-printer-zigbee-bridge
  
  # 6. Install python requirements globally (system-level matching your service's python execution)
  - cd /opt/little-printer-zigbee-bridge && pip3 install -r bridge/requirements.txt --break-system-packages
  
  # 7. Tell systemd to find the file created by write_files, then boot it up
  - systemctl daemon-reload
  - systemctl enable little-printer-zigbee-bridge.service
  - systemctl start little-printer-zigbee-bridge.service
  
  # 8. Disable cloud-init for subsequent boots
  - touch /etc/cloud/cloud-init.disabled
EOF

4. Copy Project Files

Not needed anymore: we now just clone the repo on the first start.

Copy the bridge project onto the image. Ownership will be fixed by cloud-init's runcmd on first boot.

sudo cp -r /path/to/little-printer-zigbee-bridge /media/$USER/rootfs/opt/little-printer-zigbee-bridge


5. Clean Private Data Before Creating Image

WiFi credentials

Restore network-config to placeholder values - the same file from step 2a but with your actual credentials replaced:

cat > /media/$USER/bootfs/network-config << 'EOF'
network:
  version: 2
  renderer: NetworkManager
  wifis:
    wlan0:
      dhcp4: true
      # Change this to your country code (ISO 3166-1 alpha-2)
      regulatory-domain: "NL"
      access-points:
        "YourNetworkName":
          password: "YourPassword"
      optional: false
EOF

Shell history

sudo rm -f /media/$USER/rootfs/root/.bash_history
sudo rm -f /media/$USER/rootfs/home/bridge/.bash_history

Machine ID

sudo rm -f /media/$USER/rootfs/etc/machine-id
sudo rm -f /media/$USER/rootfs/var/lib/dbus/machine-id
sudo touch /media/$USER/rootfs/etc/machine-id

An empty /etc/machine-id triggers a fresh ID to be generated on first boot.

Logs

sudo rm -rf /media/$USER/rootfs/var/log/journal/*
sudo rm -rf /media/$USER/rootfs/var/log/*.log
sudo rm -rf /media/$USER/rootfs/tmp/*
sudo rm -rf /media/$USER/rootfs/var/tmp/*

6. Create the .img File

Unmount the SD card partitions (keep the card inserted)

sudo umount /media/$USER/rootfs
sudo umount /media/$USER/bootfs

Image the SD card with dd

Replace /dev/sda with your actual device from lsblk:

sudo dd if=/dev/sda of=~/little-printer-zigbee-bridge.img bs=4M status=progress

Shrink and compress with PiShrink

wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
chmod +x pishrink.sh
sudo ./pishrink.sh -z ~/little-printer-zigbee-bridge.img

This creates little-printer-zigbee-bridge.img.gz, compressed and shrunk to the minimum size needed.


7. User Instructions

  1. Download little-printer-zigbee-bridge.img.gz and flash it to an SD card using Raspberry Pi Imager or balenaEtcher
  2. After flashing, open the boot partition of the SD card (visible on Windows, Mac, and Linux as a small FAT drive)
  3. Open network-config and replace YourNetworkName and YourPassword with your WiFi credentials
  4. Optionally update regulatory-domain to your country code (e.g. GB, US, DE)
  5. Save the file, eject the SD card, and insert it into the Raspberry Pi
  6. Power on - the Pi will configure WiFi, install Python dependencies, and start the bridge automatically on first boot. The first boot takes a little longer than usual while dependencies are installed.

Troubleshooting

Bridge does not start Check the cloud-init log to see if first-boot setup completed:

sudo cat /var/log/cloud-init-output.log

WiFi never comes up Check NetworkManager and the network-config that was applied:

sudo systemctl status NetworkManager
sudo cat /var/log/cloud-init-output.log | grep -i network

Script starts before network is ready The ExecStartPre ping loop should make the script wait until internet is reachable.

Check bridge service status

sudo systemctl status little-printer-zigbee-bridge.service
sudo journalctl -u little-printer-zigbee-bridge.service