[WIP] NixOS support#379
Open
mrosseel wants to merge 202 commits into
Open
Conversation
- build.yml: single build + Cachix push + unstable channel updates - release.yml: manual release workflow for stable/beta channels Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The SD image module provides filesystems, but toplevel builds need a minimal stub to evaluate successfully. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Required for NixOS module system to accept devMode setting. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Required when module has both options and config sections. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces FIXME placeholders with actual SRI hashes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Uses Pi5 runner when RUNNER_LABELS variable is set, falls back to ubuntu with QEMU emulation otherwise. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Filter to only Pi 4B device tree (CM4 incompatible with our overlays) - Use shorthand DTS syntax for PWM overlay Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Untracked file was excluded from Nix flake source tree, causing "No module named 'PiFinder.sys_utils_base'" on SD card boot. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add camera overlay (imx477) to netboot config.txt via flake.nix - Fix sys_utils import in main.py to use utils.get_sys_utils() - Add hip_main.dat fetch to pifinder-src.nix for starfield plotting - Add dma_heap udev rule for libcamera/picamera2 access - Fix shared memory naming in solver.py (remove leading /) - Add DNS nameservers for netboot environment - Document power control scripts in CLAUDE.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add runtimeCameraSelection option to hardware.nix (default: true) - SD image includes config.txt with "include camera.txt" directive - Users can edit camera.txt and reboot to switch cameras - Supported cameras: imx296, imx290 (imx462), imx477 - Fix cameraDriver scope in hardware.nix (moved to top-level let) - Add sudoers rules for systemctl stop/start pifinder.service - Add DMA heap udev rule for libcamera video group access - Netboot config sets cameraType = "imx477" for HQ camera dev Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor sys_utils modules to use common base class - Add sys_utils_nixos.py for NixOS-specific implementations - Add get_sys_utils() detection in utils.py for platform selection - Add flake.lock for reproducible builds - Add NetworkManager config to networking.nix - Add deploy-image-to-nfs.sh for netboot development workflow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update build.yml CI workflow - Fix fonts.py import - Fix marking_menus.py formatting - Add missing import to preview.py - Simplify objects_db.py - Add catalog_imports improvements - Update pifinder_objects.db Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Switch to NFSv4 with caching disabled (noac, actimeo=0) - Disable auto-optimise-store in devMode (hard links fail on NFS) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ServerAliveInterval/CountMax to prevent timeout during transfers - Use rsync -R (relative) to preserve directory structure correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comets.txt is downloaded at runtime and must be in a writable location, not the read-only Nix store. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extend eth0 wait to 30 seconds with debug output - Wait for link carrier before DHCP - Add DHCP retries (3 attempts) - Add LIBCAMERA_IPA_MODULE_PATH to pifinder service environment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restore SUBSYSTEM=="pwm" udev rule that was accidentally removed. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Turns on keypad LEDs during sysinit for early visual boot feedback. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- boot-splash.c: displays welcome image with scanning animation - Starts at sysinit, stops when pifinder.service starts - Much faster than Python splash Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove nixos-hardware module (saves 659MB linux-firmware) - Fetch nixos-rebuild at runtime (saves ~500MB llvm/nix deps) - Remove git from systemPackages (nix has built-in git for flakes) Target: ~150MB vs current 1.7GB
- Remove default packages (vim, nano, etc) - Disable polkit, udisks2, speechd - Should reduce closure significantly
NetworkManager-vpnc alone has 1.1GB closure (webkitgtk, llvm, etc). Disable all NM plugins for bootstrap - we just need WiFi.
The 6 .direnv/ nix-direnv cache files were tracked but regenerate on every direnv reload, so rebases baked divergent copies into the nixos stack. Gitignore + untrack stops that churn going forward. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erivations Replace the 524-line nixos/pkgs/python-packages.nix (26 manually packaged PyPI deps, each with hand-chased hashes and build patches) with a uv-managed workspace realized into the Nix store via uv2nix. Changes: * Deps declared in python/pyproject.toml, pinned in python/uv.lock (117 pkgs) * nixos/pkgs/uv-python.nix builds the runtime/dev virtualenvs; the 5 native packages (python-libinput, python-prctl, python-pam, dbus-python, pygobject) keep their build patches as uv2nix overrides * flake.nix: add pyproject-nix/uv2nix/pyproject-build-systems inputs, thread via specialArgs, devShell uses the uv2nix devEnv * libcamera Python bindings stay a Nix overlay (not on PyPI) All four nixosConfigurations + the devShell evaluate; the aarch64 build is to be validated by CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cache DeterminateSystems/magic-nix-cache-action now returns HTTP 418 and the GitHub Actions cache rate-limits it (Twirp ResourceExhausted, "rate limit exceeded"), so `nix develop` cannot fetch the dev environment and every lint/test/type-check job fails before ruff/pytest/mypy even run — which all testable PRs inherit. Mirror build.yml/release.yml and substitute from the self-hosted Attic cache cache.pifinder.eu (ADR 0004) instead, falling back to cache.nixos.org when ATTIC_TOKEN is unavailable (e.g. fork PRs) so the job never hard-fails. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s_utils failures Changes: * networking.nix: pifinder-wifi-fallback service+timer brings up PiFinder-AP when no client connects within 45s (or when AP is forced via the UI), so a device with an unreachable saved network stays reachable and AP survives reboot. * sys_utils.py: persist chosen WiFi mode to PiFinder_data/wifi_mode for the fallback service to restore. * software.py: show a clear 'No internet - check WiFi' screen instead of an empty channel list when GitHub is unreachable. * utils.py: log why get_sys_utils() falls back to the no-op fake (previously swallowed) so a failed NM/dbus/pam import is diagnosable. * remove dead Raspberry Pi OS scripts switch-ap.sh/switch-cli.sh (dhcpcd/hostapd; inert on NixOS). Needs on-device validation of the AP fallback timing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the Pi5 self-hosted runner is slow or unavailable, the fallback now uses GitHub's free hosted arm64 runner (public repos) and builds natively: no QEMU binfmt, no --system aarch64-linux cross flag, and extra-platforms dropped. Timeout cut 360 -> 60 min. Job id kept as build-emulated so downstream needs: references are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t, evdev
These C-extension packages build from sdist in the uv2nix devEnv, but their
overrides did not declare a build backend. When the packages are not cached
(notably the x86_64 lint/test/type-check runners) the sdist build fails and
takes the whole devEnv down with it:
- dbus-python, pygobject: "No module named 'mesonpy'" -> add the meson-python
build system via resolveBuildSystem (alongside the existing pkg-config + C
libs).
- evdev: "No module named 'setuptools'" -> add the setuptools build system
(evdev had no override at all).
Mirrors the existing python-libinput / python-prctl overrides, which already
use resolveBuildSystem { setuptools = []; }.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ders, patches) Surfaced by the first real build. In uv-python.nix: * adafruit-blinka: ignore missing libgpiod.so.2 (vendored non-Pi SoC helpers) * dbus-python/pygobject/pycairo: add ninja for the meson-python build * pygobject: add pycairo for py3cairo.h * evdev: add linuxHeaders and repoint setup.py's header search off /usr/include * rpi-gpio/sh/spidev/pidng: provide the setuptools build backend * python-pam: patch the installed wheel module in postInstall (no src/ at patch time) pyproject.toml: empty [tool.setuptools] package set so the virtual root builds an empty wheel instead of tripping setuptools flat-layout discovery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arch64) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(a) Configure cache.pifinder.eu/pifinder as a public substituter (public key, no token) in build-emulated and the lint/test/type-check jobs, so fork-PR runs (brickbots) pull from the cache instead of building from source. Gate attic login + push on ATTIC_TOKEN so tokenless runs are pull-only and don't fail at push. (b) Add always() to stamp-build so a skipped build-emulated (when build-native succeeds) no longer skips the stamp — green builds now update pifinder-build.json. Also add nixos/brickbots-attic-setup.md: how to give brickbots a push token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- delete by NM connection UUID, not by id == ssid. The old match silently no-ops on any entry whose connection id differs from its SSID (e.g. the corrupt "0x20" entry whose SSID held byte-array text), so such networks could never be deleted from the UI. - add: persist via add_connection(save_to_disk=True) first, then make a best-effort activate. Saving no longer depends on being able to activate right now, which failed (and saved nothing) when wlan0 was busy in AP mode or the network was out of range. - get_wifi_networks: re-query NetworkManager live instead of returning a stale cache, so changes made outside the process (AP/CLI switch, repairs) show up without an app restart. - decode SSIDs with errors="replace" so a single undecodable SSID can't crash the whole network list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NetworkManager (like other security-sensitive plugin loaders) refuses to load any plugin file not owned by root. A store with non-root paths — uid 1000 baked into the migration tarball, or imported by single-user nix — makes NM drop its wifi device plugin entirely: wlan0 shows as "unmanaged", WIFI-HW as "missing", and no wifi client connection ever comes up. Add an idempotent boot oneshot (ordered before NetworkManager) that normalises /nix/store and the nix db dir back to root, remounting the store read-write only when a repair is actually needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The uv2nix env builds RPi.GPIO 0.7.1 from PyPI. Its C module init calls get_rpi_info(), which reads the board revision from /proc/device-tree/system/linux,revision or the /proc/cpuinfo "Revision" line. On a NixOS Pi 4 (arm64, mainline device tree) neither is present, so the import raises "This module can only be run on a Raspberry Pi!" — and since adafruit-blinka imports it via `board`, PiFinder crash-loops at startup. Add a postPatch that makes get_rpi_info() fall back to the always-present /proc/device-tree/model string, synthesising a Pi 4 Model B revision when it sees "Raspberry Pi". Detection then succeeds and the app starts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e / read-only) /nix/store is a read-only bind mount of the same device as /. The repair oneshot remounted it with "remount,rw"/"remount,ro", which operates on the shared superblock, so restoring ro flipped / (and /nix/var) read-only too — breaking the next pifinder-upgrade (nix build could not write temproots). Carry "bind" on both remounts so only this mount's per-mount ro flag changes and the rootfs superblock is left alone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
wifi_mode() returned a value detected once in __init__. When the radio falls back to AP after startup, NetworkManager shows PiFinder-AP active but the UI still said "Client" (and local_ip() returned the wrong address). Detect the mode live from NetworkManager on each call, mirroring get_wifi_networks(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…owing them The upgrade piped `nix build` through a gawk progress filter that dropped nix's stderr, so a failed upgrade only reported "failed" with no cause. Tee the build output to /run/pifinder/upgrade-nix.log and, on failure, dump its tail to the journal so the actual error is visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The placeholder "pifinder-release:REPLACE_WITH_PIFINDER_RELEASE_PUBKEY=" isn't valid base64, so nix aborts every operation with "invalid character in Base64 string" — bricking upgrades on the device. Remove it from both services.nix and migration.nix; the device trusts the working `pifinder` cache. The real release key is added only once that cache is provisioned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace root.initialPassword with hashedPassword (sha-512 of "solveit"). initialPassword only applies at account creation and drifts when changed at runtime; hashedPassword is enforced on every activation, so root access is deterministic. Test-device cred; hash is in the world-readable store, which is fine for a known password. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The closure was staged via `nix-store -qR | tar -c | tar -x`, and the extract runs as the non-root CI user, so the staged /nix/store became uid 1000. tar then baked that into the tarball, and the device extracted non-root store paths — which makes NetworkManager refuse its wifi plugin (wlan0 unmanaged). Force --owner=0 --group=0 when packaging so the tarball is always root-owned at the source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
picamera2 imports its DRM (pykms) and Qt preview backends unconditionally, but the headless device ships neither, so `import picamera2` died on a missing "pykms" and the camera process crash-looped. picamera2 installs from a py3-none-any wheel, so patch the installed previews/__init__.py in $out to make those backends optional (PiFinder only uses NullPreview). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pin the rationale so the web change-password feature (sudo chpasswd) is not broken by a future switch to hashedPassword, which would re-enforce the default on every activation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… progress
The Pi has no RTC, so pifinder-first-boot.service ran before NTP corrected the clock and every binary-cache TLS fetch failed ("certificate is not yet valid"), leaving the knight-rider splash frozen and the full system undownloaded. Order the unit after time-sync.target and block on timedatectl NTPSynchronized (with a /run/systemd/timesync/synchronized fallback) before downloading.
Also replace the indeterminate knight-rider scanner with a real progress bar during the download: boot-splash gains a --progress mode that renders a file-driven bar (0-100), and the first-boot script computes copied/total store-path percentage from nix's output and feeds it via /run/pifinder-boot-progress.
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
Test plan
🤖 Generated with Claude Code