fix: Add sudo access for systemd service status on DietPi/Orange Pi#19
Open
fix: Add sudo access for systemd service status on DietPi/Orange Pi#19
Conversation
Root cause: DietPi automation scripts only execute when AUTO_SETUP_INSTALL_SOFTWARE=1 is set. Without this, scripts are created but never run, leaving no install.sh after first boot. Changes: - Add AUTO_SETUP_INSTALL_SOFTWARE=1 to dietpi.txt config - Create Automation_Custom_PreScript.sh (before partition cleanup) - Create Automation_Custom_Script.sh (after cleanup, fallback) - Enhanced logging to /var/log/birdnetpi_automation.log - Add BIRDNETPI_README.txt with troubleshooting guide - Update summary messages for all preservation methods Implements quadruple redundancy for install.sh preservation: 1. AUTO_SETUP_CUSTOM_SCRIPT_EXEC (traditional) 2. preserve_installer.sh (custom script) 3. Automation_Custom_PreScript.sh (key fix - before cleanup) 4. Automation_Custom_Script.sh (fallback - after cleanup) Fixes install.sh not appearing on DietPi + Orange Pi 5 Pro where DIETPISETUP partition is deleted after first boot.
The DIETPISETUP partition is deleted after DietPi first boot, removing install.sh. While automation scripts attempt to preserve it, they run during first boot when the partition may already be gone. Solution: Also copy install.sh directly to /root on the rootfs partition during SD card flashing. This ensures install.sh persists regardless of when automation scripts execute. - Mount rootfs partition (partition 2) during configure_dietpi_boot - Copy install.sh to rootfs:/root/install.sh - Keeps automation scripts as fallback for macOS users (ext4 limitations) - Linux users get install.sh directly on persistent storage
macOS can write to ext4 filesystems using anylinuxfs. Update the configure_dietpi_boot function to attempt mounting the rootfs partition on macOS using diskutil. - Try to mount partition 2 (rootfs) using diskutil mount - anylinuxfs automatically handles ext4 filesystem access - Falls back to boot partition + automation scripts if mount fails - Unmounts rootfs after copying install.sh This ensures install.sh is written directly to persistent storage on macOS, not just relying on first-boot preservation scripts.
Fixed two issues with macOS rootfs mounting: 1. DietPi uses partition 3 for rootfs (1=BIOS, 2=DIETPISETUP, 3=ROOTFS) 2. /root directory doesn't exist on fresh rootfs - create it with mkdir -p Changes: - Changed from disk4s2 to disk4s3 for rootfs partition on macOS - Added 'sudo mkdir -p /root' before copying install.sh - Applied same fix to Linux path for consistency
Added debug output to show: - All available partitions on the device - Which partition we're trying to mount - Error message when mount fails This will help diagnose why rootfs partition mounting is failing on macOS.
Discovered via debug output that DietPi Orange Pi 5 Pro uses: - Partition 1: Linux Filesystem (rootfs) - 1.3 GB - Partition 2: DIETPISETUP (FAT32) - 1.0 MB Changed from disk4s3 to disk4s1 for macOS rootfs mounting. This matches the actual partition layout on the SD card.
Implemented proper anylinuxfs integration for DietPi similar to Armbian: - Check if anylinuxfs is installed - Unmount any existing anylinuxfs mounts - Run 'sudo anylinuxfs disk4s1 -w false' to mount rootfs - Wait for mount point to appear in /Volumes - Copy install.sh to /root on the mounted rootfs - Unmount with 'sudo anylinuxfs unmount' when done This should finally enable rootfs mounting on macOS with anylinuxfs.
anylinuxfs creates a mount using the partition name (e.g., disk4s1) rather than a filesystem label. The previous code looked for specific mount names like "dietpi_root", "DIETPI", or "Linux", which didn't exist. Now captures the list of volumes before running anylinuxfs, then finds the new volume by comparing before/after and checking for Linux filesystem markers (/etc, /root, /usr). This correctly detects the mount regardless of what name anylinuxfs uses.
When copying to ext4 filesystem via anylinuxfs, macOS cp tries to copy extended attributes which are not supported on ext4. This causes: "could not copy extended attributes: Operation not permitted" Using dd instead bypasses extended attributes entirely and just copies the raw file content. Also improved anylinuxfs unmount error handling to catch CalledProcessError in addition to TimeoutExpired.
DietPi and Orange Pi systems don't have spi/gpio groups by default like Raspberry Pi OS does. This was causing usermod errors: usermod: group 'spi' does not exist usermod: group 'gpio' does not exist Now create these groups if they don't exist before adding the birdnetpi user to them. Uses getent to check group existence safely.
…guration - Copy birdnetpi_config.txt to rootfs partition (persists after DIETPISETUP deletion) - Add os_key and device_key to config file for unattended setup - Update install.sh to source config from /root, /boot/firmware, or /boot - Clean up temp config file after flashing - Initialize config_file variable to fix pyright warning
- Add orange_pi_0w2 to Armbian and DietPi device lists - Remove orange_pi_5 from both OS image configurations - Keep Orange Pi 5 Plus and Pro variants - Update Armbian URL: orangepizero2w - Update DietPi URL: DietPi_OrangePiZero2W-ARMv8-Bookworm.img.xz - Update docstring example to use orange_pi_5_pro
- Add BIRDNETPI_OS_KEY and BIRDNETPI_DEVICE_KEY to environment exports - Enables setup_system.py to use pre-configuration when config file is missing - Fixes issue where OS/device prompts appeared despite config file being created - Works around DietPi deleting DIETPISETUP partition after first boot
- Add orange_pi_0w2 to DEVICE_PROPERTIES with SPI capability - Fix DEVICE_PROPERTIES keys to match actual device keys (use underscores) - Add Allwinner H618 SPI overlay (spi-spidev) for Orange Pi Zero 2W - Update SPI configuration to handle Orange Pi Zero 2W specifically - Improve error handling when SPI overlay is not configured - Add chip description to SPI enabled messages (Allwinner H618 vs RK3588)
The config file needs to export os_key and device_key so they're available to install.sh when it sources the file. Without export, the variables are set in the config file's scope but not passed to the parent shell.
- Remove PI_IMAGES dictionary (was only referenced by dead functions) - Remove select_pi_version() function (never called) - Remove download_image() function (never called, superseded by download_image_new) - Update FlasherWizardApp instantiation to use only OS_PROPERTIES argument The TUI wizard (flasher_tui.py) now builds os_images internally from the devices module, so flash_sdcard.py no longer needs to pass it. Total cleanup: 212 lines removed
The copy_birdnetpi_config function was still writing shell script format to birdnetpi_config.txt instead of JSON format to birdnetpi_config.json. This meant WiFi settings and git branch were not being persisted to the boot configuration. Changes: - Rewrite copy_birdnetpi_config to output JSON format - Add wifi_ssid, wifi_password, wifi_auth to saved config - Change output filename from birdnetpi_config.txt to birdnetpi_config.json - Use json.dumps() instead of building shell script lines - Update docstring to reflect JSON format
The install.sh script was still looking for birdnetpi_config.txt and trying to source it as a shell script, but we now write JSON format to birdnetpi_config.json. Changes: - Update config file paths from .txt to .json - Parse JSON using python3 instead of sourcing shell script - Extract repo_url and branch fields from JSON - Gracefully handle missing python3 or parse errors - Update comment to reference .json format
The DietPi preservation scripts embedded in flash_sdcard.py were still referencing birdnetpi_config.txt instead of the new .json format. This caused fresh flashes to write .txt files instead of .json files, which broke the JSON parsing in install.sh and setup_system.py. Changes: - Update first preservation script to copy birdnetpi_config.json - Update second preservation script to copy birdnetpi_config.json - Update direct rootfs copy to use birdnetpi_config.json - Update all echo messages to reference .json files All three code paths that preserve config for DietPi now use .json format.
Instead of trying to parse JSON before Python is installed, we now: 1. Read install.sh during flash 2. Substitute the REPO_URL and BRANCH defaults with configured values 3. Write the modified install.sh to boot partition This ensures the branch setting is used even before Python is available, eliminating the need for JSON parsing in early boot stages. Simplifies install.sh by removing the JSON parsing logic that ran before Python was installed (which always failed silently).
The root cause was that copy_installer_script() correctly substituted the branch/repo defaults in install.sh and copied it to the boot partition, but then the DietPi rootfs copy code was copying the ORIGINAL install.sh again, overwriting the modified one. Now copy_installer_script() returns the path to the modified temp file, and the rootfs copy uses that instead of reading the original file again.
Remove --quiet flags from uv lock and uv sync commands to provide feedback during the dependency installation process, which can take several minutes on slower devices.
The birdnetpi user cannot read /root/birdnetpi_config.json due to directory permissions (700). Copy the config file to /opt/birdnetpi/ during installation where the birdnetpi user has access. Changes: - install.sh: Copy config from /root to /opt/birdnetpi after cloning - setup_system.py: Check /opt/birdnetpi first for config file
The orange_pi_0w2 device key was not in the supported devices list, causing a KeyError during setup. Add it with proper display name.
Configure UV_CACHE_DIR=/var/cache/uv to cache downloaded packages. This speeds up retries when installation fails and avoids re-downloading packages that were already fetched successfully.
- Cache packages in /dev/shm (RAM) instead of SD card - Speeds up retries without wearing SD card - Clean up cache at end to free RAM for runtime
Fix service status detection showing "unknown" for all services on DietPi/Orange Pi platforms by adding sudo to systemctl commands. On DietPi, the birdnetpi user cannot access systemd D-Bus without sudo privileges, resulting in "Failed to connect to bus" errors. Changes: - Add sudo prefix to systemctl commands in EmbeddedSystemdStrategy - Create /etc/sudoers.d/birdnetpi-systemctl during installation - Grant NOPASSWD sudo access for service management commands - Update test expectations to match new command format The sudoers file uses wildcards to allow flags like --no-pager and includes permissions for birdnetpi services, caddy, redis, and reboot.
Add support for enabling SPI on DietPi/Armbian (Orange Pi) platforms by detecting and configuring /boot/armbianEnv.txt. Changes: - Check for existing SPI devices first (skip if already enabled) - Support Raspberry Pi OS via /boot/firmware/config.txt (existing) - Support DietPi/Armbian via /boot/armbianEnv.txt (new) - Add spi-spidev to overlays parameter - Handle both new and existing overlays configurations - Provide warning if platform cannot be detected This ensures e-paper HAT detection works on all supported platforms.
DietPi uses /boot/dietpiEnv.txt instead of /boot/armbianEnv.txt on some platforms. Update SPI detection to check for both files. This fixes SPI enablement on Orange Pi 5 Pro running DietPi.
On RK3588-based boards like Orange Pi 5 Pro, the platform comes with pre-configured SPI overlays (e.g., rk3588-spi4-m0-cs1-spidev). The installer was incorrectly adding the generic spi-spidev overlay on top of these, which could cause conflicts. Changes: - Detect any existing SPI overlay (platform-specific or generic) - If SPI overlay exists, only verify/add param_spidev_spi_bus=0 - Don't add spi-spidev if platform-specific overlay already present - Only add spi-spidev on systems without any SPI overlay configured This ensures we respect platform-specific device tree configurations while still enabling SPI on boards that need the generic overlay.
When SPI overlay and param_spidev_spi_bus are both already configured, no reboot is needed since nothing was changed. Only set SPI_ENABLED=true (which triggers reboot) when we actually modify the boot configuration. This prevents unnecessary reboots when re-running the installer on a system that already has SPI properly configured.
- Detect RK3588 overlays with incorrect chip prefix - Replace with correct format (spi4-m2-cs0-spidev without prefix) - DietPi automatically prepends overlay_prefix, so overlay names should not include it - Add param_spidev_max_freq=100000000 for RK3588 platforms - Only trigger reboot when configuration is actually modified Tested on Orange Pi 5 Pro - /dev/spidev4.0 now appears correctly after reboot
- Use spi4-m2-cs0-spidev overlay (M2-CS0 variant is verified working) - Remove chip prefix from overlay names (DietPi auto-prepends from overlay_prefix) - Add param_spidev_max_freq=100000000 for RK3588 platforms (required) - Also fixed ROCK 5B overlay format for consistency This eliminates the need for the installer to reboot when SPI is enabled since the SD card boots with correct SPI configuration from the start.
The configure_dietpi_boot() function now downloads the Waveshare ePaper library to the boot partition when SPI is enabled, matching the behavior of configure_boot_partition_new() for Raspberry Pi OS. This eliminates the massive git clone during install.sh which was failing due to the repository containing 20k+ STM32 firmware files. The flasher uses sparse-checkout to download only the Python subdirectory (~6MB). The install.sh script can then use the pre-downloaded library from /boot/firmware/waveshare-epd (or /boot/waveshare-epd on DietPi).
Created download_waveshare_library() helper to eliminate code duplication between configure_dietpi_boot() and configure_boot_partition_new(). The helper function: - Uses sparse-checkout to download only Python subdirectory (~6MB) - Avoids cloning 20k+ STM32 firmware files from the full repository - Provides clear documentation and consistent behavior across platforms This reduces the code from ~130 lines (65 lines × 2) to ~70 lines (65 lines helper + 2 simple calls), improving maintainability.
- Copy only lib/ directory and setup.py (essential for installation) - Skip pic/ (example images, several MB) and examples/ (test scripts) - Prevents 'No space left on device' error on ~200MB boot partitions - Update docstring and comments to reflect space-saving approach
The previous implementation had several issues preventing the Waveshare library from being installed from the pre-downloaded tarball: 1. Boot partition mount location was hardcoded to /boot/firmware, but DietPi uses different mount points and deletes DIETPISETUP after first boot 2. The 'uv lock' command was regenerating the lockfile by accessing Git, even with the patched pyproject.toml, causing /dev/shm to run out of space 3. Tarball was only on boot partition which gets deleted on DietPi first boot Changes: - **flash_sdcard.py**: Copy Waveshare tarball to rootfs:/root/ for persistence - **install.sh**: Check multiple locations for tarball (boot partition variants + rootfs) - **install.sh**: Remove 'uv lock' command - let 'uv sync' handle the local path - **install.sh**: Add fallback message when tarball not found This ensures the compressed Waveshare library is available even after the DIETPISETUP partition is deleted, preventing the 20,805-file git clone that exhausts /dev/shm memory.
/dev/shm is often limited to 512MB on many systems, which is insufficient for uv's cache when building packages. This causes 'No space left on device' errors even for small packages. Switch to /tmp which is typically larger and still avoids excessive SD card writes on most systems (often tmpfs-backed or similar).
- Add patch_waveshare_orangepi.py to automatically add Orange Pi support to Waveshare library during installation - Replace build-essential with minimal dependencies (gcc, libc6-dev) to reduce installation size (removes perl, make, g++, dpkg-dev) - Add libportaudio2 and libsndfile1 for audio dependencies - Fix pyproject.toml patching using heredoc to avoid DietPi su wrapper quote mangling issues - OrangePi class uses lgpio with gpiochip4 and SPI bus 4 - Detects Orange Pi via /proc/device-tree/model - Idempotent patching (can run multiple times safely)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Problem
On DietPi/Orange Pi, the birdnetpi user cannot access systemd D-Bus without sudo privileges, resulting in:
sudo systemctl is-activeworks but plainsystemctldoesn't for birdnetpi userSolution
Service Strategies: Added sudo prefix to systemctl commands in
EmbeddedSystemdStrategyget_service_status(): Added sudo tosystemctl is-activeget_service_details(): Added sudo tosystemctl showreboot_system(): Already had sudoInstallation: Created
/etc/sudoers.d/birdnetpi-systemctlwith NOPASSWD access--no-pagerTests: Updated test expectations to match new command format with sudo
Verification
Tested on DietPi running on Orange Pi Zero 2W:
Test Plan
uv run pytest tests/birdnetpi/system/test_service_strategies.py)uv run pre-commit run)Files Changed
src/birdnetpi/system/service_strategies.py: Added sudo to systemctl commandsinstall/install.sh: Create sudoers file during installationtests/birdnetpi/system/test_service_strategies.py: Updated test expectationsRelated Work
This completes the DietPi/Orange Pi support stack: