diff --git a/deploy/nixos/configuration.nix b/deploy/nixos/configuration.nix index d473a42..2306f5a 100644 --- a/deploy/nixos/configuration.nix +++ b/deploy/nixos/configuration.nix @@ -7,6 +7,16 @@ # System basics system.stateVersion = "24.05"; + # ── Image slimming (bitspire#70 sizing) ────────────────────────────── + # This is a single-purpose Electron kiosk; strip the desktop/multimedia + # baggage NixOS pulls in by default so the disk image stays lean. + # - speechd: text-to-speech (speech-dispatcher → espeak-ng → mbrola, ~1GB). + # An ATM does not talk. + # - documentation: man/info/NixOS manual — no one reads them on a kiosk. + services.speechd.enable = lib.mkForce false; + documentation.enable = false; + documentation.nixos.enable = false; + # Networking networking = { hostName = "bitspire"; @@ -121,8 +131,10 @@ # Node.js for the application pkgs-unstable.nodejs_22 - # Camera support - v4l-utils + # Camera support. v4l-utils' default build drags in the whole Qt6 stack + # for its qv4l2 GUI (~0.5GB) — we only ever use the v4l2-ctl CLI, so drop + # the GUI. + (v4l-utils.override { withGUI = false; }) fswebcam # ATM operations diff --git a/deploy/nixos/hardware/upboard-serial.nix b/deploy/nixos/hardware/upboard-serial.nix new file mode 100644 index 0000000..dfbf947 --- /dev/null +++ b/deploy/nixos/hardware/upboard-serial.nix @@ -0,0 +1,61 @@ +# UP Board serial peripherals — the validator / dispenser / printer wiring +# shared by the INSTALLED configs (hardware/upboard.nix, used by both +# tejo-installed and sintra-installed) AND the sintra live ISO (live.nix). +# Single source of truth so the two artifacts can't drift — the earlier bug +# was exactly this drift (the sintra live ISO lacked ftdi_sio + the ttyJ7 +# symlink, so the F56 dispenser failed while the installed image worked). +# +# Sintra IS a UP Board, so these are the UP Board rules; ttyS1/ttyS5 cover the +# older UP Board / UP4000 (Tejo) dispenser nodes and ttyS4 covers the Sintra +# (Apollo Lake) where the F56 is on the SoC MMIO UART. Only the device that +# actually exists at runtime gets the symlink, so all three coexist safely. +# +# Serial port mapping: +# ttyJ4 = Printer (Nippon NP-2511D-2) +# ttyJ5 = Validator (iVIZION, ID003) +# ttyJ7 = Dispenser (Fujitsu F53/F56) +{ lib, ... }: + +{ + boot.kernelModules = [ + "usbserial" # USB-to-serial adapters + "ftdi_sio" # FTDI USB serial (the iVIZION validator bridge) + "cp210x" # CP210x USB serial (alternative adapter) + ]; + + boot.kernelParams = [ + # Do NOT route the kernel console through ttyS4 on Sintra. ttyS4 is the + # SoC's MMIO 16550A (the only real UART besides the legacy ttyS0 at I/O + # 0x3f8) and is wired to the Fujitsu F56 dispenser's RS-232 header. Holding + # it as console prevents userspace opening it at 9600 baud and HAL fails + # with "Input/output error setting custom baud rate of 9600". For serial + # debug, point console at ttyS0 instead. + "console=tty0" + ]; + + services.udev.extraRules = lib.mkAfter '' + # Generic serial port permissions (so the non-root HAL user can open them) + KERNEL=="ttyS[0-9]*", MODE="0666" + KERNEL=="ttyUSB[0-9]*", MODE="0666" + KERNEL=="ttyACM[0-9]*", MODE="0666" + + # Printer (ttyJ4) + KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4" + KERNEL=="ttyUSB0", SYMLINK+="ttyJ4" + + # Validator (ttyJ5) + KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5" + KERNEL=="ttyUSB1", SYMLINK+="ttyJ5" + + # Dispenser (ttyJ7). ttyS1/ttyS5 = older UP Board / UP4000; ttyS4 = Sintra. + KERNEL=="ttyS1", SYMLINK+="ttyJ7" + KERNEL=="ttyS4", SYMLINK+="ttyJ7" + KERNEL=="ttyS5", SYMLINK+="ttyJ7" + + # Legacy ttyAMA0 alias + SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout" + + # Disable USB autosuspend (prevents serial adapters from sleeping) + ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="on" + ''; +} diff --git a/deploy/nixos/hardware/upboard.nix b/deploy/nixos/hardware/upboard.nix index b3fe1f3..d213ff7 100644 --- a/deploy/nixos/hardware/upboard.nix +++ b/deploy/nixos/hardware/upboard.nix @@ -11,6 +11,10 @@ { config, lib, pkgs, ... }: { + # Serial peripherals (validator/dispenser/printer modules + udev symlinks + + # console=tty0) are shared with the live ISO via ./upboard-serial.nix. + imports = [ ./upboard-serial.nix ]; + boot = { loader = { systemd-boot.enable = true; @@ -42,21 +46,12 @@ "kvm-intel" "i2c-dev" "spi-dev" - "usbserial" # USB-to-serial adapters - "ftdi_sio" # FTDI USB serial - "cp210x" # CP210x USB serial + # Serial modules (usbserial/ftdi_sio/cp210x) → ./upboard-serial.nix. ]; kernelParams = [ "i915.enable_psr=0" - # NOTE: do NOT route the kernel console through ttyS4 on Sintra. - # ttyS4 is the SoC's MMIO 16550A (the only real UART besides the - # legacy ttyS0 at I/O 0x3f8) and is wired to the Fujitsu F56 - # dispenser's RS-232 header on Sintra. Holding it as console - # prevents userspace from opening it at 9600 baud and HAL fails - # with "Input/output error setting custom baud rate of 9600". - # If you want serial debug, point console at ttyS0 instead. - "console=tty0" + # console=tty0 (keeps ttyS4 free for the F56) → ./upboard-serial.nix. "quiet" "splash" ]; @@ -104,37 +99,9 @@ hybrid-sleep.enable = false; }; - # Serial port permissions + tejo-specific symlinks + # Camera + LED/SPI peripherals. The serial rules (validator/dispenser/printer + # symlinks + permissions) are shared with the live ISO in ./upboard-serial.nix. services.udev.extraRules = lib.mkAfter '' - # Generic serial port permissions - KERNEL=="ttyS[0-9]*", MODE="0666" - KERNEL=="ttyUSB[0-9]*", MODE="0666" - KERNEL=="ttyACM[0-9]*", MODE="0666" - - # ── Tejo serial port symlinks ────────────────────────────────────── - # Both UP Board and UP4000 rules included (match different kernel paths) - - # Printer (ttyJ4) - KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4" - KERNEL=="ttyUSB0", SYMLINK+="ttyJ4" - - # Validator (ttyJ5) - KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5" - KERNEL=="ttyUSB1", SYMLINK+="ttyJ5" - - # Dispenser (ttyJ7). - # ttyS1 / ttyS5 cover earlier UP Board variants where the dispenser - # lands on those kernel-enumerated serial nodes; ttyS4 covers the - # Sintra (UP Board Atom/Apollo Lake) where the dispenser is wired - # to the SoC's MMIO UART. Whichever device actually exists at - # runtime gets the ttyJ7 symlink. - KERNEL=="ttyS1", SYMLINK+="ttyJ7" - KERNEL=="ttyS4", SYMLINK+="ttyJ7" - KERNEL=="ttyS5", SYMLINK+="ttyJ7" - - # Legacy ttyAMA0 alias - SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout" - # ── Camera devices ───────────────────────────────────────────────── SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-5", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan" SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-2", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan" @@ -147,8 +114,5 @@ SUBSYSTEM=="spidev", GROUP="spi", MODE="0660" SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660" SUBSYSTEM=="leds", KERNEL=="upboard:*", ACTION=="add|change", RUN+="${pkgs.findutils}/bin/find /sys$devpath -type f -exec ${pkgs.coreutils}/bin/chmod g+u {} + -exec ${pkgs.coreutils}/bin/chown :leds {} +" - - # Disable USB autosuspend (prevents serial adapters from sleeping) - ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="on" ''; } diff --git a/deploy/nixos/live.nix b/deploy/nixos/live.nix index 698e0bb..7c5682f 100644 --- a/deploy/nixos/live.nix +++ b/deploy/nixos/live.nix @@ -47,7 +47,12 @@ in # Reuse ATM systemd service module ./bitspire-atm.nix - ]; + ] + # Sintra: share the UP Board serial hardware (validator/dispenser/printer + # modules + udev symlinks + console=tty0) with the installed image so the + # live ISO drives the same hardware. Safe to import here — unlike upboard.nix + # it declares no fileSystems, so there's no live-boot mount conflict. + ++ lib.optionals (machineModel == "sintra") [ ./hardware/upboard-serial.nix ]; # ISO image settings image.fileName = "bitspire-${machineModel}-live.iso"; diff --git a/flake.nix b/flake.nix index 9305991..63296c0 100644 --- a/flake.nix +++ b/flake.nix @@ -347,6 +347,25 @@ 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" ]; + }; }) ]; }; @@ -354,7 +373,8 @@ inherit pkgs lib; config = cfg.config; format = "raw"; - partitionTableType = "efi"; + # hybrid = GPT + bios_grub partition + ESP → BIOS + UEFI bootable. + partitionTableType = "hybrid"; diskSize = "auto"; label = "nixos-usb"; # ext4 root label (make-disk-image -L) };