fix: Add sudo access for systemd service status on DietPi/Orange Pi#19
Open
mverteuil wants to merge 56 commits into
Open
fix: Add sudo access for systemd service status on DietPi/Orange Pi#19mverteuil wants to merge 56 commits into
mverteuil wants to merge 56 commits into
Conversation
3 tasks
bec3cd7 to
016cd6d
Compare
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)
016cd6d to
9ee6458
Compare
install.sh exports a `sudo` bash function when running as root; bash
serializes it into the environment as `BASH_FUNC_sudo%%=() { ... }`.
When the setup_app.py subprocess chain does `sudo -u birdnetpi ...`,
the downstream login shell re-parses that entry and executes the
function body as a shell command, failing with:
-bash: line 1: $'BASH_FUNC_sudo%%=() { ... }': command not found
Add a _clean_env() helper that filters BASH_FUNC_* out of the inherited
env and use it at the two subprocess callsites (install-assets and
setup-system).
Observed on DietPi Bookworm + Pi Zero 2 W during the flash-time install
flow when running `BIRDNETPI_BRANCH=... /boot/firmware/install.sh` as
root.
flash_sdcard.py reads `config["admin_password"]` at the DietPi/RPi boot configuration paths (lines 1622, 2154), but the TUI was writing the collected password under `config["password"]` — so a fresh flash with a new config raised KeyError: 'admin_password'. Older saved profiles happened to already use the matching key, masking the bug. Standardize the TUI on `admin_password` (matches the consumer) and keep backwards compatibility for older profiles that stored `password`.
Two fixes that became visible while diagnosing post-reboot WiFi failure on Orange Pi 5 Pro / DietPi. 1. dietpi-wifi.txt was missing aWIFI_KEYMGR[0]. DietPi's first-boot script reads this when generating /etc/wpa_supplicant/wpa_supplicant.conf; without it the supplicant config is incomplete and may associate via fallbacks the first time but fail on warm reboot. Now writes the value collected by the TUI (config["wifi_auth"], default "WPA-PSK"). 2. setup_app.create_directories now mkdirs /var/log/journal so systemd- journald flips to persistent storage. DietPi's stock log2ram setup loses all logs on hard power-cycle, which is exactly what happens when a board hangs at boot — making post-mortem diagnosis impossible.
DietPi mounts a tmpfs over /var/log via dietpi-ramlog.service, which shadows the /var/log/journal directory we create for persistent journald. Result: journald sees an empty /var/log at boot, reverts to volatile storage, and a hard power-cycle (the exact failure mode we're trying to diagnose) leaves no logs behind. The service's ExecStop is supposed to flush tmpfs back to disk on shutdown, but only runs on a clean shutdown. Disable dietpi-ramlog when present so /var/log/journal is real disk and journald keeps logs across reboots. Trade-off is more SD card writes, which is acceptable for a system that's expected to be debuggable.
dietpi.txt was setting CONFIG_BOOT_WAIT_FOR_NETWORK=2, which makes DietPi block boot at network-online.target until DHCP+DNS work fully. Combined with the OPi 5 Pro's tendency to take a while (or fail) to re-associate WiFi on warm reboot, this hangs the system at boot indefinitely — services never start, SSH never comes up. Diagnosed via the dietpi-firstboot.log on a card that wouldn't reboot. Drop to value 1 (wait for any interface up, don't require online). First-boot asset download is still gated by DietPi's own AUTO_SETUP_BOOT_WAIT_FOR_NETWORK=1 flag, so install flow is unaffected.
The Ampak AP6275P (BCM4375) WiFi module on the Orange Pi 5 Pro fails to re-initialize on warm reboot under Bookworm. Driver loads, but the firmware probe / SDIO handshake errors out, so wlan0 never appears and ifupdown's wpa_supplicant call fails. Install a tiny systemd oneshot at sysinit.target that does rmmod brcmfmac brcmutil; modprobe brcmfmac before networking starts. This forces a clean (re-)init that mimics a cold boot. Service is no-op on systems without brcmfmac (ConditionPathExists guards the unit, and the module-load is idempotent).
Real bug confirmed via dmesg from a freshly-flashed OPi 5 Pro: brcmfmac mmc2:0001:1: Direct firmware load for brcm/brcmfmac43456-sdio.rockchip,rk3588s-orangepi-5-pro.bin failed with error -2 DietPi's Bookworm image ships the AP6256 (BCM4345/9) generic firmware but not the Orange Pi 5 Pro board-specific NVRAM. Driver falls back to generic, which works for cold boot but lacks the calibration data the chip needs to recover from a non-clean reset on warm reboot. Result: WiFi works first time, dies on every subsequent reboot, even cold power-cycle doesn't always clear it. Two fixes: 1. install_opi5pro_brcmfmac_firmware downloads the correct .bin/.txt from Joshua-Riek/firmware (de-facto upstream for OPi 5 Pro Linux firmware) and drops them at the kernel-expected path. No-op on other devices via /proc/device-tree/model gate. 2. install_brcmfmac_reset_service now also rfkill block/unblock wifi between the rmmod and modprobe, forcing the Rockchip rfkill driver to power-cycle the WLAN GPIO (the [WLAN_RFKILL]: rfkill_set_wifi_bt_power sequence in dmesg confirms the GPIO control is wired). Pure module reload alone wasn't enough — the chip needed actual power off/on.
The "missing OPi 5 Pro firmware" was a red herring. Joshua-Riek's
firmware repo ships those board-name files as git symlinks pointing at
the generic brcmfmac43456-sdio.{bin,txt} — the binary content is
identical. The earlier download approach would have grabbed 22-byte
text "files" containing the symlink target string, then written those
useless bytes over a real firmware path.
Replace with creating the symlinks locally. This silences the
Direct firmware load ... failed -2 warning so it stops being a
confounder, but the real warm-reboot recovery is the rfkill power-cycle
in birdnetpi-brcmfmac-reset.service.
…tion Armbian's boot dir is part of the ext4 rootfs, mounted on macOS via anylinuxfs (NFS). macOS's cp tries to preserve extended attributes which NFS rejects, causing: cp: ... could not copy extended attributes ... Operation not permitted The Armbian-specific cp calls already had -X; this one was missed, which left /boot/install.sh absent on every Armbian flash and broke the post-flash bash /boot/install.sh instruction shown by the TUI. DietPi paths are unaffected (their boot partition is FAT, mounted natively by macOS, no xattrs in play).
Same xattr-vs-NFS issue as the install.sh copy fix, hitting the next sudo cp call (birdnetpi_config.json). Sweeping the rest of flash_sdcard.py and adding -X to every plain cp + converting cp -r to cp -rX. Safe everywhere — FAT and ext4-via-anylinuxfs both — since -X only skips xattrs that the filesystem either lacks or rejects. 15 cp calls and 1 cp -r call updated.
Armbian Trixie ships Python 3.13 by default and python3.11 isn't in the default repos, so the apt step crashed out with "Unable to locate package python3.11". DietPi/Bookworm has it; Armbian Trixie does not. Drop python3.11/-venv/-dev from the apt list. uv reads pyproject.toml's `requires-python = "==3.11.*"` and downloads the right managed interpreter at venv-create time, so this works on both Bookworm (python3.11 happens to be available) and Trixie (uv installs its own).
Armbian Trixie on some boards puts armbianEnv.txt under /boot/firmware rather than /boot. Without checking both locations we fell through to 'WARNING: Could not detect system type to enable SPI'. Add /boot/firmware fallbacks plus orangepiEnv.txt while we're at it.
install.sh only looked at /root/birdnetpi_config.json, which DietPi populates from /boot during firstboot but Armbian doesn't — so on Armbian the config never made it into /opt/birdnetpi/, and downstream setup-system errored out. - install.sh now checks /root, /boot/firmware, and /boot in order, and uses `sudo test -f` so the existence check works even when invoked by a non-root SSH user (whose /root traversal is denied by mode 700). - get_boot_config() reorders to check world-readable boot paths before /root, so the birdnetpi user can find the config without needing privileged access and without triggering Path.exists() permission noise on systems with strict /root perms.
SPI overlay handling on Armbian Trixie + Orange Pi 5 Pro doesn't currently work end-to-end (the kernel comes up but /dev/spidev* doesn't appear from the overlay we ship). Hide the "Enable SPI" toggle in the flasher TUI for this combo so users don't pick it expecting an e-paper display to work. Implemented as an explicit SPI_UNSUPPORTED set so it's easy to add or remove combos as we get more hardware coverage.
… RPi OS) After dd finishes, macOS doesn't always have /dev/diskNs1 visible immediately — the partition table re-read can lag a few seconds, especially on slower SD writers. The hard-coded `diskutil mount /dev/diskNs1` was racing that and bailing with "Failed to find disk". Poll for the partition node for up to 30s, then mount. Fall back to `diskutil mountDisk <device>` if the partition still hasn't appeared, which forces macOS to rescan the whole device. Hit during a Pi 4 / Raspbian flash; the same code runs for any non- DietPi RPi OS image.
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: