Commit graph

28 commits

Author SHA1 Message Date
2572a14c61 fix(rpi4): enable pcscd — without it the kiosk hangs before drawing
This is the white screen. Not graphics, not the bundle, not pairing.

The app constructs @pokusew/pcsclite at startup. That calls
SCardEstablishContext(), which calls SCardCheckDaemonAvailability(), which
— finding no pcscd — BUSY-LOOPS in fstatat64 at ~92% CPU rather than
returning an error. It runs on Electron's main thread before the
BrowserWindow is created, so no window is ever made and the panel stays
white.

It is a hang, not a crash, which is why it presented so badly. Nothing
throws. Nothing is logged after "[StateStore] Initialized database". The
process looks healthy: systemd reports the service active, Electron is
running, and there is even a gpu-process. But a setInterval registered
before startup never fires once in 32 seconds, the main thread sits in
state R, and the remote debugger reports zero page targets.

V8's own tooling cannot see it either, because the thread never yields to
the inspector: Debugger.pause returns nothing and Profiler.stop times out.
A native backtrace was the only thing that worked:

  #0 fstatat64                     libc
  #1 SCardCheckDaemonAvailability  libpcsclite
  #2 SCardEstablishContext         libpcsclite
  #3 PCSCLite::PCSCLite()          pcsclite.node
  #4 PCSCLite::New(...)

Ruled out along the way, each by direct test on the machine: graphics (it
fails identically with the GPU fully disabled), Electron on aarch64 (a
minimal app renders fine), the Vue bundle (loading the real index.html
from a minimal main process mounts the app and reaches "[ATM] State
machine initialized"), the preload script, the CSP, kiosk and fullscreen
window options, better-sqlite3, /dev/shm, memory, page size, X
authorisation, and isDev.

upboard.nix and batm3.nix both enable pcscd for their real readers, which
is why no x86 machine has ever hit this. This module did not, and that was
the entire difference. pcscd with no reader attached simply idles, so
enabling it costs nothing.

Worth noting for the wider fleet: any future board that omits pcscd
inherits this, and it presents as a blank screen with a healthy-looking
service. The robust fix is for the app to not block its main thread on a
card-reader handshake at all — the NFC path is already documented as
best-effort — but that is an app change and this unblocks the hardware.
2026-09-25 22:09:36 +02:00
8917b8b967 fix(rpi4): raise CMA to 256M, document the firmware-partition step
Two findings from the first Pi 4 (actually a CM4) bring-up, chased from a
white screen to hardware-accelerated X.

CMA. The vc4 display pipeline allocates its framebuffer from the contiguous
memory area, and the default reservation here is 32MiB with ~11MiB free. The
attached panel is 3840x1080, whose framebuffer is ~16.6MB before double
buffering, so X picked the mode and then died:

  Output HDMI-1 using initial mode 3840x1080 +0+0
  (EE) AddScreen/ScreenInit failed for driver 0

nixos-hardware's fkms-3d injected a CMA overlay alongside its display one;
cma=256M replaces that half. This part is declarative, since kernelParams
reach the extlinux APPEND line.

The display itself is NOT declarative, and that is the uncomfortable part.
This board boots the FIRMWARE's vendor DTB, not the DTBs NixOS builds: the
live device tree carries __symbols__ and mainline's do not, and U-Boot found
no FDTDIR match for compatible "raspberrypi,4-compute-module" so it passed
the firmware's DTB straight through. hardware.deviceTree.overlays therefore
cannot reach the running tree at all, which is also why the earlier fkms-3d
removal fixed a build error without fixing the display.

In the vendor DTB every display node ships disabled, so config.txt needs
`dtoverlay=vc4-kms-v3d,noaudio` and /boot/firmware/overlays/ has to be
populated from raspberrypifw. Three traps in that one line, each of which
failed silently:

  - the NixOS sd-image writes the DTBs to the firmware partition but NOT the
    overlays, so the directory ships empty and dtoverlay= does nothing
  - copying only vc4-kms-v3d.dtbo is insufficient; the firmware remaps that
    to vc4-kms-v3d-pi4.dtbo on this board, so the whole directory goes
  - without noaudio, vc4_hdmi cannot register its PCM component, returns
    -517 (EPROBE_DEFER) forever, and the DRM device never registers, so X
    finds no card. We deleted the audio stack anyway.

Result on the machine: card1 is vc4-drm with HDMI-A-1 connected, card2 is
v3d, and X reports

  glamor X acceleration enabled on V3D 4.2.14.0

against swrast before. The manual steps are written into the module so the
next person does not rediscover them from a blank screen, but they are lost
on a reflash and belong in the image builder. Follow-up.
2026-09-25 18:02:31 +02:00
11cc1b88d6 fix(rpi4): drop fkms-3d, mainline does full KMS without an overlay
With the mainline kernel from the previous commit, the device-tree overlay
step fails outright:

  Applying overlay rpi4-cma-overlay
  Applying overlay rpi4-vc4-fkms-v3d-overlay
  libfdt.FdtException: pylibfdt error -1: FDT_ERR_NOTFOUND

nixos-hardware's fkms-3d applies two overlays that patch nodes present in
the Raspberry Pi VENDOR kernel's DTBs and absent from mainline's. Predicted
when the kernel changed; this is it arriving.

It is also the wrong thing to want. "fkms" is FIRMWARE KMS, the older
arrangement where the VideoCore firmware owns the display and Linux drives
it at arm's length. Mainline does full KMS, and mainline's own
bcm2711-rpi-4-b.dtb already describes the hardware: it carries
brcm,bcm2711-vc5 and brcm,2711-v3d nodes, confirmed by decompiling the DTB
with dtc. The vc4 and v3d drivers bind to those directly, no overlay
involved.

So the overlay was not providing capability, it was translating for a
kernel we no longer use.

hardware.deviceTree.overlays is now empty, so there is nothing left for the
overlay builder to fail on. Verified by evaluating the config.

No replacement needed for videoDrivers either. fkms-3d used to set it as a
side effect, but the shared configuration.nix already declares modesetting,
which is correct for full KMS and is what the x86 machines use. Setting it
again here just produced ["modesetting" "modesetting"].

Still unproven on hardware: whether X comes up on vc4 rather than falling
back to a framebuffer. That is the next thing to read out of
/var/log/X.0.log once the machine boots, and it is now a minutes-long
iteration rather than a kernel compile per attempt.
2026-09-25 15:45:56 +02:00
b2bf2ffe4b fix(rpi4): use the mainline kernel, not the uncached Pi vendor one
nixos-hardware's raspberry-pi/4 module mkDefaults boot.kernelPackages to
the Raspberry Pi vendor kernel (linux-rpi, via common/kernel.nix). That
kernel is in no binary cache: Hydra does not build nixos-hardware's
overlay kernels, and it is not in aiolabs.cachix.org either. So every Pi
compiles a kernel from source, on an SD card, and recompiles on every
bump.

Found the hard way during the first Pi 4 bring-up, which spent hours on

  building linux-rpi-6.18.39-stable_20260724 (buildPhase):
    CC [M]  fs/overlayfs/inode.o

before anyone looked closely enough to notice it was not the app. I had
told the operator only the app would build, having checked that Electron
and the Pi firmware were cached and never checked the kernel.

Mainline aarch64 kernels are cached, and mainline demonstrably boots a
Pi 4 — it is what the stock NixOS aarch64 SD image runs, which is how this
machine was bootstrapped in the first place. The vendor kernel's
Pi-specific patches buy nothing this kiosk needs: display, USB serial and
WiFi are all mainline, and vc4/v3d KMS has been mainline for years.

  before: linux-rpi-6.18.39-stable_20260724   not cached, hours to build
  after:  linux-6.12.90                       cached, downloads

Verified the config still evaluates, which also clears nixos-hardware's
assertion that the kernel be at least 6.1.

Watch the graphics path. fkms-3d is a nixos-hardware overlay built around
the vendor kernel's firmware-KMS route; the mainline equivalent is full KMS
(vc4-kms-v3d). If X lands on the framebuffer with Electron rendering in
software, that overlay is where to look, not this kernel choice.

rpi5 is left alone deliberately and still carries the vendor kernel, so it
will pay the same cost whenever someone first builds it. Mainline Pi 5
support is younger than Pi 4's and the RP1 southbridge needed vendor
patches for longer, so that one wants its own check rather than the same
change applied on faith.
2026-09-25 15:27:12 +02:00
91c6994dd4 feat(deploy): add aarch64 Raspberry Pi 4 target
The Pi 4 twin of the Pi 5 build: `rpi4-installed` (in-place rebuild target),
`rpi4-image`, and `packages.aarch64-linux.{sd-image-rpi4,atm-app-rpi4}`, all
through the board-keyed machinery of the previous commit. The shared runtime
is untouched; only the board pair is new.

deploy/nixos/hardware/raspberry-pi-4.nix mirrors raspberry-pi-5.nix line for
line except where the boards differ:
- KMS for the kiosk display is an opt-in on the Pi 4
  (`hardware.raspberry-pi."4".fkms-3d`), which also injects the CMA + vc4
  device-tree overlays; the Pi 5 gets it by default. Without it X falls back
  to the framebuffer and Electron renders in software.
- fkms-3d sets videoDrivers itself, so the module doesn't.
Everything else — extlinux boot, console pinned to tty0 so the GPIO UART is
free for a validator, no-suspend, the ttyValidator{0,1,2} udev symlinks — is
identical by design.

Evaluation-verified only: rpi4-installed/rpi4-image instantiate, and against
rpi5 they differ solely in the expected places (bcm2711 device tree, the two
fkms overlays, the rpiVersion=4 kernel, no clk-rp1 in initrd, machine model
in the env seed). Not yet booted on hardware; the doc says so.

docs/raspberry-pi-setup.md covers both boards — build, flash, first boot +
provisioning via the spire seed, in-place updates, peripherals — since #87
shipped the Pi 5 without one. It replaces a never-committed Pi 4 sketch
(parked on wip/rpi4-sketch) whose flake wiring didn't evaluate and whose
provisioning section predated the pairing seed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
2026-09-24 21:44:25 +02:00
082f738ffa fix(deploy): console=tty0 was being dropped on the Pi 5
raspberry-pi-5.nix set `boot.kernelParams = lib.mkDefault [ "console=tty0" ]`
to keep the serial console off the GPIO UART so a validator can own it. It
never took effect: kernelParams is list-merged, and only definitions at the
highest priority survive — nixpkgs defines loglevel/lsm at normal priority,
so the mkDefault list was discarded wholesale. Effective params on
rpi5-installed were `[ "loglevel=4" "lsm=landlock,yama,bpf" ]`, no console=
at all, which makes the kernel fall back to the device tree's stdout-path:
that same UART.

Drop the mkDefault so the entry merges. Verified by evaluating
config.boot.kernelParams before/after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
2026-09-24 21:43:53 +02:00
a77bae50ee feat(deploy): add aarch64 Raspberry Pi 5 target (sd-image)
Proper aarch64 NixOS target for the DIY Pi 5 build, wired additively so
the x86 fleet path is untouched (both the new Pi sd-image and the
existing sintra-installed still evaluate cleanly):

- nixos-hardware input (raspberry-pi-5 module) for Pi kernel/firmware/GPU.
- aarch64 pkgs + pkgs-unstable + mkAtmApp instances (parallel to x86).
- mkPiConfig: aarch64 nixosSystem reusing the shared configuration.nix +
  bitspire-atm service, replicating the installed-config runtime (bitspire
  service, first-boot env seed, electron service override, swap). Drops
  the x86 fleet machinery for a first bring-up: no determinate/autoUpgrade
  (not yet fleet-managed) and no atm-tui (needs an aarch64 package).
- raspberry-pi-5.nix hardware module: extlinux boot, vc4/v3d KMS for X,
  primary UART free for a GPIO-wired validator, no-suspend, and stable
  /dev/ttyValidator* udev symlinks for USB-serial validator adapters
  (Apex 7600 RS-232 via adapter, NV10 USB+).
- nixosConfigurations.rpi5-installed + packages.aarch64-linux.sd-image-rpi5.

BUILD NOTE: the app closure (aarch64 electron/native addons) needs an
aarch64 builder — a native Pi/arm box or `boot.binfmt` qemu emulation on
an x86 host. Config evaluates on x86; it just can't build there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ivBosaWmv8vwFE7ejrdHW
2026-08-16 21:30:53 +02:00
Patrick Mulligan
ffbacafe39 fix(nfc): auto-recover a wedged CCID reader via USB power-cycle
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>
2026-08-06 19:51:39 +02:00
Patrick Mulligan
d00f3c0bbd fix(batm3): make touch calibration immune to USB-boot timing race
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.
2026-08-06 18:52:33 +02:00
Patrick Mulligan
0a3156855c feat(deploy): authorize bitspire for pcscd (polkit) + NFC diagnostics
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>
2026-08-05 05:01:08 +02:00
Patrick Mulligan
74c420fbd3 feat(deploy): enable pcscd on batm3 for the Bolt Card reader
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>
2026-08-05 04:34:38 +02:00
Patrick Mulligan
f52d942e57 fix(deploy): eGalax touchscreen on batm3 (kernel 6.6 + calibration)
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>
2026-07-29 23:56:41 +00:00
Patrick Mulligan
6edcb8b96d feat(deploy): USB-bootable batm3 test image (disk-image-batm3-usb)
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>
2026-07-29 23:56:41 +00:00
7e90719508 refactor(deploy): share UP Board serial hardware between installed + live ISO
The sintra live ISO (live.nix) had no serial support — ftdi_sio and the
ttyJ5/ttyJ7 udev symlinks were only in hardware/upboard.nix (installed), so
booting iso-sintra on real hardware failed on the validator + F56 dispenser
while the disk image worked. The two definitions had already drifted (live's
tejo block lacked ttyS4).

Extract the UP Board serial peripherals (usbserial/ftdi_sio/cp210x, the
ttyJ4/ttyJ5/ttyJ7 udev symlinks + permissions, console=tty0) into
hardware/upboard-serial.nix and import it from both upboard.nix (installed
tejo + sintra) and live.nix (sintra only). Single source of truth — the two
artifacts can't drift again. Named upboard-serial (not sintra-serial) since
upboard.nix serves both tejo-installed and sintra-installed.

Camera + LED/SPI rules stay inline in upboard.nix (installed-specific; the
pairing camera works via getUserMedia without the scanner symlink). Verified
by eval: live sintra now carries ftdi_sio + console=tty0 + ttyJ7; installed
sintra/tejo unchanged (serial present, camera present, no console dupe).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:31:36 +02:00
7c1011d382 chore(nix): bump nixpkgs 24.05 → 24.11
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>
2026-06-01 19:12:11 +02:00
264cc47e0c refactor(deploy): rename system user lamassu → bitspire
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>
2026-06-01 19:11:32 +02:00
2551c6fcf8 fix(nixos/upboard): release ttyS4 from kernel console for F56 dispenser
The Fujitsu F56 bill dispenser on Sintra is wired to the SoC's MMIO
UART at 0xa171b000, which the kernel enumerates as ttyS4 (the only
real on-board UART besides the legacy ttyS0 at I/O 0x3f8). The
existing kernelParams routed the kernel console through ttyS4, so
userspace could never open it: HAL crashed at startup with
"Input/output error setting custom baud rate of 9600" when trying
to initialise the dispenser, which in turn aborted the entire ATM
init chain in production mode (HAL failure → initError set → kiosk
shows "ATM unavailable" → Lightning client never gets to even try
talking to LNbits).

Two coordinated changes:

1. Drop `console=ttyS4,115200n8` from kernelParams. Keep `console=tty0`
   so kernel messages still land on the framebuffer console during
   boot. Anyone who wants a serial debug console can point it at
   ttyS0 (the legacy 8250 at I/O 0x3f8) — that port stays free.

2. Extend the dispenser-tag udev rules: add `KERNEL=="ttyS4"
   SYMLINK+="ttyJ7"` alongside the existing ttyS1 / ttyS5 entries.
   Different UP Board variants enumerate the dispenser-side UART
   under different kernel-allocated indices; whichever real device
   shows up at boot gets the ttyJ7 alias HAL is wired to expect.

Diagnostic before/after on Sintra (`stty -F /dev/ttyJ7 9600`):
  before: Input/output error
  after:  OK

Once this rolls out the renderer should progress past HAL init,
proceed to initializeLightningServices(), and start connecting to
LNbits over the nostr transport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
a28894ed52 fix: rename remaining /var/lib/lamassu-atm references → /var/lib/bitspire (2d follow-up)
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>
2026-06-01 19:08:03 +02:00
b7aa10e7fb fix(nixos/upboard): force-load eMMC stack in initrd + align /boot label
First Sintra boot from the freshly-flashed eMMC panicked in stage 1:

  An error occurred in stage 1 of the boot process, which must mount
  the root filesystem on '/mnt-root' and then start stage 2.
  mount: can't find /mnt-root/ in /proc/mounts
  stage 2 init script (/mnt-root/nix/store/...nixos-system-bitspire/init) not found
  Kernel panic - not syncing: Attempted to kill init!

Root cause: the UP Board's eMMC controller is enumerated via ACPI
(sdhci-acpi), but the initrd only had sdhci_pci available. /dev/mmcblk*
nodes never materialised in stage 1, so root-by-label resolution
silently failed and switch_root had nothing to chroot into. Tejo (same
hardware module) appears to have been getting lucky with a different
controller binding, or its eMMC firmware exposes PCI-style SDHCI.

  - Add sdhci-acpi + mmc_block to initrd.availableKernelModules so the
    block device infrastructure is present in the early-boot ramdisk.
  - Force-load both via initrd.kernelModules so they're guaranteed to
    fire before stage 1 init runs — leaving them on availableKernel
    Modules alone relies on udev autoload firing in time, which it
    wasn't.

Also a separate-but-adjacent fix in the same module: /boot was
declared as by-label/boot, but make-disk-image.nix with
partitionTableType="efi" labels the FAT partition "ESP". This was
the FIRST boot failure I hit (manually worked around with
`fatlabel /dev/mmcblk0p1 boot` while in the Alpine live image).
Aligning the declared label with what the image build produces means
future flashes don't need that hand-step. douro.nix already used ESP.

Build verified by `nix eval` on the sintra-installed config — initrd
kernelModules now contains [ "sdhci-acpi" "mmc_block" "dm_mod" ].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
Patrick Mulligan
cc109b2958 fix(deploy): assign unique WireGuard IPs per machine
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>
2026-04-04 19:18:12 -04:00
Patrick Mulligan
928b8d84cd fix(deploy): escape $kernel udev variable in batm3 touchscreen rule
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>
2026-03-29 22:57:51 -04:00
Patrick Mulligan
67c7c12ade feat: availability broadcast (Kind 30078) + WiFi auto-connect
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>
2026-03-29 22:36:58 -04:00
Patrick Mulligan
7afcc6fc5d feat(deploy): add eGalax touchscreen support for Dell 9030 AIO
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>
2026-03-29 22:04:59 -04:00
Patrick Mulligan
6cf372055f feat(deploy): add stable USB serial symlinks for BATM3
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>
2026-03-27 20:35:53 -04:00
Patrick Mulligan
88a52cf769 feat(deploy): add batm3-installed NixOS config
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>
2026-03-25 17:13:02 -04:00
Patrick Mulligan
143ae9ff0b feat(nix): add tejo hardware support and per-model auto-upgrade
- Add tejo-specific udev rules for both UP Board and UP4000 variants
  (serial symlinks ttyJ4/J5/J7, camera, LED SPI, I2C, USB autosuspend)
- Add USB serial kernel modules (usbserial, ftdi_sio, cp210x) for tejo
- Add tejo serial console kernel params (ttyS4 debug UART)
- Fix upboard.nix: hardware.graphics → hardware.opengl (NixOS 24.05)
- Add tejo-installed nixosConfiguration with model-specific auto-upgrade
- Parameterize auto-upgrade flake URL per machine model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:54:50 -05:00
Patrick Mulligan
625cebed25 refactor(nix): consolidate deploy flake into root flake with pure ISO builds
Move deploy/nixos/flake.nix into the root flake.nix, adding mkAtmApp
for pure Nix builds of the Electron app (no local pnpm needed). Simplify
build-iso.sh to a thin wrapper around `nix build .#iso-<model>`. Add
douro hardware configuration. Streamline live.nix to consume the
Nix-built app package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:58:43 -05:00
Patrick Mulligan
19d43c2939 feat(deploy): add NixOS live USB ISO for ATM hardware testing
Add NixOS configuration to build a bootable live USB ISO that runs the
ATM Electron app in kiosk mode on physical hardware (UpBoard). The ISO
boots from squashfs, auto-starts X11/openbox, and launches Electron in
production mode.

Key changes:
- deploy/nixos/live.nix: Live USB module (squashfs+tmpfs, no disk install)
- deploy/nixos/flake.nix: Nix flake with ISO build output
- deploy/nixos/provision-atm.sh: Auto-provision LP credentials via API
- deploy/nixos/build-iso.sh: End-to-end build workflow script
- apps/machine: Fix Electron production mode (ELECTRON_FORCE_PROD),
  Vue Router hash mode for file:// protocol, relative asset paths

Build: cd deploy/nixos && bash build-iso.sh
Test:  qemu-system-x86_64 -enable-kvm -m 2G -cdrom result/iso/*.iso

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 20:11:23 -05:00