bitspire/flake.nix
Padreug 7e59951fcc wip(rpi4): park the Pi 4 sketch — superseded by the Pi 5 machinery in #87
Never-committed working tree state found in the dev worktree: a Pi 4 hardware
module, a setup walkthrough, an upboard-serial refactor, and flake wiring that
does not evaluate (aarch64 packages block nested inside packages.x86_64-linux;
pkgs-aarch64 built with `inherit aarch64` instead of `system`, so it is really
x86; references a nixosConfigurations.rpi4-installed that is never defined).

Parked verbatim for reference. The real Pi 4 target is rebuilt on top of #87's
mkPiInstalled/mkPiImage shape in feat/rpi4-target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
2026-09-20 11:17:41 +02:00

555 lines
26 KiB
Nix

{
description = "bitSpire - Nostr-Native Lightning ATM";
inputs = {
# Stable NixOS for the ATM OS base
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
# Stable NixOS for the ATM OS base (aarch64) — Raspberry Pi 4
nixpkgs-aarch64.url = "github:NixOS/nixpkgs/nixos-25.11";
# Unstable for Electron, Node.js, pnpm (latest versions)
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
devenv = {
url = "github:cachix/devenv";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
# Determinate Nix — ensures douro uses same nix version as dev machines
# so cachix binary cache hits match (nix 2.33 hashes == nix 2.33 hashes).
# The bare `?3` semver pin (≥3.0.0) was resolving to 3.16.3, which
# ships a regressed nix-functional-tests:local-overlay-store /
# stale-file-handle that FAILs when the determinate-nix derivation
# has to be built from source (no binary cache hit) — this blocked
# disk-image-sintra builds locally. `?3.15` (semver ≥3.15.0) skips
# past the regression; flake.lock currently resolves it to 3.20.0,
# which builds and ships nix 2.34.6.
determinate.url = "https://flakehub.com/f/DeterminateSystems/determinate/3.15";
# ATM TUI — operator management tool
atm-tui = {
url = "git+ssh://forgejo@git.atitlan.io/aiolabs/atm-tui.git";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
};
outputs = { self, nixpkgs, nixpkgs-unstable, nixpkgs-aarch64, flake-utils, rust-overlay, devenv, determinate, atm-tui }:
let
system = "x86_64-linux";
aarch64 = "aarch64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
pkgs-aarch64 = import nixpkgs-aarch64 {
inherit aarch64;
config.allowUnfree = true;
};
pkgs-unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = true;
overlays = [ (import rust-overlay) ];
};
# Pure ATM app builder (no --impure needed)
mkAtmApp = import ./nix/mkAtmApp.nix {
inherit pkgs pkgs-unstable;
src = self;
};
# Fiat code per machine model
fiatCodeForModel = {
douro = "GTQ";
tejo = "GTQ";
sintra = "EUR";
batm3 = "USD";
};
lib = nixpkgs.lib;
# Helper to create a live USB NixOS config for a specific machine model
mkLiveConfig = machineModel:
let
atm-app = mkAtmApp {
model = machineModel;
fiatCode = fiatCodeForModel.${machineModel} or "USD";
};
in
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit pkgs-unstable nixpkgs machineModel atm-app;
};
modules = [
./deploy/nixos/live.nix
determinate.nixosModules.default
{
environment.systemPackages = [
atm-tui.packages.${system}.default
(pkgs.writeShellScriptBin "fund-atm" ''
exec ${pkgs-unstable.nodejs}/bin/node ${atm-app}/dist-electron/fund-atm.bundle.cjs "$@"
'')
];
environment.variables.ATM_DB_PATH = "/var/lib/bitspire/state.db";
}
];
};
# Helper to create a disk-installed NixOS config for a specific machine model.
# Unlike live configs (squashfs + tmpfs), installed configs use ext4 root
# and support `nixos-rebuild switch` for in-place updates.
mkInstalledConfig = machineModel: hardwareModule:
let
atm-app = mkAtmApp {
model = machineModel;
fiatCode = fiatCodeForModel.${machineModel} or "USD";
};
fiatCode = fiatCodeForModel.${machineModel} or "USD";
in
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit pkgs-unstable atm-app;
};
modules = [
hardwareModule
./deploy/nixos/configuration.nix
./deploy/nixos/bitspire-atm.nix
determinate.nixosModules.default
({ config, lib, pkgs, ... }: {
services.bitspire = {
enable = true;
appDir = "${atm-app}";
};
# Operator TUI and CLI tools
environment.systemPackages = [
atm-tui.packages.${system}.default
(pkgs.writeShellScriptBin "fund-atm" ''
exec ${pkgs-unstable.nodejs}/bin/node ${atm-app}/dist-electron/fund-atm.bundle.cjs "$@"
'')
];
environment.variables.ATM_DB_PATH = "/var/lib/bitspire/state.db";
# Electron sandbox needs unprivileged user namespaces
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
# Passwordless sudo for remote nixos-rebuild switch
security.sudo.wheelNeedsPassword = false;
# Allow bitspire user to use nix commands + pull from aiolabs binary cache.
# max-jobs = 1: prefer substitution from the cache, but allow ONE
# local build slot for tiny activation-time stitch derivations
# (boot.json, system-units, X-Restart-Triggers, etc.) that are
# inherently per-machine and can never be pre-cached. Heavy
# nixpkgs compiles (rustc, kernel, electron) are still
# effectively cache-only — they're upstream-cached, so a cache
# miss on them stays vanishingly rare in practice.
# max-jobs = 0 was tried first and silently bricked nightly
# auto-upgrades for 6+ days on an uncacheable trivial derivation
# (systemd-boot's boot.json).
nix.settings = {
max-jobs = 1;
# Hard ceiling on any local build's wall-clock time. Activation-
# time stitch derivations (boot.json, system-units, etc.) finish
# in well under a second; anything that doesn't return in 60s
# is by definition a heavy compile that has no business running
# on ATM hardware (kernel, electron, rustc). Kill it fast so
# the upgrade fails loudly instead of silently wedging the box
# for an hour. Time-bounds the max-jobs=1 escape hatch.
timeout = 60;
trusted-users = [ "root" "bitspire" ];
substituters = [ "https://cache.nixos.org" "https://aiolabs.cachix.org" ];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"aiolabs.cachix.org-1:PrAjsGU9PE77tFKP2+iO+mgR88c4xv3utM9JmpTblUQ="
];
};
# Auto-upgrade: pulls latest flake and runs nixos-rebuild switch.
# NOTE: bitSpire machines pull from the `aiolabs/bitspire` repo —
# the post-migration home of this code. This branch (dev) pins the
# upgrade source to ?ref=dev so any ATM flashed from `dev` stays on
# `dev`. Without the explicit ?ref=dev, nix would resolve the repo's
# default branch and could silently change a dev-deployed Sintra at
# 04:00. The legacy `aiolabs/lamassu-next` repo still feeds the
# not-yet-converted production ATMs (batm3, douro) from its own
# branches; it is retired once those machines migrate to bitspire.
# To update manually: sudo nixos-rebuild switch --flake git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=dev#<model>-installed
system.autoUpgrade = {
enable = true;
flake = "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=dev#${machineModel}-installed";
flags = [ "--refresh" ];
dates = "04:00"; # daily at 4am
allowReboot = false;
};
# Minimal env template (aiolabs/bitspire#70 remnant hygiene).
# Seed ONLY image-baked, non-maskable values. Everything else the
# ATM needs comes from the pairing SEED (relay, lnbits_npub, bunker)
# or from LNbits over the transport (operator pubkey, fee config) —
# so we must NOT pre-seed those keys. A present-but-empty
# VITE_RELAY_URL / VITE_LNBITS_SERVER_PUBKEY / VITE_OPERATOR_PUBKEYS
# is a masking hazard: env WINS over the seed, and this activation
# only writes when .env is ABSENT, so any value written at first
# boot is frozen for the life of the disk. Leaving the keys out
# entirely lets the seed/transport be the sole source.
#
# VITE_RELAY_URL / VITE_LNBITS_SERVER_PUBKEY are emitted ONLY when
# the operator deliberately pins them via the Nix options (non-empty
# default ""), which is an explicit override that wins over the seed.
system.activationScripts.bitspire-env = ''
mkdir -p /var/lib/bitspire
if [ ! -f /var/lib/bitspire/.env ]; then
cp ${pkgs.writeText "bitspire-env-default" (''
VITE_LAMASSU_MACHINE_MODEL=${machineModel}
VITE_LAMASSU_FIAT_CODE=${fiatCode}
VITE_SPIRE_SEED=
ELECTRON_FORCE_PROD=1
DISPLAY=:0
'' + pkgs.lib.optionalString (config.services.bitspire.relayUrl != "") ''
VITE_RELAY_URL=${config.services.bitspire.relayUrl}
'' + pkgs.lib.optionalString (config.services.bitspire.lnbitsServerPubkey != "") ''
VITE_LNBITS_SERVER_PUBKEY=${config.services.bitspire.lnbitsServerPubkey}
'')} /var/lib/bitspire/.env
chmod 600 /var/lib/bitspire/.env
chown bitspire:bitspire /var/lib/bitspire/.env
fi
'';
# Override systemd service for Electron runtime
systemd.services.bitspire = {
serviceConfig = {
EnvironmentFile = lib.mkForce "/var/lib/bitspire/.env";
Environment = "LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib";
ExecStart = lib.mkForce "${pkgs-unstable.electron}/bin/electron --no-sandbox --disable-gpu-sandbox --disable-gpu --disable-software-rasterizer --enable-logging ${atm-app}";
MemoryMax = lib.mkForce "1G";
NoNewPrivileges = lib.mkForce false;
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
PrivateTmp = lib.mkForce false;
DevicePolicy = lib.mkForce "auto";
DeviceAllow = lib.mkForce [ "char-* rw" ];
};
};
# Reset eDP display output after X starts
systemd.services.display-reset = {
description = "Reset eDP display output";
after = [ "display-manager.service" ];
requires = [ "display-manager.service" ];
wantedBy = [ "graphical.target" ];
before = [ "bitspire.service" ];
serviceConfig = {
Type = "oneshot";
User = "bitspire";
Environment = "DISPLAY=:0";
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.xorg.xrandr}/bin/xrandr --output eDP-1 --off; sleep 1; ${pkgs.xorg.xrandr}/bin/xrandr --output eDP-1 --auto'";
};
};
# Swap file — ATMs have ~2GB RAM; prevents hard-freeze under memory pressure
swapDevices = [{ device = "/var/swapfile"; size = 1024; }];
# Clean /tmp on boot to prevent stale build artifacts filling disk
boot.tmp.cleanOnBoot = true;
# SSH with password for initial provisioning
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
})
];
};
in
{
# ── NixOS Configurations (top-level, not per-system) ──────────
nixosConfigurations = {
# Ergonomic names: `nixos-rebuild switch --flake .#douro`
douro = mkLiveConfig "douro";
tejo = mkLiveConfig "tejo";
sintra = mkLiveConfig "sintra";
batm3 = mkLiveConfig "batm3";
# Backwards-compat aliases. Renamed lamassu-live-* → bitSpire-live-*
# for the brand transition; both styles available until callers
# (CI / scripts / docs) catch up. Drop the lamassu-* names once
# nothing references them.
bitSpire-live-douro = mkLiveConfig "douro";
bitSpire-live-tejo = mkLiveConfig "tejo";
bitSpire-live-sintra = mkLiveConfig "sintra";
bitSpire-live = mkLiveConfig "douro";
lamassu-live-douro = mkLiveConfig "douro";
lamassu-live-tejo = mkLiveConfig "tejo";
lamassu-live-sintra = mkLiveConfig "sintra";
lamassu-live = mkLiveConfig "douro";
# Installed-to-disk configs (proper GPT + systemd-boot, supports nixos-rebuild)
douro-installed = mkInstalledConfig "douro" ./deploy/nixos/hardware/douro.nix;
tejo-installed = mkInstalledConfig "tejo" ./deploy/nixos/hardware/upboard.nix;
# Sintra shares Aaeon UP Board hardware with tejo (same validator
# at ttyJ5, dispenser at ttyJ7 layout) — reuse the same hw module.
sintra-installed = mkInstalledConfig "sintra" ./deploy/nixos/hardware/upboard.nix;
batm3-installed = mkInstalledConfig "batm3" ./deploy/nixos/hardware/batm3.nix;
# USB-bootable variant of batm3-installed. This is the config the
# flashed USB stick actually runs — distinct fs labels so stage-1 can't
# latch the internal drive, nofail /boot, no growPartition, autoUpgrade
# off. Exposed as a named config (not just inline in the disk-image
# target) so its system closure can be built here and deployed in-place
# with `nix copy` + `switch-to-configuration` — updating the app on a
# running stick WITHOUT reflashing (preserves pairing + /var/lib state).
# disk-image-batm3-usb builds its filesystem image from this same config.
batm3-usb = self.nixosConfigurations.batm3-installed.extendModules {
modules = [
({ lib, ... }: {
fileSystems."/".device = lib.mkForce "/dev/disk/by-label/nixos-usb";
fileSystems."/boot".device = lib.mkForce "/dev/disk/by-label/ESP-USB";
# /boot must NOT be a hard boot dependency on the USB image. The
# firmware already loaded the bootloader before Linux; without
# nofail, a slow/late ESP-USB enumeration (BOT is slower than UAS)
# blows past systemd's 90s device-timeout into emergency mode with
# root locked — a dead end. nofail + short timeout lets the
# already-mounted root carry the boot; /boot mounts if/when it shows.
fileSystems."/boot".options = [ "nofail" "x-systemd.device-timeout=10s" ];
# NO growPartition/autoResize: sfdisk rewriting the partition table
# on first boot is the single most bus-stressing write, and flaky
# USB bridges drop off the bus mid-rewrite (sfdisk wedges in D-state
# and ESP-USB vanishes with the device). Persistent state is a few
# MB and the image ships ~2GB free. The internal-SATA disk-image-
# batm3 keeps growPartition (a real AHCI SSD won't drop the bus).
system.autoUpgrade.enable = lib.mkForce false;
})
];
};
};
# ── Standalone NixOS module ───────────────────────────────────
nixosModules.default = import ./deploy/nixos/bitspire-atm.nix;
nixosModules.bitspire = import ./deploy/nixos/bitspire-atm.nix;
# ── Packages (x86_64-linux only for ATM hardware) ─────────────
packages.${system} = {
# Pure ATM app derivations
atm-app-douro = mkAtmApp { model = "douro"; fiatCode = "GTQ"; };
atm-app-tejo = mkAtmApp { model = "tejo"; fiatCode = "GTQ"; };
atm-app-sintra = mkAtmApp { model = "sintra"; fiatCode = "EUR"; };
atm-app-batm3 = mkAtmApp { model = "batm3"; fiatCode = "USD"; };
# ISO images
iso-douro = self.nixosConfigurations.douro.config.system.build.isoImage;
iso-tejo = self.nixosConfigurations.tejo.config.system.build.isoImage;
iso-sintra = self.nixosConfigurations.sintra.config.system.build.isoImage;
iso-batm3 = self.nixosConfigurations.batm3.config.system.build.isoImage;
# Raw disk images (dd-able to mSATA/eMMC, proper GPT + ESP)
disk-image-douro = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = self.nixosConfigurations.douro-installed.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
};
disk-image-sintra = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = self.nixosConfigurations.sintra-installed.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
};
# BATM3 (OptiPlex 9030 AIO board-swap) installed image, dd-able to
# its SATA drive. Unlike the douro/sintra images, this one grows
# itself: growPartition expands the root partition to fill whatever
# drive it lands on (16GB today) at first boot and autoResize
# stretches the ext4 to match — no manual parted/resize2fs step
# after flashing, and all the drive's headroom is available to the
# nix store from day one (cf. #55). Image-only override: once
# grown, subsequent nixos-rebuilds against plain batm3-installed
# are unaffected.
disk-image-batm3 =
let
cfg = self.nixosConfigurations.batm3-installed.extendModules {
modules = [
{
boot.growPartition = true;
fileSystems."/".autoResize = true;
}
];
};
in
import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = cfg.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
};
# USB-bootable Sintra image with DISTINCT partition labels
# (nixos-usb / ESP-USB) so the stick can be booted on a Sintra whose
# eMMC already holds a nixos/ESP-labelled install without a by-label
# collision — stage-1 would otherwise race between the two roots and
# likely mount the eMMC. Auto-upgrade is disabled: this is a portable
# test / hand-off image, not a managed fleet member, and disabling it
# also removes the scheduled bootloader writes that could otherwise
# land on the eMMC's ESP.
disk-image-sintra-usb =
let
cfg = self.nixosConfigurations.sintra-installed.extendModules {
modules = [
({ lib, ... }: {
fileSystems."/".device = lib.mkForce "/dev/disk/by-label/nixos-usb";
fileSystems."/boot".device = lib.mkForce "/dev/disk/by-label/ESP-USB";
system.autoUpgrade.enable = lib.mkForce false;
# The Sintra's Aaeon firmware USB-boots in Legacy/BIOS mode — it
# boots the live ISO via its isolinux (BIOS) El Torito image, not
# the UEFI ESP. systemd-boot is UEFI-only, so a dd'd systemd-boot
# image isn't recognised as bootable. Switch THIS USB image to
# GRUB with BOTH BIOS (MBR + bios_grub partition, via the "hybrid"
# table below) and UEFI (removable /EFI/BOOT/BOOTX64.EFI) — mirroring
# the live ISO's dual boot — so it boots on Legacy and UEFI alike.
# Scoped to the USB image; the eMMC install keeps systemd-boot.
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.loader.efi.canTouchEfiVariables = lib.mkForce false;
boot.loader.grub = {
enable = lib.mkForce true;
efiSupport = true;
efiInstallAsRemovable = true;
# make-disk-image's build VM exposes the image as /dev/vda;
# GRUB installs its BIOS stage to that disk's MBR.
devices = lib.mkForce [ "/dev/vda" ];
};
})
];
};
baseImage = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = cfg.config;
format = "raw";
# hybrid = GPT + bios_grub partition + ESP → BIOS + UEFI bootable.
partitionTableType = "hybrid";
diskSize = "auto";
label = "nixos-usb"; # ext4 root label (make-disk-image -L)
};
in
pkgs.runCommand "nixos-disk-image-sintra-usb"
{ nativeBuildInputs = [ pkgs.parted pkgs.mtools ]; }
''
mkdir -p $out
cp --sparse=always ${baseImage}/nixos.img $out/nixos.img
chmod +w $out/nixos.img
# make-disk-image hardcodes the ESP FAT label to "ESP"; relabel the
# volume to ESP-USB so /boot (by-label/ESP-USB) doesn't collide with
# the eMMC's ESP. Volume label only — bootloader files are untouched,
# and UEFI loads /EFI/BOOT/BOOTX64.EFI regardless of the label.
espStart=$(parted -sm "$out/nixos.img" unit B print | awk -F: '$1==1 {gsub("B","",$2); print $2}')
echo "ESP partition starts at byte $espStart — relabelling to ESP-USB"
export MTOOLS_SKIP_CHECK=1
mlabel -i "$out/nixos.img@@$espStart" ::ESP-USB
printf 'verify ESP label: '; mlabel -i "$out/nixos.img@@$espStart" -s :: || true
'';
# USB-bootable BATM3 TEST image with DISTINCT partition labels
# (nixos-usb / ESP-USB). The plain disk-image-batm3 reuses the generic
# nixos/ESP labels, so a USB stick carrying it, booted on a batm3 whose
# internal SATA drive ALREADY holds a nixos/ESP-labelled install, makes
# stage-1's by-label/nixos resolve to the internal drive (larger fs,
# journal recovers) instead of the stick — the stage-2 init path baked
# into the USB's boot entry isn't on that root, so stage 1 aborts.
# Distinct labels make stage-1 pick the stick unambiguously WITHOUT
# touching the internal drive. Unlike disk-image-sintra-usb this keeps
# systemd-boot: the batm3 firmware UEFI-USB-boots fine via the ESP's
# /EFI/BOOT/BOOTX64.EFI removable fallback, so no GRUB/hybrid-table
# change is needed — only the label disambiguation here plus the
# usb_storage/uas initrd modules (in batm3.nix). Does NOT grow to fill
# the stick (see the growPartition note below — sfdisk on first boot
# wedges flaky USB bridges); auto-upgrade off (test image, not a managed
# fleet member — also stops scheduled bootloader writes landing on the
# internal drive's ESP).
disk-image-batm3-usb =
let
# Filesystem image of the batm3-usb config (defined in
# nixosConfigurations). Same config that in-place deploys target, so
# a reflash and a `switch-to-configuration` converge on one system.
baseImage = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = self.nixosConfigurations.batm3-usb.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
label = "nixos-usb"; # ext4 root label (make-disk-image -L)
};
in
pkgs.runCommand "nixos-disk-image-batm3-usb"
{ nativeBuildInputs = [ pkgs.parted pkgs.mtools ]; }
''
mkdir -p $out
cp --sparse=always ${baseImage}/nixos.img $out/nixos.img
chmod +w $out/nixos.img
# make-disk-image hardcodes the ESP FAT label to "ESP"; relabel the
# volume to ESP-USB so /boot (by-label/ESP-USB) can't resolve to an
# internal drive's ESP. Volume label only — bootloader files are
# untouched, and UEFI loads /EFI/BOOT/BOOTX64.EFI regardless.
espStart=$(parted -sm "$out/nixos.img" unit B print | awk -F: '$1==1 {gsub("B","",$2); print $2}')
echo "ESP partition starts at byte $espStart — relabelling to ESP-USB"
export MTOOLS_SKIP_CHECK=1
mlabel -i "$out/nixos.img@@$espStart" ::ESP-USB
printf 'verify ESP label: '; mlabel -i "$out/nixos.img@@$espStart" -s :: || true
'';
# Backwards compat
# aarch64-linux packages (Raspberry Pi 4)
packages.${aarch64} = {
# ATM app derivation for Raspberry Pi 4
atm-app-rpi4 = mkAtmApp { model = "rpi4"; fiatCode = "USD"; };
# Raspberry Pi 4 disk image (aarch64)
# Note: Raspberry Pi 4 uses the Raspberry Pi firmware bootloader,
# not systemd-boot. The image is built for aarch64 and booted via
# the rpi-bootloader (rpi-firmware). Requires an SD card with GPT
# partition table (ESP + ext4 root).
disk-image-rpi4 = import (nixpkgs-aarch64 + "/nixos/lib/make-disk-image.nix") {
pkgs = pkgs-aarch64; inherit lib;
config = self.nixosConfigurations.rpi4-installed.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
};
};
iso = self.nixosConfigurations.douro.config.system.build.isoImage;
};
}
//
# ── Dev shells (per-system via flake-utils) ───────────────────
flake-utils.lib.eachDefaultSystem (sys:
let
dev-pkgs = import nixpkgs-unstable {
system = sys;
overlays = [ (import rust-overlay) ];
};
in
{
devShells.default = devenv.lib.mkShell {
pkgs = dev-pkgs;
modules = [ ./devenv.nix ];
};
}
);
}