bitspire/deploy/nixos/hardware/batm3.nix
Padreug 6042d69356 fix(deploy): tejo had no WireGuard address, so it had no way back in
`networking.wireguard.interfaces.wg0.ips` was set in hardware/douro.nix
and hardware/batm3.nix, but hardware/upboard.nix is shared by tejo and
sintra — an address there would be claimed by both machines on the same
/24, so neither got one. tejo therefore evaluated to `wg0.ips = [ ]`:
the interface comes up with no IP and the tunnel is silently dead. On a
machine with no other route in, that is how you lose a box.

Replace the two per-hardware definitions with one `wireguardIpForModel`
table in flake.nix, keyed on model like fiatCodeForModel /
upgradeWindowForModel / nfcReaderForModel, and give tejo 10.0.0.3/24 —
the address it answers on today under its factory Debian.

douro (10.0.0.4/24) and batm3 (10.0.0.5/24) evaluate unchanged; sintra
stays deliberately unlisted, since it is reachable on the LAN and has
never had a tunnel address.

The address is only half of it: the VPS maps peer pubkey to tunnel IP,
so the machine still needs /var/lib/wireguard/wg0.key carried over from
its previous install (or a fresh key added to the VPS peer list). Both
wireguard units are ConditionPathExists-guarded on that key, so a
keyless first boot is clean and the tunnel starts once it is dropped in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-06 19:26:14 +02:00

227 lines
9.2 KiB
Nix

# BATM3 Hardware Configuration
# Dell OptiPlex 9030 AIO, Intel i7-4790S, Intel HD 4600
# Board replacement for GeneralBytes BATM3 (ARM Android → x86 NixOS)
{ config, lib, pkgs, ... }:
{
boot = {
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
timeout = 3;
};
# Pin the 6.6 LTS kernel. The Dell 9030 AIO's eGalax SAW touch panel
# (0eef:0001) works with the usbtouchscreen driver on 6.6 (the known-good
# internal-SATA install runs 6.6.68). On 25.11's default 6.12 kernel this
# old controller regressed: hid-multitouch grabs it and mis-parses the HID
# report ("failed to fetch feature 7", axes read stuck), usbtouchscreen
# refuses it, and touch is unusable regardless of udev/X config. Matching
# douro.nix's per-hardware kernel pin. Re-test touch before bumping this.
kernelPackages = pkgs.linuxPackages_6_6;
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"usbhid"
"sd_mod"
# USB mass-storage: required to boot the dd'd image from a USB stick
# (stage-1 must bind the flash drive as a SCSI disk so
# /dev/disk/by-label/nixos appears). Harmless on the internal-SATA
# install, where ahci+sd_mod already cover the root device.
#
# NOTE: deliberately NO "uas" here. Many USB sticks/bridges advertise
# UAS but drop off the bus ("device offline error, dev sdb") under the
# sustained write load of first-boot growPartition/journal/swapfile.
# Blacklisting uas below forces the slower-but-reliable usb-storage
# (Bulk-Only Transport) path. SATA/eMMC installs don't use uas anyway.
"usb_storage"
];
# Keep the USB flash drive off the flaky UAS driver (see note above).
blacklistedKernelModules = [ "uas" ];
kernelModules = [
"kvm-intel"
"usbtouchscreen"
];
kernelParams = [
"quiet"
"splash"
# Disable USB autosuspend so the boot medium (and kiosk peripherals)
# aren't power-suspended mid-I/O — another cause of "device offline".
"usbcore.autosuspend=-1"
];
};
# Disk layout: GPT with ESP (sda1) + ext4 root (sda2)
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
hardware = {
graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
libva-vdpau-driver
libvdpau-va-gl
];
};
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
};
powerManagement = {
enable = true;
cpuFreqGovernor = "performance";
};
# The Feitian KP382 contactless reader (096e:0608) is declared as a machine
# capability, not here: `nfcReaderForModel` in flake.nix drives
# services.bitspire.nfc.enable, which owns pcscd, the polkit rules and the
# wedge-recovery unit (deploy/nixos/bitspire-atm.nix).
# Disable suspend/hibernate for kiosk
systemd.targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
# WiFi: read credentials from /var/lib/bitspire/wifi.conf (not in repo)
# Format: SSID=MyNetwork\nPSK=MyPassword
system.activationScripts.wifi-setup = ''
WIFI_CONF="/var/lib/bitspire/wifi.conf"
if [ -f "$WIFI_CONF" ]; then
SSID=$(grep '^SSID=' "$WIFI_CONF" | cut -d= -f2-)
PSK=$(grep '^PSK=' "$WIFI_CONF" | cut -d= -f2-)
if [ -n "$SSID" ] && [ -n "$PSK" ]; then
CONN_FILE="/etc/NetworkManager/system-connections/$SSID.nmconnection"
if [ ! -f "$CONN_FILE" ]; then
cat > "$CONN_FILE" << EOF
[connection]
id=$SSID
type=wifi
autoconnect=true
[wifi]
mode=infrastructure
ssid=$SSID
[wifi-security]
key-mgmt=wpa-psk
psk=$PSK
[ipv4]
method=auto
[ipv6]
method=auto
EOF
chmod 600 "$CONN_FILE"
echo "[WiFi] Created connection for $SSID"
fi
fi
fi
'';
# eGalax touchscreen (Dell 9030 AIO built-in panel)
# By default usbhid/hid-multitouch claim the eGalax and mis-parse its
# HID report descriptor (X axis reads as stuck), so touch is unusable.
# Fix: hand the device to the usbtouchscreen kernel driver, which parses
# the raw eGalax protocol into a clean single-touch ABS device that the
# X evdev driver + calibration matrix (below) map correctly. This mirrors
# the known-good internal-SATA install.
#
# The RUN command modprobes usbtouchscreen ITSELF before unbinding usbhid
# and handing over via new_id. usbtouchscreen is also in boot.kernelModules
# (systemd-modules-load), but on a USB boot systemd-udev-trigger fires this
# rule (~2s) BEFORE modules-load gets usbtouchscreen in (~12s) — so the
# new_id write hit a not-yet-loaded driver and the panel was left bound to
# nothing. Loading it inline here makes the handoff independent of that
# boot-ordering race (on internal-SATA boot the order happened to work).
services.udev.extraRules = lib.mkAfter ''
KERNEL=="ttyS[0-9]*", MODE="0666"
KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[0-9]*", MODE="0666"
SUBSYSTEM=="tty", ATTRS{serial}=="DDDLb103Y23", SYMLINK+="ttyF56", MODE="0666"
SUBSYSTEM=="tty", ATTRS{serial}=="A9YW78OC", SYMLINK+="ttyMEI", MODE="0666"
SUBSYSTEM=="tty", ATTRS{serial}=="A9ZF8ELY", SYMLINK+="ttyNFC", MODE="0666"
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="0eef", ATTRS{idProduct}=="0001", RUN+="${pkgs.bash}/bin/bash -c '${pkgs.kmod}/bin/modprobe usbtouchscreen 2>/dev/null; echo ''$kernel:1.0 > /sys/bus/usb/drivers/usbhid/unbind 2>/dev/null; echo 0eef 0001 > /sys/bus/usb/drivers/usbtouchscreen/new_id 2>/dev/null'"
# Belt-and-suspenders for touch calibration: on a slow USB boot the
# usbtouchscreen panel can bind AFTER egalax-calibrate's poll window, which
# leaves the panel uncalibrated and unresponsive ("dead"). (Re)start the
# calibration the instant the eGalax input node actually appears — this is
# device-driven, so it cannot lose a boot-timing race no matter how late the
# driver hands over. Pairs with egalax-calibrate's own (widened) poll loop.
ACTION=="add", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="eGalax Inc. USB TouchController", TAG+="systemd", ENV{SYSTEMD_WANTS}+="egalax-calibrate.service"
'';
# Force the X evdev driver on the eGalax (not libinput). The usbtouchscreen
# node is a plain single-touch absolute device; evdev + the transformation
# matrix in egalax-calibrate below give correct orientation. Mirrors the
# working internal-SATA install's /etc/X11/xorg.conf.d/99-egalax.conf.
environment.etc."X11/xorg.conf.d/99-egalax.conf".text = ''
Section "InputClass"
Identifier "eGalax Touchscreen"
MatchVendor "0eef"
MatchProduct "0001"
MatchDevicePath "/dev/input/event*"
Driver "evdev"
Option "InvertY" "false"
Option "InvertX" "false"
Option "SwapAxes" "false"
Option "Calibration" ""
EndSection
'';
# Apply touchscreen calibration after X11 starts
# Matrix: swap X/Y axes, invert both, scale to active panel area (238-1853 x 197-1869)
systemd.services.egalax-calibrate = {
description = "Calibrate eGalax touchscreen";
after = [ "display-manager.service" ];
requires = [ "display-manager.service" ];
wantedBy = [ "graphical.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "bitspire";
# DISPLAY *and* XAUTHORITY — without the auth cookie xinput dies with
# "Invalid MIT-MAGIC-COOKIE-1 key / Unable to connect to X server" and
# the matrix is never applied, so touches register but land in the wrong
# place (the panel then feels dead). This was the actual boot-time bug.
Environment = [ "DISPLAY=:0" "XAUTHORITY=/home/bitspire/.Xauthority" ];
# Wait for the eGalax X device to appear (usbtouchscreen binds a little
# after display-manager on a USB boot) and retry, instead of a fixed
# sleep — more robust to boot timing. 120s window: on a slow USB boot the
# panel has bound as late as ~30-60s after display-manager, so a 30s cap
# gave up before the device appeared and left touch dead (this service is
# ALSO re-triggered by a udev rule when the input node shows up, so this
# loop is the fallback, not the only path). Matrix: swap X/Y + invert +
# scale to the active panel area (matches the known-good internal install).
ExecStart = pkgs.writeShellScript "egalax-calibrate" ''
for i in $(${pkgs.coreutils}/bin/seq 1 120); do
if ${pkgs.xorg.xinput}/bin/xinput list --name-only 2>/dev/null | ${pkgs.gnugrep}/bin/grep -qx 'eGalax Inc. USB TouchController'; then
exec ${pkgs.xorg.xinput}/bin/xinput set-prop 'eGalax Inc. USB TouchController' \
'Coordinate Transformation Matrix' 0 -1.268 1.147 -1.224 0 1.118 0 0 1
fi
${pkgs.coreutils}/bin/sleep 1
done
echo "egalax-calibrate: eGalax device not found after 120s" >&2
exit 1
'';
};
};
# WireGuard VPN address → wireguardIpForModel in flake.nix.
}