docs: finish the LNbits-era doc sweep across docs/ + .claude/skills/
Second + final batch of the doc refresh. README + CLAUDE went out in
8c9ae29; deploy/nixos/README + obsolete-flow flags in 924844f. This
commit covers everything left.
docs/machine-installation.md
Was describing a manual AppImage scp deploy + a `lamassu-kiosk`
systemd unit that hasn't been the deployment path for months.
Replaced with a high-level "what the pipeline does and why"
overview that points at deploy/nixos/README.md for the full
command-by-command walkthrough. Includes the BATM3 chassis-mod
note (custom Dell OptiPlex retrofit, not a stock Dell).
docs/architecture-comparison.md
Rewrote the comparison to be lamassu-server (≤ v8.1.5) vs bitSpire
(LNbits-backed) instead of the original lamassu-server vs LP-backed
lamassu-next framing. Updated the cash-out + cash-in flow diagrams
to show the actual nostr-transport path (LNbits-bundled nostrrelay
extension at ws://<host>:5001/nostrrelay/test, no separate strfry
container). Replaced the migration-path section with a softer
"when to choose what" framing that includes Lamassu's current
commercial offering as a legitimate third option. Added a header
pointer to the Acknowledgements section.
docs/business-model.md
Light touch-ups: Lightning.Pub → LNbits where it appeared, swapped
the [[ndebit-cash-in-flow]] link for [[architecture-comparison]],
noted the kind-30078 service beacon for availability broadcasts.
docs/device-configuration.md
Dropped "Lamassu" branding from the machine-model headings
(Sintra / tejo / douro / batm3 are referenced by hardware identity
here, not by Lamassu's product line). Added the Sintra-specific
ttyS4-vs-placeholder-ttyS1..3 gotcha we hard-learned during the
first flash. Corrected the BATM3 entry: stock GeneralBytes chassis
with a Dell OptiPlex 9030 AIO motherboard physically grafted in,
NOT a Dell out of the box. Updated the example /dev/ttyJ* symlink
output to match what a healthy Sintra actually shows.
docs/adr/001-hal-architecture.md
ADRs are historical artifacts — kept the original decision text
intact. Added a postscript noting:
- The package rename @lamassu/hal → @bitSpire/hal
- The v8.1.5 boundary on any lamassu-machine source-tree
references (Lamassu's 2024-01-26 license transition)
- That the "Remaining Work" list is complete and the first
successful Sintra hardware integration ran on 2026-05-13
.claude/skills/lightning-check.md
Rewrote end-to-end. Was Lightning.Pub-flavoured with CLINK kinds
21001/21002 as the primary flows; now validates the LNbits nostr-
transport surface (kind-21000 envelope, NIP-44 v2 encryption,
subscribe_payments filter discipline, lnurlw link composition).
Preserved a --clink mode for the still-live kind-21003 operator-
management surface. Includes a "what to check" rubric for cash-out
vs cash-in flows that mirrors the actual code in
apps/machine/src/services/lightning.ts.
.claude/skills/hal-check.md
Two pivots: (1) acknowledge ADR-001's TypeScript-not-Rust choice
and reframe all the safety checklists in TS-flavour (type safety,
discriminated unions, single-writer serial, bounded emitters)
instead of Rust-flavour (unsafe, borrow checker). (2) Add explicit
v8.1.5 provenance boundary plus a "forbidden operations" section
that prohibits diffing or porting from v8.1.6+ lamassu-machine
source. Updated the port-validation source-reference table to
list TS file paths under packages/hal/ instead of Rust paths.
.claude/skills/docs.md
@lamassu/* → @bitSpire/*. Replaced the Lightning.Pub mermaid
diagram with a current cash-out flow showing the nostr-transport
RPC + subscribe_payments push path. Left the createOffer noffer
example in the API-docs template section since it's illustrative
("here's what a good TSDoc block looks like") rather than current
reference documentation.
.claude/skills/test.md
One-line: @lamassu/nostr-client → @bitSpire/nostr-client in the
pnpm-filter example.
deploy/nixos/README.md
Single touch-up: clarified the douro/batm3 hardware-module comments
to reflect that BATM3 is a custom-installed Dell board in a
GeneralBytes BATM3 chassis (not a Dell OEM).
Files NOT touched in this sweep (intentionally):
- packages/hal/src/**/*.ts attribution comments — those reference
"lamassu-machine" in their port-source headers. Those are
factually accurate (the drivers ARE ported from there, up to
v8.1.5) and constitute necessary license/attribution metadata.
Editing them would erase the provenance trail.
- .claude/skills/{nostr-check,security}.md — already protocol-
neutral, no LP/lamassu references to clean up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0b94bef4be
commit
53b0d382e8
10 changed files with 692 additions and 792 deletions
|
|
@ -1,278 +1,96 @@
|
|||
# Machine Installation
|
||||
# Deploying bitSpire to a Sintra (or other ATM)
|
||||
|
||||
This document describes how to deploy the bitSpire ATM software to a Sintra machine. (Historical name: "Lamassu Next" — renamed on the `dev` branch as part of the LNbits-backend transition.)
|
||||
This document gives the high-level shape of an ATM deployment. **For the step-by-step walkthrough — every command from `nix build` to a kiosk on a real Sintra — see [deploy/nixos/README.md](../deploy/nixos/README.md).** This file is the orientation doc that explains *why* the pipeline looks the way it does.
|
||||
|
||||
## Prerequisites
|
||||
> The pre-NixOS workflow described in earlier versions of this file (manual AppImage scp, hand-written systemd unit, ad-hoc env files) has been retired. NixOS is the supported deployment path on `dev`. The historical AppImage build still exists for one-off testing on non-NixOS dev boxes, but it is not how production ATMs are installed.
|
||||
|
||||
### On the Sintra
|
||||
## The pipeline at a glance
|
||||
|
||||
- Linux OS with X11 display server
|
||||
- Network connectivity (WiFi or Ethernet)
|
||||
- User account with `dialout` group membership (for serial port access)
|
||||
|
||||
### Backend Services
|
||||
|
||||
The ATM requires network access to:
|
||||
|
||||
| Service | Purpose | Required |
|
||||
| ------------- | ------------------- | -------- |
|
||||
| Nostr relay | CLINK communication | Yes |
|
||||
| Lightning.Pub | Payment processing | Yes |
|
||||
|
||||
These can be self-hosted or provided by a third party.
|
||||
|
||||
## Building the Application
|
||||
|
||||
### Development Machine Setup
|
||||
|
||||
1. Enter the development environment:
|
||||
|
||||
```bash
|
||||
cd lamassu-next
|
||||
devenv shell
|
||||
```
|
||||
|
||||
2. Build the Electron application:
|
||||
|
||||
```bash
|
||||
cd apps/machine
|
||||
pnpm build:electron
|
||||
```
|
||||
|
||||
3. Build artifacts are created in `apps/machine/release/`:
|
||||
|
||||
```
|
||||
release/
|
||||
├── bitSpire-0.1.0.AppImage # Portable executable (recommended)
|
||||
└── linux-unpacked/ # Unpacked application directory
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Option A: AppImage (Recommended)
|
||||
|
||||
The AppImage is a self-contained executable that works on any Linux distribution.
|
||||
|
||||
1. Copy to Sintra:
|
||||
|
||||
```bash
|
||||
scp "apps/machine/release/bitSpire-0.1.0.AppImage" user@sintra:/opt/lamassu/
|
||||
```
|
||||
|
||||
2. Make executable:
|
||||
|
||||
```bash
|
||||
ssh user@sintra
|
||||
chmod +x "/opt/lamassu/bitSpire-0.1.0.AppImage"
|
||||
```
|
||||
|
||||
3. Test manually:
|
||||
|
||||
```bash
|
||||
export DISPLAY=:0
|
||||
"/opt/lamassu/bitSpire-0.1.0.AppImage" --no-sandbox
|
||||
```
|
||||
|
||||
### Option B: Unpacked Directory
|
||||
|
||||
For faster startup times, deploy the unpacked application:
|
||||
|
||||
1. Copy to Sintra:
|
||||
|
||||
```bash
|
||||
scp -r apps/machine/release/linux-unpacked user@sintra:/opt/lamassu/
|
||||
```
|
||||
|
||||
2. Test manually:
|
||||
|
||||
```bash
|
||||
ssh user@sintra
|
||||
export DISPLAY=:0
|
||||
/opt/lamassu/linux-unpacked/lamassu-machine --no-sandbox
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a configuration file at `/opt/lamassu/.env`:
|
||||
|
||||
```bash
|
||||
# Machine model (sintra, gaia, or custom)
|
||||
VITE_LAMASSU_MACHINE_MODEL=sintra
|
||||
|
||||
# Fiat currency code (ISO 4217)
|
||||
VITE_LAMASSU_FIAT_CODE=USD
|
||||
|
||||
# Custom device paths (optional, uses preset defaults if not set)
|
||||
# VITE_LAMASSU_VALIDATOR_DEVICE=/dev/ttyJ5
|
||||
# VITE_LAMASSU_DISPENSER_DEVICE=/dev/ttyJ7
|
||||
|
||||
# Cassette configuration (optional)
|
||||
# VITE_LAMASSU_CASSETTES='[{"denomination":20,"count":100}]'
|
||||
```
|
||||
┌─────────────────┐ nix build .# ┌─────────────────────────┐
|
||||
│ dev box │ ─disk-image-sintra──▶ │ result/nixos.img │
|
||||
│ (Nix + flake) │ │ (8.5 GB sparse, GPT) │
|
||||
└─────────────────┘ └────────────┬────────────┘
|
||||
│ dd
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ USB stick │
|
||||
└──────────┬───────────┘
|
||||
│ carry to ATM
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────────────┐
|
||||
│ Alpine live USB │ boot Sintra │ Sintra (UP Board) │
|
||||
│ (toolchain) │ ─────────────────────▶ │ eMMC: empty │
|
||||
└────────┬────────┘ └──────────┬──────────┘
|
||||
│ apk add + dd USB → eMMC + parted resize + reboot
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ Sintra booted from eMMC, bitspire.service in needs-prov state │
|
||||
└──────────────────────────────┬─────────────────────────────────┘
|
||||
│ provision-atm.sh from dev box
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ /var/lib/bitspire/.env populated, service restarted, kiosk │
|
||||
│ connects to LNbits over nostr-transport, ready for transactions │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
See [Device Configuration](./device-configuration.md) for detailed configuration options.
|
||||
## Why this pipeline
|
||||
|
||||
### Lightning.Pub Connection
|
||||
Three properties matter for ATM software running unattended in retail locations:
|
||||
|
||||
The ATM needs to connect to a Lightning.Pub instance. Configure via environment:
|
||||
1. **Reproducible boot media.** Two ATMs flashed from the same `nixos.img` boot identical environments — same kernel, same systemd units, same Electron build, same nix-store closure. No "works on my machine" drift between fleet units.
|
||||
2. **Auditable provenance.** Every file on the ATM traces back to a derivation in `/nix/store/`, and every derivation traces back to a commit in this repo. There's no `pip install` reaching out to PyPI, no `npm install` pulling un-pinned packages, no `apt update` mutating the system underfoot.
|
||||
3. **Safe in-place updates.** `nixos-rebuild switch` against the `dev` branch atomically installs a new generation; if the new generation fails to boot or activate, the previous one stays bootable. Auto-upgrade at 04:00 daily means an operator can patch the fleet by pushing to `dev`.
|
||||
|
||||
```bash
|
||||
# Nostr relay WebSocket URL (required)
|
||||
VITE_RELAY_URL=wss://your-relay.example.com
|
||||
The disk-image approach (versus `nixos-install` from a live USB) is specifically to avoid the human-in-the-loop installation step. Every Sintra gets the same image; the only per-machine variation is the `.env` written at provisioning time.
|
||||
|
||||
# Lightning.Pub's Nostr public key (required)
|
||||
# Get from: docker logs lamassu-lightning-pub | grep pubkey
|
||||
VITE_LIGHTNING_PUB_PUBKEY=4be8e203a3341bb2b74a4dcbf8774e061437f63ec21af7ec3144c8d0a68e2f39
|
||||
## What's in the image
|
||||
|
||||
# Lightning.Pub HTTP API URL (optional, for admin operations)
|
||||
VITE_LIGHTNING_PUB_API_URL=https://lp.operator.com
|
||||
Conceptually:
|
||||
|
||||
# ATM's Nostr private key (recommended for persistent identity)
|
||||
# Generate with: npx @lamassu/nostr-client generate-keypair
|
||||
# If not set, a new ephemeral identity is generated on each restart
|
||||
VITE_ATM_PRIVATE_KEY=0123456789abcdef...
|
||||
```
|
||||
| Layer | Source | Purpose |
|
||||
|---|---|---|
|
||||
| **Kernel + initrd** | `nixpkgs` 24.05 + `upboard.nix` initrd modules | Boot the Sintra hardware (eMMC via `sdhci-acpi`, validator/dispenser at `ttyJ5`/`ttyJ7`) |
|
||||
| **NixOS base** | `nixpkgs` 24.05 | systemd, Xorg, openbox, the `lamassu` user, sshd for provisioning |
|
||||
| **bitspire.service** | `deploy/nixos/bitspire-atm.nix` | systemd unit that launches the Electron kiosk |
|
||||
| **The Electron app** | `apps/machine` built into a nix derivation | The actual ATM UI + state machine + Lightning client |
|
||||
| **Hardware-specific config** | `deploy/nixos/hardware/upboard.nix` (or `douro.nix`, `batm3.nix`) | udev rules, kernel modules, panel calibration |
|
||||
|
||||
**Important:** The `VITE_LIGHTNING_PUB_PUBKEY` is required. Without it, the ATM cannot communicate with Lightning.Pub.
|
||||
## What's NOT in the image
|
||||
|
||||
## Auto-Start on Boot
|
||||
The image is **identity-free** by design. After flashing, the ATM has no concept of:
|
||||
- Which LNbits server to talk to
|
||||
- Which nostr relay to use
|
||||
- Its own nostr identity (signing key)
|
||||
- Which fiat currency to display (defaulted from build args, but overridable)
|
||||
|
||||
### Systemd Service
|
||||
All of these come from `/var/lib/bitspire/.env`, which is written by `provision-atm.sh` after first boot. This separation means a single image flavor can serve dev, staging, and prod just by changing the provisioning data.
|
||||
|
||||
Create `/etc/systemd/system/lamassu-kiosk.service`:
|
||||
## Pre-deployment checklist
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=bitSpire ATM Kiosk
|
||||
After=graphical.target network-online.target
|
||||
Wants=network-online.target
|
||||
Before flashing a Sintra you'll want:
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=lamassu
|
||||
Environment=DISPLAY=:0
|
||||
EnvironmentFile=/opt/lamassu/.env
|
||||
WorkingDirectory=/opt/lamassu
|
||||
ExecStart=/opt/lamassu/linux-unpacked/lamassu-machine --no-sandbox
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
- [ ] **An Alpine live USB** for use as the installer-OS on the Sintra (Alpine is small, has a working busybox toolchain, and apk is fast). The bitSpire image itself is *not* a live system — it expects to live on the eMMC.
|
||||
- [ ] **A second USB stick** to flash the bitSpire image onto (this is what you'll dd between on the Sintra).
|
||||
- [ ] **A running LNbits instance** with the nostr-native-transport branch built in. The dev compose at `~/dev/local/docker/regtest` provides one; production deployments point at `lnbits.aiolabs.dev` or your operator's instance.
|
||||
- [ ] **Network reachability between the Sintra and the LNbits host.** The ATM connects to the relay endpoint at `ws://<lnbits-host>:5001/nostrrelay/test` (no separate strfry container — LNbits ships its own `nostrrelay` extension).
|
||||
- [ ] **The LNbits server pubkey** (`docker logs <lnbits-container> | grep 'Public key (share this)'`).
|
||||
- [ ] **A freshly generated nostr private key** for the ATM (`openssl rand -hex 32`). Each ATM should have its own; never share keys between machines.
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
```
|
||||
## After deployment
|
||||
|
||||
Enable and start:
|
||||
Once the kiosk is up, useful things to know:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable lamassu-kiosk
|
||||
sudo systemctl start lamassu-kiosk
|
||||
```
|
||||
- **Service status:** `ssh lamassu@<atm> 'sudo systemctl status bitspire'`
|
||||
- **Live log tail:** `ssh lamassu@<atm> 'sudo journalctl -u bitspire -f'`
|
||||
- **Re-provision (e.g., wrong relay URL):** rerun `provision-atm.sh` from the dev box with the new env vars
|
||||
- **Push a code change without reflashing:** `nixos-rebuild switch --flake .#sintra-installed --target-host lamassu@<atm> --use-remote-sudo`
|
||||
- **Inspect transaction history:** `ssh lamassu@<atm> 'sudo bash /etc/nixos/atm-transactions.sh'` (queries `/var/lib/bitspire/state.db`)
|
||||
|
||||
### Monitoring
|
||||
## Related documentation
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
sudo systemctl status lamassu-kiosk
|
||||
|
||||
# View logs
|
||||
sudo journalctl -u lamassu-kiosk -f
|
||||
|
||||
# Restart after configuration changes
|
||||
sudo systemctl restart lamassu-kiosk
|
||||
```
|
||||
|
||||
## Hardware Verification
|
||||
|
||||
### Serial Port Access
|
||||
|
||||
1. Verify devices exist:
|
||||
|
||||
```bash
|
||||
ls -la /dev/ttyJ*
|
||||
# Expected: /dev/ttyJ4 (printer), /dev/ttyJ5 (validator), /dev/ttyJ7 (dispenser)
|
||||
```
|
||||
|
||||
2. Check user permissions:
|
||||
|
||||
```bash
|
||||
groups
|
||||
# Should include 'dialout'
|
||||
```
|
||||
|
||||
3. If not in dialout group:
|
||||
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
# Logout and login for changes to take effect
|
||||
```
|
||||
|
||||
### Display Configuration
|
||||
|
||||
The Sintra uses a 1080x1920 portrait display. Verify X11 is running:
|
||||
|
||||
```bash
|
||||
echo $DISPLAY
|
||||
# Should output :0 or similar
|
||||
|
||||
xdpyinfo | head -5
|
||||
# Should show display information
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Application Won't Start
|
||||
|
||||
1. **Missing display**: Ensure `DISPLAY=:0` is set
|
||||
2. **Sandbox error**: Use `--no-sandbox` flag
|
||||
3. **Permission denied**: Check file is executable (`chmod +x`)
|
||||
|
||||
### Hardware Not Responding
|
||||
|
||||
1. **Check serial ports exist**: `ls -la /dev/ttyJ*`
|
||||
2. **Check permissions**: User must be in `dialout` group
|
||||
3. **Check connections**: Ensure cables are properly seated
|
||||
4. **Power cycle hardware**: Turn validator/dispenser off and on
|
||||
|
||||
### Network Issues
|
||||
|
||||
1. **Test relay connection**: `websocat wss://your-relay.example.com`
|
||||
2. **Check DNS resolution**: `ping your-relay.example.com`
|
||||
3. **Verify firewall**: Ensure outbound WebSocket connections allowed
|
||||
|
||||
### Logs
|
||||
|
||||
Application logs are written to:
|
||||
|
||||
- **Systemd**: `journalctl -u lamassu-kiosk`
|
||||
- **Electron**: `~/.config/lamassu-machine/logs/`
|
||||
|
||||
## Updating
|
||||
|
||||
1. Build new version on development machine
|
||||
2. Stop the service:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop lamassu-kiosk
|
||||
```
|
||||
|
||||
3. Replace application files:
|
||||
|
||||
```bash
|
||||
scp -r apps/machine/release/linux-unpacked/* user@sintra:/opt/lamassu/linux-unpacked/
|
||||
```
|
||||
|
||||
4. Restart service:
|
||||
|
||||
```bash
|
||||
sudo systemctl start lamassu-kiosk
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- The `--no-sandbox` flag is required for Electron on some Linux configurations. This is acceptable for a dedicated kiosk machine.
|
||||
- Keep the ATM's nsec private key secure. It authorizes all transactions from this machine.
|
||||
- Use a dedicated user account (`lamassu`) with minimal privileges.
|
||||
- Consider firewall rules to restrict network access to only required services.
|
||||
- [deploy/nixos/README.md](../deploy/nixos/README.md) — the full step-by-step walkthrough and NixOS module reference
|
||||
- [device-configuration.md](./device-configuration.md) — hardware-specific configuration (validator types, cassette layouts, fiat currency)
|
||||
- [adr/001-hal-architecture.md](./adr/001-hal-architecture.md) — why HAL is TypeScript-in-Node rather than Rust-via-Tauri
|
||||
- [CLAUDE.md](../CLAUDE.md) — the dev-facing overview, including the hardware-specific gotchas we hard-learned on the first real Sintra flash
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue