{ description = "Noisebell - door monitor system"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixos-hardware.url = "github:NixOS/nixos-hardware/master"; agenix = { url = "github:ryantm/agenix"; inputs.nixpkgs.follows = "nixpkgs"; }; crane.url = "github:ipetkov/crane"; rust-overlay = { url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; nixos-raspberrypi = { url = "github:nvmd/nixos-raspberrypi/main"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, nixos-hardware, nixos-raspberrypi, agenix, crane, rust-overlay, }: let system = "x86_64-linux"; pkgs = import nixpkgs { inherit system; overlays = [ rust-overlay.overlays.default ]; }; rustToolchain = pkgs.rust-bin.stable.latest.default; craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; src = pkgs.lib.cleanSourceWith { src = ./.; filter = path: type: (builtins.match ".*\.png$" path != null) || (craneLib.filterCargoSources path type); }; remoteArgs = { inherit src; pname = "noisebell"; version = "0.1.0"; strictDeps = true; doCheck = false; }; remoteArtifacts = craneLib.buildDepsOnly remoteArgs; buildRemoteMember = name: craneLib.buildPackage ( remoteArgs // { cargoArtifacts = remoteArtifacts; cargoExtraArgs = "-p ${name}"; } ); noisebell-cache = buildRemoteMember "noisebell-cache"; noisebell-rss = buildRemoteMember "noisebell-rss"; noisebell-discord = buildRemoteMember "noisebell-discord"; crossPkgs = import nixpkgs { inherit system; crossSystem.config = "aarch64-unknown-linux-gnu"; overlays = [ rust-overlay.overlays.default ]; }; muslPkgs = import nixpkgs { inherit system; crossSystem.config = "aarch64-unknown-linux-musl"; overlays = [ rust-overlay.overlays.default ]; }; piRustToolchain = pkgs.rust-bin.stable.latest.default.override { targets = [ "aarch64-unknown-linux-gnu" "aarch64-unknown-linux-musl" ]; }; piCraneLib = (crane.mkLib pkgs).overrideToolchain piRustToolchain; piArgs = { inherit src; pname = "noisebell-pi"; version = "0.1.0"; strictDeps = true; doCheck = false; CARGO_BUILD_TARGET = "aarch64-unknown-linux-gnu"; CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = "${crossPkgs.stdenv.cc.targetPrefix}cc"; TARGET_CC = "${crossPkgs.stdenv.cc.targetPrefix}cc"; CC_aarch64_unknown_linux_gnu = "${crossPkgs.stdenv.cc.targetPrefix}cc"; HOST_CC = "${pkgs.stdenv.cc.nativePrefix}cc"; depsBuildBuild = [ crossPkgs.stdenv.cc ]; cargoExtraArgs = "-p noisebell"; }; piArtifacts = piCraneLib.buildDepsOnly piArgs; noisebell-pi = piCraneLib.buildPackage ( piArgs // { cargoArtifacts = piArtifacts; } ); piStaticArgs = { inherit src; pname = "noisebell-pi-static"; version = "0.1.0"; strictDeps = true; doCheck = false; CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl"; CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER = "${muslPkgs.stdenv.cc.targetPrefix}cc"; TARGET_CC = "${muslPkgs.stdenv.cc.targetPrefix}cc"; CC_aarch64_unknown_linux_musl = "${muslPkgs.stdenv.cc.targetPrefix}cc"; HOST_CC = "${pkgs.stdenv.cc.nativePrefix}cc"; depsBuildBuild = [ muslPkgs.stdenv.cc ]; cargoExtraArgs = "-p noisebell"; }; piStaticArtifacts = piCraneLib.buildDepsOnly piStaticArgs; noisebell-pi-static = piCraneLib.buildPackage ( piStaticArgs // { cargoArtifacts = piStaticArtifacts; } ); piImageBaseModule = { lib, nixos-raspberrypi, pkgs, ... }: { imports = with nixos-raspberrypi.nixosModules; [ default usb-gadget-ethernet ]; system.stateVersion = "24.11"; boot.loader.raspberry-pi = { variant = "02"; firmwarePackage = nixos-raspberrypi.packages.${pkgs.stdenv.hostPlatform.system}.raspberrypifw; bootloader = "kernel"; }; boot.supportedFilesystems = lib.mkForce [ "ext4" "vfat" ]; boot.kernelParams = lib.mkAfter [ "cfg80211.ieee80211_regdom=US" ]; networking.networkmanager.enable = lib.mkForce false; }; flash-pi-sd = pkgs.writeShellApplication { name = "flash-pi-sd"; runtimeInputs = [ agenix.packages.${system}.default pkgs.coreutils pkgs.nix pkgs.parted pkgs.systemd pkgs.util-linux pkgs.xz pkgs.zstd ]; text = '' set -euo pipefail PARTPROBE=${pkgs.parted}/bin/partprobe MOUNT=${pkgs.util-linux}/bin/mount UMOUNT=${pkgs.util-linux}/bin/umount MOUNTPOINT=${pkgs.util-linux}/bin/mountpoint FINDMNT=${pkgs.util-linux}/bin/findmnt UDEVADM=${pkgs.systemd}/bin/udevadm if [ "$#" -ne 1 ]; then echo "usage: flash-pi-sd /dev/sdX" >&2 exit 1 fi device="$1" flake_path=${builtins.toString ./.} zstd_bin=${pkgs.zstd}/bin/zstd secrets_dir=${builtins.toString ./secrets} rules_file=${builtins.toString ./secrets/secrets.nix} key_name="bootstrap-identity.age" boot_mount_dir="$(mktemp -d)" cleanup() { if "$MOUNTPOINT" -q "$boot_mount_dir"; then sudo "$UMOUNT" "$boot_mount_dir" fi rm -rf "$boot_mount_dir" } trap cleanup EXIT if [ ! -b "$device" ]; then echo "not a block device: $device" >&2 exit 1 fi boot_part="''${device}1" case "$device" in *[0-9]) boot_part="''${device}p1" ;; esac echo "Requesting sudo access before build and flash..." sudo -v echo "Sudo authentication successful." echo "Building full Pi NixOS image..." image_out="$(nix build \ --print-out-paths \ --cores 0 \ --max-jobs auto \ "$flake_path#nixosConfigurations.pi.config.system.build.sdImage")" image="$(echo "$image_out"/sd-image/*.img*)" if [ ! -f "$image" ]; then echo "failed to locate SD image under $image_out/sd-image" >&2 exit 1 fi echo "Flashing $image to $device..." if [ "''${image##*.}" = "zst" ]; then "$zstd_bin" -d --stdout "$image" | sudo dd of="$device" bs=16M conv=fsync status=progress elif [ "''${image##*.}" = "xz" ]; then xz -d -c "$image" | sudo dd of="$device" bs=16M conv=fsync status=progress else sudo dd if="$image" of="$device" bs=16M conv=fsync status=progress fi sync sudo "$PARTPROBE" "$device" sudo "$UDEVADM" settle if "$FINDMNT" -rn "$boot_part" >/dev/null 2>&1; then sudo "$UMOUNT" "$boot_part" fi echo "Installing bootstrap age identity onto $boot_part..." sudo "$MOUNT" "$boot_part" "$boot_mount_dir" ( cd "$secrets_dir" RULES="$rules_file" agenix -d "$key_name" ) | sudo tee "$boot_mount_dir/noisebell-bootstrap.agekey" >/dev/null sudo chmod 600 "$boot_mount_dir/noisebell-bootstrap.agekey" sync echo "Done. This is the full Pi NixOS image." ''; }; pi-serial = pkgs.writeShellApplication { name = "pi-serial"; runtimeInputs = [ pkgs.coreutils pkgs.procps pkgs.tio pkgs.util-linux ]; text = '' set -euo pipefail baud_rate=115200 data_bits=8 stop_bits=1 parity=none flow_control=none serial_tools="screen tio minicom picocom" port="" if [ "$#" -gt 1 ]; then echo "usage: pi-serial [device]" >&2 exit 1 fi if [ "$#" -eq 1 ]; then port="$1" else for candidate in /dev/serial/by-id/* /dev/ttyUSB* /dev/ttyACM*; do if [ -e "$candidate" ]; then port="$candidate" break fi done fi if [ -z "$port" ]; then echo "No serial device found." >&2 echo "Check the adapter and run: ls -l /dev/serial/by-id /dev/ttyUSB* /dev/ttyACM* 2>/dev/null" >&2 exit 1 fi log_file="pi-serial-$(date +%Y%m%d-%H%M%S).log" echo "Stopping old serial sessions for this user" for tool in $serial_tools; do pkill -x -u "$USER" "$tool" 2>/dev/null || true done sleep 1 echo "Waiting for port to become free: $port" while fuser "$port" >/dev/null 2>&1; do sleep 1 done echo "Using serial port: $port" echo "Logging to: $log_file" echo "Start this before powering the Pi." exec sudo tio \ -b "$baud_rate" \ -d "$data_bits" \ -s "$stop_bits" \ -p "$parity" \ -f "$flow_control" \ -t \ --log \ --log-file "$log_file" \ "$port" ''; }; in { packages.${system} = { inherit noisebell-cache noisebell-rss noisebell-discord flash-pi-sd pi-serial ; default = noisebell-cache; }; packages.aarch64-linux = { noisebell = noisebell-pi; noisebell-static = noisebell-pi-static; default = noisebell-pi; }; nixosModules = { cache = import ./remote/cache-service/module.nix noisebell-cache; rss = import ./remote/rss-service/module.nix noisebell-rss; discord = import ./remote/discord-bot/module.nix noisebell-discord; default = { imports = [ (import ./remote/cache-service/module.nix noisebell-cache) (import ./remote/rss-service/module.nix noisebell-rss) (import ./remote/discord-bot/module.nix noisebell-discord) (import ./remote/hosted-module.nix { inherit self agenix; }) ]; }; }; nixosConfigurations.pi = nixos-raspberrypi.lib.nixosSystem { specialArgs = { inherit nixos-raspberrypi; }; modules = [ nixos-raspberrypi.nixosModules.sd-image agenix.nixosModules.default piImageBaseModule (import ./pi/module.nix { pkg = noisebell-pi; rev = self.shortRev or "dirty"; }) ./pi/configuration.nix ]; }; devShells.${system}.default = craneLib.devShell { packages = [ agenix.packages.${system}.default flash-pi-sd pi-serial pkgs.nix pkgs.parted pkgs.rust-analyzer pkgs.tio pkgs.zstd ]; }; apps.${system} = { flash-pi-sd = { type = "app"; program = "${flash-pi-sd}/bin/flash-pi-sd"; }; pi-serial = { type = "app"; program = "${pi-serial}/bin/pi-serial"; }; }; }; }