The fleet table called sintra a working dev unit; its nixos-upgrade had
failed every night 2026-10-06 → 10-09 on the same 60 s atm-app timeout
as batm3, so nothing merged that week reached it. Recorded with the
interim rule — push-cache after every dev push, because mkAtmApp's
src = self makes any commit invalidate the cached toplevel — and the two
traps found while applying it: a lockfile change silently reuses a stale
pnpmDeps store unless the hash is re-derived, and activation scripts
don't have grep/sed on PATH.
Three corrections to facts a session reads before touching code.
The maintainer taking over the Lamassu codebase has given permission to
use lamassu-machine and lamassu-server, post-boundary included, as prior
art (relayed by padreug, 2026-10-09). The hard rule that limited us to
c0b69d1 is superseded; the guidance is now reference-over-port, with
verbatim ports naming their source commit.
lamassu-server DID have a public-domain era — last open commit adbc9709,
licence added in d06a8f54, both 2023-09-19 — contrary to what the
squashed ~/lamassu/lamassu-server checkout suggests (its first commit is
already Appendix A). History survives in ~/dev/repos/ and at Software
Heritage. The earlier "not re-verified" note is replaced with the
verified boundary.
The hardware-driver table claimed ccnet, cashflow_sc, bnr_advance,
genmega, hcm2, gsr50 and three printers. The tree has validators id003
and ebds, dispensers f56 and puloon, no printers directory, and orphaned
Rust files from an abandoned HAL. The table now matches the tree.
Three claims in this file were stale, and I took all three at face value
today before checking any of them.
`dev` is not a staging branch. Every live machine runs it. batm3's
nixos-upgrade unit pulls ?ref=dev#batm3-installed daily at 04:00, which
makes "push freely to dev" actively dangerous advice — a bad commit
reaches production hardware overnight, unattended. The file said the
production ATMs ran `main` against Lightning.Pub and only sintra was on
dev. batm3 runs bitspire.service out of /var/lib/bitspire with a
VITE_SPIRE_SEED and no Lightning.Pub vars at all.
bitspire.service runs as `bitspire`, not `lamassu`. Leftover from the
rename in 46e52f6.
Added a surveyed fleet table, because two facts in it are load-bearing
for anything touching hardware. Every GPU binds crocus, including
sintra's Braswell which does so despite being Gen8. And batm3's ethernet
is DOWN — its only working network path is an Intel 7260 over WiFi — so
intel/iwlwifi firmware is what keeps that machine reachable at all.
Also recorded that batm3's nightly upgrade is currently failing. It dies
building the ATM app locally against the 60s nix.settings.timeout,
because the app is in neither aiolabs.cachix.org nor cache.nixos.org.
The timeout comment in flake.nix assumes heavy derivations are
upstream-cached; that holds for nixpkgs and not for our own app. The
machine is therefore pinned to its last successful generation and
nothing merged to dev reaches it. Same class as #98, different mechanism.
The fix is publishing atm-app-* to the cachix, not raising the ceiling.
Noted douro as down, pending a reflash and WireGuard reconnection, and
tejo as still Debian (ubilinux4, kernel 4.9) and never installed with
bitspire — a flake target rather than a deployment. Both matter when
reading "all four models build".
Electron's console bridge stringifies each console argument on its way to
the journal, so `console.log('msg:', { a, b })` arrives as
`msg: [object Object]` and every field is lost.
That cost a debugging session today: a cassette-state publish that had
in fact applied an operator refill correctly looked from the journal like
nothing had happened, because the d-tag, event id and stamp were all
inside the object.
Three call sites, the only ones in the renderer passing an object. The
publish line now also carries seq and the applied-op count, which are the
two things worth knowing when an operation seems not to have landed.
Recorded in CLAUDE.md's debugging invariants so it does not come back.
The provenance section claimed v8.1.5 was the last fully-open release.
GitHub history says otherwise: a9234d124d ("chore: add LICENSE",
2023-09-19) removed UNLICENSE and added the proprietary Appendix A SLA,
and the v8.1.5 tag (2023-09-21) already ships it. The true public-domain
boundary is that commit's parent, c0b69d1 ("chore: v8.6.0-beta.9") —
which is further along than 8.1.5 feature-wise.
lamassu-server's own boundary is unverified; flagged in the doc.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Env table (CLAUDE.md), .env.example, and the deploy README still framed
VITE_RELAY_URL / VITE_LNBITS_SERVER_PUBKEY as required/provisioned; they now come
from the pairing seed and are env overrides only. Also refresh the slimmed seed
shape, the relayUrl/pubkey module examples ("" not wss://relay.aiolabs.dev), and
the stale lamassu-next autoUpgrade flake URL (→ aiolabs/bitspire).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Note the wizard flow next to VITE_SPIRE_SEED + a dedicated Pairing section
(aiolabs/bitspire#52): unpaired → scan seed off camera → persist + relaunch →
normal boot pairs. Records the qr-over-jsqr choice rationale by reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fund-atm resolves its signer by resuming the bunker binding from state.db
(the connect token is already spent by the main app, so it can't re-pair);
falls back to a dev nsec via VITE_ATM_PRIVATE_KEY. better-sqlite3 marked
external in the esbuild bundle. .env.example + CLAUDE.md document
VITE_SPIRE_SEED as the prod identity, VITE_ATM_PRIVATE_KEY as dev-only.
(fund-atm is slated for deprecation in favour of the operator funding the
wallet directly via the LNbits UI — kept working for now.)
Part of Phase C, aiolabs/bitspire#52.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes gap 2 from coord log 2026-06-01T18:30Z. The LNbits withdraw
extension's nostr-transport RPC now populates `link.lnurl` from
`settings.lnbits_baseurl` (aiolabs/withdraw#1 / commit e9d911e), so the
ATM no longer needs a separate HTTP URL on the wire to compose the
LNURL-withdraw callback itself.
What goes:
- `VITE_LNBITS_HTTP_URL` env var (renderer + Electron main)
- `lnbitsHttpUrl` field on `LightningConfig`, `RuntimeConfig`, and the
Window mirror in `src/types/electron.d.ts`
- The manual `${lnbitsHttpUrl}/withdraw/api/v1/lnurl/${unique_hash}`
composition in `generateLnurlWithdraw`
- The `encodeLnurl` bech32 helper in `lightning.ts` (LNbits returns
bech32-encoded; we just `.toUpperCase()` to match BOLT/LNURL convention)
- `@scure/base` dep from `apps/machine/package.json` (only used by the
removed helper; clink still uses it directly)
- The `lnbitsHttpUrl` option + `LNBITS_HTTP_URL=…` env var + boot echo
in `deploy/nixos/bitspire-atm.nix`
- Doc references in CLAUDE.md, README.md, deploy/nixos/README.md,
docs/architecture-comparison.md, and the lightning-check skill
What stays:
- `link.lnurl` consumption, with an explicit error if LNbits returns
null (which signals `LNBITS_BASEURL` is unset on the server side —
better to fail clearly than silently)
- The receiver-side bech32 uppercasing (LNbits returns lowercase per
the standard library)
Why this is a net win:
- Removes a config-drift surface — if LNbits's external URL moved
(DNS, port, reverse-proxy rewrite), every ATM in the field would
stop issuing redeemable LNURL-withdraw QRs until reconfigured.
Now LNbits derives its own URL from `settings.lnbits_baseurl`,
one source of truth.
- Removes an extra provisioning step. No more `LNBITS_HTTP_URL=…`
before running `provision-atm.sh`; the relay + server pubkey suffice.
- Removes the misleading boot echo that triggered the §`18:30Z`
smoke triage confusion ("LNbits HTTP: <url>" read like ATM-→-LNbits
connectivity, when it was only ever a URL embedded in customer QRs).
Also adds a `# pragma: allowlist secret` marker above the
`VITE_ATM_PRIVATE_KEY` doc block in `.env.example` so the global
secret scanner stops false-positiving on the documentation prose.
Workspace typecheck + 24/24 apps/machine tests still green.
Co-Authored-By: Claude Opus 4.7 <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>
Both top-level docs were stale after the lamassu-next → bitSpire +
Lightning.Pub → LNbits migration shipped on this branch. They still
listed @lamassu/* package scopes, treated Lightning.Pub as the backend,
described cash-in as ndebit/CLINK, and pointed at removed packages.
README.md: rewritten end-to-end. Calls out the branch model (main vs
dev) so contributors don't accidentally affect production ATMs.
Documents the actual cash-out (BOLT11 + subscribe_payments by hash)
and cash-in (LNURL-withdraw + subscribe_payments by tag/link_id) wire
flows. Quick-start uses the dev compose's bundled LNbits with
nostr-transport + the LNbits-internal nostrrelay extension (the relay
endpoint is ws://<host>:5001/nostrrelay/test — no separate strfry
container, this was a pitfall during Sintra provisioning). Disk-image
deployment summary points at deploy/nixos/README for full details.
CLAUDE.md: rewritten to describe dev-branch reality. Lists actual
package names (@bitSpire/*), notes packages/lightning was deleted in
3d, calls out the LightningBackend adapter pattern in services/
lightning.ts, and gives an env-var reference table + a wire-envelope
crib for kind-21000. Sintra-specific hardware notes (UART layout,
initrd modules for the ACPI eMMC controller) bake in everything we
hard-learned during the first flash.
Both docs now include an Acknowledgements / Provenance section that:
- credits Lamassu Industries AG's open-source lamassu-machine /
lamassu-server (the v8.1.5 release line) as the prior art the
HAL drivers and state machine derive from — bitSpire wouldn't
exist without that foundation
- explicitly states Lamassu transitioned to a proprietary,
source-available "Appendix A SLA" on 2024-01-26 with v8.1.6+
gated behind a paid OSA subscription, and that bitSpire
incorporates no code from v8.1.6 or later
- declares bitSpire independent of Lamassu Industries AG
- in CLAUDE.md specifically: a hard rule that future contributors
(or future Claude runs) must not pull / port / copy code from
lamassu-machine at v8.1.6+; only the 8.1.5 tree is in-scope
License clarification: AGPL-3.0 (matches LNbits, which we link
against) — dropped the earlier "matches LNbits + lamassu-machine
pedigree" phrasing since lamassu-machine is no longer under a free
license.
References:
https://blog.lamassu.is/updates-to-our-lamassu-software-license/https://github.com/lamassu/lamassu-machine
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final rename commit covering user-facing copy and the docs that
describe current state. The mechanics of the rename are done after
this; the LNbits backend swap (phase 3) is the next concern.
Code branding strings (Lightning invoice descriptions):
apps/machine/src/services/lightning.ts
apps/machine/src/stores/atm.ts
docs/clink-protocol.md (example code blocks)
"Lamassu ATM Payment" → "bitSpire Payment"
"Lamassu ATM - Cash Out" → "bitSpire - Cash Out"
`Lamassu ATM - Buy ${n} sats`→ `bitSpire - Buy ${n} sats`
Top-level docs:
README.md, CLAUDE.md — title + intro + dir-tree references.
deploy/nixos/README.md — title + worktree-path commands.
docs/machine-installation.md — opening line carries the historical
note ("Lamassu Next" → "bitSpire"). The body still uses
`/opt/lamassu/` paths and the `lamassu-kiosk` systemd unit
because the dev branch is moving to NixOS disk-image flash
(phase 4) — this AppImage-sideload doc represents the legacy
deploy path. Leaving the LP/lamassu refs in there as part of
its historical context; a separate doc will describe the
NixOS path.
.claude/skills/nostr-check.md — header only.
DELIBERATELY left as "Lamassu Next" (pedagogical / historical):
- docs/adr/001-hal-architecture.md — frozen ADR; renaming
distorts the historical decision context.
- docs/architecture-comparison.md — deliberately contrasts
"lamassu-server" (prior) with "lamassu-next" (us at the time
of writing).
NOT done in this commit (deferred to LNbits/clean-up phase):
- docker/docker-compose.dev.yml container names
(lamassu-relay, lamassu-bitcoind, etc.) — these belong to the
LP-bearing dev stack that 3c/3d will significantly reshape.
Verified: pnpm typecheck clean (12/12 cached).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Failed dispenses (sats debited, cash not dispensed) were invisible —
transactions only recorded on 'complete'. Now records on 'dispenseError'
with status ('dispense_error'|'partial'|'complete'), error message, and
per-cassette detail.
Also fixes a bug in both HAL services where dispense results were mapped
by amounts-array index instead of cassette position, causing swapped
denomination counts when cassette order differs from request order.
Schema v3→v4: adds status/error columns to transactions, new
cassette_bills table for per-cassette provisioned/dispensed/rejected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add dev.sh script for managing regtest development environment
- Implement cmd_fund to fund ATM app owner via Lightning.Pub API
- Add --fund flag to cmd_up for automatic funding on startup
- Update setup_atm_app to write VITE_APP_ID to machine .env
- Fix Electron IPC to pass appId and extensionApiUrl to renderer
- Restructure repo from nested lamassu-next/ to root
The dev.sh script now supports:
- ./dev.sh up --fund # Start regtest and auto-fund ATM
- ./dev.sh fund # Fund existing ATM app
- ./dev.sh status # Show environment status
- ./dev.sh reset # Clean restart
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add lamassu-server, lamassu-machine, lamassu-install as submodules
- Add CLAUDE.md with development guidance
- Add docs/modernization-plan.md with 2026 refactoring roadmap
- Add .gitignore for Node.js/Nix development
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>