From 56b1d594d77ac62efac65f216725fb8fbc9a1c92 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Fri, 30 Jan 2026 11:00:12 -0500 Subject: [PATCH] feat: add runtime config via Electron IPC - Add .env.example with all configuration options documented - Add get-config IPC handler in Electron main process - Expose getConfig() in preload for renderer access - Update lightning.ts to load config at runtime in Electron - Fall back to Vite build-time env vars for browser dev mode - Add TypeScript declarations for electronAPI This allows the same build to be deployed to different machines with different configurations (relay URL, Lightning.Pub pubkey, etc.) set via environment variables at runtime. Co-Authored-By: Claude Opus 4.5 --- lamassu-next/apps/machine/.env.example | 49 +++++++++++++++++ lamassu-next/apps/machine/electron/main.ts | 22 ++++++++ lamassu-next/apps/machine/electron/preload.ts | 21 ++++++++ .../apps/machine/src/services/lightning.ts | 52 +++++++++++++++++-- .../apps/machine/src/types/electron.d.ts | 28 ++++++++++ 5 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 lamassu-next/apps/machine/.env.example create mode 100644 lamassu-next/apps/machine/src/types/electron.d.ts diff --git a/lamassu-next/apps/machine/.env.example b/lamassu-next/apps/machine/.env.example new file mode 100644 index 0000000..28f8fc8 --- /dev/null +++ b/lamassu-next/apps/machine/.env.example @@ -0,0 +1,49 @@ +# Lamassu ATM Configuration +# Copy this file to .env and fill in your values + +# ============================================================================= +# Hardware Configuration +# ============================================================================= + +# Machine model preset (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 - JSON array) +# VITE_LAMASSU_CASSETTES='[{"denomination":20,"count":100}]' + +# ============================================================================= +# Lightning.Pub Connection (Required) +# ============================================================================= + +# Nostr relay WebSocket URL +VITE_RELAY_URL=ws://localhost:7777 + +# Lightning.Pub's Nostr public key (required!) +# Get from: docker logs lamassu-lightning-pub | grep pubkey +VITE_LIGHTNING_PUB_PUBKEY= + +# Lightning.Pub HTTP API URL (optional) +VITE_LIGHTNING_PUB_API_URL=http://localhost:1776 + +# ============================================================================= +# ATM Identity +# ============================================================================= + +# ATM's Nostr private key (hex format, 64 characters) +# Generate with: npx @lamassu/nostr-client generate-keypair +# If not set, generates ephemeral identity on each restart +VITE_ATM_PRIVATE_KEY= + +# ============================================================================= +# Development Only +# ============================================================================= + +# Lightning.Pub admin token (dev/testing only) +VITE_ADMIN_TOKEN=lamassu-dev-admin-token diff --git a/lamassu-next/apps/machine/electron/main.ts b/lamassu-next/apps/machine/electron/main.ts index c948e3e..e5df3e5 100644 --- a/lamassu-next/apps/machine/electron/main.ts +++ b/lamassu-next/apps/machine/electron/main.ts @@ -53,6 +53,28 @@ ipcMain.handle('get-version', () => { return app.getVersion() }) +/** + * Get runtime configuration from environment variables + * This allows configuration to be set at runtime (not baked in at build time) + */ +ipcMain.handle('get-config', () => { + return { + // Lightning.Pub connection + relayUrl: process.env.VITE_RELAY_URL || 'ws://localhost:7777', + lightningPubPubkey: process.env.VITE_LIGHTNING_PUB_PUBKEY || '', + lightningPubApiUrl: process.env.VITE_LIGHTNING_PUB_API_URL || 'http://localhost:1776', + atmPrivateKey: process.env.VITE_ATM_PRIVATE_KEY || '', + adminToken: process.env.VITE_ADMIN_TOKEN || '', + + // Hardware configuration + machineModel: process.env.VITE_LAMASSU_MACHINE_MODEL || 'sintra', + fiatCode: process.env.VITE_LAMASSU_FIAT_CODE || 'USD', + validatorDevice: process.env.VITE_LAMASSU_VALIDATOR_DEVICE, + dispenserDevice: process.env.VITE_LAMASSU_DISPENSER_DEVICE, + cassettes: process.env.VITE_LAMASSU_CASSETTES, + } +}) + // App lifecycle app.whenReady().then(() => { createWindow() diff --git a/lamassu-next/apps/machine/electron/preload.ts b/lamassu-next/apps/machine/electron/preload.ts index 5abcb95..bcd7c62 100644 --- a/lamassu-next/apps/machine/electron/preload.ts +++ b/lamassu-next/apps/machine/electron/preload.ts @@ -7,11 +7,31 @@ import { contextBridge, ipcRenderer } from 'electron' +/** + * Runtime configuration interface + * These values are read from environment variables at runtime (not build time) + */ +export interface RuntimeConfig { + relayUrl: string + lightningPubPubkey: string + lightningPubApiUrl: string + atmPrivateKey: string + adminToken: string + machineModel: string + fiatCode: string + validatorDevice?: string + dispenserDevice?: string + cassettes?: string +} + // Expose protected methods to renderer contextBridge.exposeInMainWorld('electronAPI', { // Get app version getVersion: (): Promise => ipcRenderer.invoke('get-version'), + // Get runtime configuration (read from environment at runtime) + getConfig: (): Promise => ipcRenderer.invoke('get-config'), + // Platform info platform: process.platform, }) @@ -21,6 +41,7 @@ declare global { interface Window { electronAPI: { getVersion: () => Promise + getConfig: () => Promise platform: NodeJS.Platform } } diff --git a/lamassu-next/apps/machine/src/services/lightning.ts b/lamassu-next/apps/machine/src/services/lightning.ts index 18eb69e..487e257 100644 --- a/lamassu-next/apps/machine/src/services/lightning.ts +++ b/lamassu-next/apps/machine/src/services/lightning.ts @@ -30,22 +30,42 @@ import { import type { OfferRequest } from '@lamassu/clink' import type { ATMServices, ATMContext } from '@lamassu/state-machine' +// Import Electron types +import type {} from '@/types/electron' + // Check if we're in a browser environment const isBrowser = typeof window !== 'undefined' +// Check if we're running in Electron (electronAPI is exposed via preload) +const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined + /** - * Lightning configuration from environment variables + * Lightning configuration * - * Environment variables (prefix with VITE_ for Vite): + * In Electron: Read from environment at runtime via IPC + * In Browser (dev): Read from Vite's import.meta.env (build-time) + * + * Environment variables: * - VITE_RELAY_URL: Nostr relay WebSocket URL * - VITE_LIGHTNING_PUB_PUBKEY: Lightning.Pub's Nostr pubkey (hex or npub) * - VITE_LIGHTNING_PUB_API_URL: Lightning.Pub HTTP API URL * - VITE_ATM_PRIVATE_KEY: ATM's Nostr private key (hex or nsec) * - VITE_ADMIN_TOKEN: Lightning.Pub admin token (dev only) */ -function loadLightningConfig() { +interface LightningConfig { + relayUrl: string + lightningPubPubkey: string + lightningPubApiUrl: string + adminToken: string + atmPrivateKey: string +} + +/** + * Load configuration - async to support Electron IPC + */ +async function loadLightningConfig(): Promise { // Development defaults (local Docker infrastructure) - const defaults = { + const defaults: LightningConfig = { relayUrl: 'ws://localhost:7777', lightningPubPubkey: '', lightningPubApiUrl: 'http://localhost:1776', @@ -53,6 +73,23 @@ function loadLightningConfig() { atmPrivateKey: '', } + // In Electron, get runtime config from main process + if (isElectron && window.electronAPI) { + try { + const runtimeConfig = await window.electronAPI.getConfig() + return { + relayUrl: runtimeConfig.relayUrl || defaults.relayUrl, + lightningPubPubkey: runtimeConfig.lightningPubPubkey || defaults.lightningPubPubkey, + lightningPubApiUrl: runtimeConfig.lightningPubApiUrl || defaults.lightningPubApiUrl, + adminToken: runtimeConfig.adminToken || defaults.adminToken, + atmPrivateKey: runtimeConfig.atmPrivateKey || defaults.atmPrivateKey, + } + } catch (e) { + console.warn('[Lightning] Failed to get runtime config from Electron:', e) + } + } + + // Fallback: Vite build-time env vars (for browser dev mode) return { relayUrl: import.meta.env.VITE_RELAY_URL || defaults.relayUrl, lightningPubPubkey: import.meta.env.VITE_LIGHTNING_PUB_PUBKEY || defaults.lightningPubPubkey, @@ -62,7 +99,8 @@ function loadLightningConfig() { } } -const CONFIG = loadLightningConfig() +// Config is loaded async now - will be set in initializeLightningServices +let CONFIG: LightningConfig interface LightningServices { nostrClient: NostrClient @@ -87,6 +125,10 @@ type PaymentReceivedCallback = (preimage: string) => void */ export async function initializeLightningServices(): Promise { console.log('[Lightning] Initializing services...') + + // Load configuration (async for Electron runtime config) + CONFIG = await loadLightningConfig() + console.log('[Lightning] Relay URL:', CONFIG.relayUrl) console.log('[Lightning] Lightning.Pub pubkey:', CONFIG.lightningPubPubkey || '(not configured)') diff --git a/lamassu-next/apps/machine/src/types/electron.d.ts b/lamassu-next/apps/machine/src/types/electron.d.ts new file mode 100644 index 0000000..40b30e7 --- /dev/null +++ b/lamassu-next/apps/machine/src/types/electron.d.ts @@ -0,0 +1,28 @@ +/** + * Type declarations for Electron API exposed via preload + */ + +export interface RuntimeConfig { + relayUrl: string + lightningPubPubkey: string + lightningPubApiUrl: string + atmPrivateKey: string + adminToken: string + machineModel: string + fiatCode: string + validatorDevice?: string + dispenserDevice?: string + cassettes?: string +} + +declare global { + interface Window { + electronAPI?: { + getVersion: () => Promise + getConfig: () => Promise + platform: NodeJS.Platform + } + } +} + +export {}