Skip to content

How the SD card image is built

Since 2025 the ethoscope image is based on Raspberry Pi OS, which supports the Raspberry Pi hardware better and ships native libcamera packages. Images made before 2025 were based on Arch Linux ARM.

The image is Raspberry Pi OS Lite (64-bit), based on Debian Trixie.

  1. Write Raspberry Pi OS Lite (64-bit) to an empty microSD card or SSD with Raspberry Pi Imager. You can set a user name and a host name (for example ethoscope000) when writing.

  2. Start a Raspberry Pi from it, connected by Ethernet (a screen and keyboard are optional). The first boot takes a while: the system generates its SSH keys, expands to fill the card and reboots.

  3. Log in (for example ssh [email protected]), download the installation script and run it:

    Terminal window
    wget https://raw.githubusercontent.com/gilestrolab/ethoscope/refs/heads/dev/accessories/install_ethoscope_debian.sh
    sudo bash install_ethoscope_debian.sh

The script does everything: it creates the ethoscope user (password ethoscope), clones the software into /opt/ethoscope (with origin pointing to the node’s git mirror at git://node.local and a github remote for fetching directly from GitHub), installs the device software and its systemd services, and configures MariaDB, the network and the Pi hardware. It reboots once after installing the system packages and then carries on by itself. When it has finished, the new ethoscope appears on the node as ETHOSCOPE_000.

The camera stack differs between models: the legacy start_x.elf and bcm2835-v4l2 on the Pi 2 and 3, KMS/libcamera (vc4-kms-v3d, imx219) on the Pi 4 and 5. Images used to be built per model for this reason. Now configure_pi_camera.sh manages a single block in config.txt, and ethoscope_camera_firstboot.service runs it at every boot. It does nothing when the block already matches the Pi, and rewrites it and reboots once when a card is moved to a different model, so one image configures its own camera. The image is fully supported on the Pi 3 and Pi 4 and is named with a _pi3_pi4 tag. The 64-bit image does not boot the common ARMv7 Pi 2, and the Pi 5 has no pigpio, so its light can only be switched on and off, although its camera is configured automatically.

Debian Trixie has no pigpio package, and pigpio is the only backend that dims the LED on GPIO17 without flicker: it times the PWM with the DMA engine, while the lgpio fallback is timed by the CPU and is clean only on the hardware PWM pins (12, 13, 18 and 19). The installer therefore builds pigpio v79 from source, after installing python3-setuptools and python3-full (Python 3.12 and later need them, or make install fails with No module named 'distutils'). pigpio is installed in /usr/local/bin without a service, so the installer writes pigpiod.service and runs it on port 8889, because 8888 is taken by ethoscope_update. After a boot, journalctl -u ethoscope_light -b | grep backend should report backend = pigpio.

A public image must not carry the unique details of the device it was made on. On the configured device, run:

Terminal window
sudo /opt/ethoscope/accessories/install_ethoscope_debian.sh --prepare-image
sudo poweroff # shut down cleanly; never pull the power

--prepare-image deletes the SSH host keys (each ethoscope generates its own at first boot, with sshd-keygen.service) and clears the installation logs, shell history, package and pip caches and the journal. It does not blank /etc/machine-id: systemd reads an empty machine-id as a first boot (ConditionFirstBoot=yes), which can hang a headless start. A shared machine-id in the image does no harm, because each ethoscope generates its own when it is renamed from ETHOSCOPE_000 to its real number.

Put the card in a Linux computer, unmount its partitions, then:

Terminal window
export DEVICE=sdc # check with lsblk: the partitions are labelled bootfs and rootfs
sudo umount /dev/${DEVICE}?* 2>/dev/null
sudo dd if=/dev/${DEVICE} of=$(date +%Y%m%d)_ethoscope000_pi3.img bs=4M status=progress conv=fsync
# write today's date into /etc/sdimagename and shrink the image to fit a 32 GB card
sudo /opt/ethoscope/accessories/ethoscope-image.sh --rename --shrink $(date +%Y%m%d)_ethoscope000_pi3.img
md5sum *_ethoscope000_pi3.img > $(date +%Y%m%d)_ethoscope000_pi3.img.md5

ethoscope-image.sh also has --info (inspect an image), --fsck, and --update (bring /opt/ethoscope inside an image up to date without writing it to a card).

The same procedure works on an SSD connected to a Pi 4. An image captured from a small SSD (40 GB, for example) can be written to a larger one and expanded afterwards, with the Expand root filesystem setting of the ethoscope or from its command line:

Terminal window
sudo raspi-config --expand-rootfs

An image built for one model can be converted to serve both the Pi 3 and the Pi 4, without rebuilding:

Terminal window
sudo ./ethoscope-image.sh --universal-camera OLD_ethoscope000_pi3.img

This mounts the image, brings /opt/ethoscope up to date, enables ethoscope_camera_firstboot.service, moves config.txt to the managed camera block (removing the old start_x.elf lines) and renames the image’s /etc/sdimagename tag from _piN to _pi3_pi4. Compress and upload the image again afterwards.