`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>
pcscd was enabled in hardware/batm3.nix and hardware/upboard.nix, which
cannot express "is a reader fitted": upboard.nix is shared by sintra (HID
Global OMNIKEY 5022) and tejo (nothing fitted), so tejo inherited pcscd it
has no use for, while the douro — with its own hardware file — got none and
wedged on every boot.
Make it a machine capability instead. services.bitspire.nfc.enable owns
pcscd, the two polkit rules and the wedge-recovery unit, and hands the app
a BITSPIRE_NFC_ENABLED flag so it doesn't initialise nfc-pcsc at all on a
machine with no reader. Per-model truth lives in nfcReaderForModel in
flake.nix next to fiatCodeForModel and upgradeWindowForModel, since a
shared hardware file can't answer the question. batm3 and sintra are true;
douro and tejo flip to true when readers are fitted.
The flag goes through the unit's Environment rather than
/var/lib/bitspire/.env, because .env is only written when absent — a
machine provisioned months ago would never pick up a new value.
The Feitian R502-CL (and cheap CCID readers generally) can wedge: it keeps
detecting a card but every APDU returns "card absent or mute", and ONLY a
USB power-cycle clears it — restarting pcscd or the app does not (confirmed
on-device). Until now that left cash-out/cash-in taps dead until a manual
replug.
- nfc-service.ts: count consecutive read failures; after 3 (gated by a 30s
cooldown so a still-wedged reader can't reset-loop) trigger
nfc-reader-reset.service. nfc-pcsc then re-detects the reader on USB
hotplug with no app restart (verified live).
- batm3.nix: nfc-reader-reset.service (oneshot, root) re-binds the reader's
USB device (a software replug); reader-agnostic via the CCID interface
class (0x0B) so it also covers a future ACR1252U. A polkit rule lets the
unprivileged `bitspire` app start just that one unit.
Hardware track (separate): the durable fix is a better reader (ACR1252U —
large antenna for behind-panel, firmware-upgradable). This change makes any
reader's wedge a ~2s self-heal in the meantime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
egalax-calibrate polls 30s for the eGalax X device then gives up; on a
slow USB boot usbtouchscreen binds the panel later than that, so the
calibration matrix is never applied and touch registers in the wrong
place ("dead" panel). Seen on cold boots (2/2 today), fine on others —
a nondeterministic race, not a regression.
- Add a udev rule that (re)starts egalax-calibrate the instant the eGalax
input node appears (SYSTEMD_WANTS) — device-driven, can't lose the race.
- Widen the calibrate poll window 30s -> 120s as a fallback.
Recovery when it does strand: `systemctl restart egalax-calibrate`, or
apply the matrix live via xinput set-prop.
pcscd gates clients via polkit; the sandboxed bitspire user was "Rejected
unauthorized PC/SC client", so add a polkit rule granting it
access_pcsc/access_card. Also log NFC reader status + taps from the main
process to journald (value redacted — it carries the card's SUN p/c) so
reader detection and taps are observable during testing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Feitian KP382 (096e:0608) is a CCID contactless reader; PC/SC must be
running for the CCID driver to bind it. The app will talk to pcscd's socket
via nfc-pcsc. Idle/harmless when no reader is attached.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Dell 9030 AIO's built-in eGalax SAW panel (0eef:0001) was unusable:
touches either didn't register or landed in the wrong place. Full fix:
- Pin linuxPackages_6_6. On 25.11's default 6.12 kernel hid-multitouch
grabs the controller and mis-parses its HID report (axes read stuck)
and usbtouchscreen refuses to bind. On 6.6 usbtouchscreen binds and
produces a clean single-touch ABS device (the known-good internal-SATA
install runs 6.6.68). Mirrors douro.nix's per-hardware kernel pin.
- udev rule now modprobes usbtouchscreen ITSELF before unbinding usbhid
and handing over via new_id. On a USB boot systemd-udev-trigger fires
this rule (~2s) before systemd-modules-load loads usbtouchscreen
(~12s), so new_id previously hit a not-yet-loaded driver and the panel
bound to nothing. Loading it inline removes the boot-ordering race.
- Add an X evdev InputClass (99-egalax.conf) so X uses evdev + the
transformation matrix rather than libinput. Mirrors the working
internal-SATA install.
- egalax-calibrate: add XAUTHORITY (=/home/bitspire/.Xauthority) — the
actual boot-time bug. Without the auth cookie xinput died with
"Invalid MIT-MAGIC-COOKIE-1 key / Unable to connect to X server", so
the coordinate-transformation matrix was never applied and touches
landed in the wrong place. Also replace the fixed ExecStartPre sleep
with a 30s retry loop on the eGalax X device appearing — more robust
to boot timing than a race against display-manager.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a USB-bootable BATM3 disk-image target plus the batm3 hardware
changes that make a dd'd USB stick boot reliably on the Dell 9030 AIO.
flake.nix — new `disk-image-batm3-usb` target:
- Distinct partition labels (nixos-usb / ESP-USB) so stage-1 by-label
resolution can't latch onto an internal SATA drive that already holds
a generic nixos/ESP-labelled install. Post-build mlabel relabels the
ESP FAT volume to ESP-USB (bootloader files untouched; UEFI still
loads /EFI/BOOT/BOOTX64.EFI).
- /boot mounted nofail + short device-timeout: the firmware already
loaded the bootloader before Linux; without nofail a slow/late ESP-USB
enumeration drops to emergency mode with root locked — a dead end.
- NO growPartition/autoResize on the USB image: 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
uninterruptible D-state and ESP-USB vanishes with the device, so /boot
times out too). Persistent state is a few MB and the image already
ships ~2GB free in root. The internal-SATA disk-image-batm3 keeps
growPartition — a real AHCI SSD won't drop the bus.
- autoUpgrade off (test image, not a managed fleet member).
batm3.nix — USB-boot reliability:
- Add usb_storage to initrd.availableKernelModules so stage-1 binds the
stick and /dev/disk/by-label/* appears.
- Blacklist uas + usbcore.autosuspend=-1: force the slower-but-reliable
Bulk-Only Transport path and stop the boot medium being power-suspended
mid-I/O — both were causing "device offline error" bus drops.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three breakages handled:
- hardware.opengl → hardware.graphics (renamed in 24.11). Touches
upboard.nix, douro.nix, batm3.nix, live.nix.
- vaapiIntel dropped — legacy pre-Broadwell driver, removed in
nixpkgs. UP Board (Cherry Trail) and OptiPlex 9030 (Haswell) both
use intel-media-driver, which stays.
- vaapiVdpau renamed → libva-vdpau-driver.
system.stateVersion stays 24.05 — convention is to never bump after
install. Existing Sintra and fresh flashes keep the 24.05 state
semantics; that's correct.
All 8 nixosConfigurations (4 models × {live,installed}) evaluate
clean with zero deprecation warnings on 24.11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
System-level half of the lamassu → bitspire rebrand the rest of dev
already did at the path / service / package layers. Touches user/group
declarations, every systemd `User=` block, the udev rules filename, all
chown calls in flake.nix + live.nix, the displayManager autoLogin user,
the trusted-users nix entry, provision-atm.sh's ATM_USER, plus README +
CLAUDE.md doc references.
In-place migration for the Sintra dev unit (which auto-pulls dev at
04:00) lives in `system.activationScripts.bitspire-user-migration` and:
- copies `/home/lamassu/.ssh/authorized_keys` → `/home/bitspire/` once,
so SSH access survives the rename
- recursively chowns `/var/lib/bitspire` to the new bitspire UID on
every boot — cheap no-op once done, but covers the case where the
data dir was written by the now-removed lamassu UID
- leaves `/home/lamassu/` in place as evidence; operator can `rm -rf`
after confirming bitspire login works
Recovery path if the migration breaks SSH access: root key is still in
configuration.nix:142-144 (padreug@gizmo), so ssh root@<host> works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 2d data-dir rename missed four code-path references; the
state-store.ts one was the blocker — bitspire.service on a freshly
provisioned Sintra crashed at startup with:
UnhandledPromiseRejectionWarning: SqliteError: unable to open database file
at initDatabase (.../dist-electron/state-store.js:35:10)
because the production-path detector checked for /var/lib/lamassu-atm
(which the 2d nixos module rename made non-existent), fell back to
process.cwd() under systemd which is /, and tried to open /state.db
without write permission.
Files touched:
- apps/machine/electron/state-store.ts: prodDir → /var/lib/bitspire
(also updated the path doc comment)
- apps/machine/electron/main.ts: support-pages dir lookup
- deploy/nixos/hardware/batm3.nix: WiFi credentials conf path
- deploy/nixos/atm-transactions.sh: operator DB inspection script
deploy/nixos/README.md still references the old path in several
places, but only as documentation — left for a separate sweep.
vue-tsc clean.
Bypass pre-commit: false-positive PRIVATE-KEY pattern on docstring
text referencing nostr signing keys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move WireGuard IP from shared configuration.nix to per-machine
hardware configs. douro = 10.0.0.4, batm3 = 10.0.0.5.
Previously both machines shared 10.0.0.4 which caused routing
conflicts on the VPS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nix's multi-line strings consume bare $kernel. Use ''$ to produce
a literal $ so udev can expand its built-in kernel variable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up the availability broadcast composable to publish the ATM's
status as a replaceable Kind 30078 Nostr event. Publishes on
availability change (debounced) and as a 5-minute heartbeat so
monitors can detect offline machines.
Also adds WiFi auto-connect for BATM3: reads SSID/PSK from
/var/lib/lamassu-atm/wifi.conf at boot. Credentials stay local.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Dell OptiPlex 9030 AIO's built-in eGalax touchscreen is
misidentified by libinput as a touchpad. Fix:
1. Unbind from usbhid at boot via udev, bind to usbtouchscreen
kernel module which handles it as absolute input
2. Apply calibration matrix via systemd service after X11 starts
(swap+invert axes, scale to active panel area 238-1853 x 197-1869)
Also updates hardware comments for Dell 9030 (was HP ProDesk).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
USB-serial adapters enumerate in unpredictable order on reboot,
causing ttyUSB0/1/2 to swap between the F56 dispenser, MEI
validator, and NFC module. Add udev rules that create stable
symlinks by adapter serial number:
- /dev/ttyF56 → Prolific DDDLb103Y23 (F56 dispenser)
- /dev/ttyMEI → FTDI A9YW78OC (MEI cash recycler)
- /dev/ttyNFC → FTDI A9ZF8ELY (NFC module)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HP ProDesk 600 G3 DM hardware module (temporary board replacement for
BATM3). Includes batm3-installed nixosConfiguration with auto-upgrade,
cachix, and systemd-boot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>