feat: add VITE_MAINTENANCE_MODE env var for service screen

Set VITE_MAINTENANCE_MODE=true in .env to show an "Under Service"
screen and block all transactions. No hardware init, no Lightning
connection — just a static screen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-03-28 00:50:34 -04:00
commit ca6d8c4f68
5 changed files with 34 additions and 3 deletions

View file

@ -49,6 +49,13 @@ VITE_ATM_PRIVATE_KEY=
# (manual dispense, remote management). Decoupled from Lightning.Pub identity. # (manual dispense, remote management). Decoupled from Lightning.Pub identity.
# VITE_OPERATOR_PUBKEYS=abcd1234...,ef567890... # VITE_OPERATOR_PUBKEYS=abcd1234...,ef567890...
# =============================================================================
# Maintenance Mode
# =============================================================================
# Show "Under Service" screen and block all transactions
# VITE_MAINTENANCE_MODE=true
# ============================================================================= # =============================================================================
# Mock Fallback (Production Safety) # Mock Fallback (Production Safety)
# ============================================================================= # =============================================================================

View file

@ -187,6 +187,9 @@ ipcMain.handle('get-config', () => {
// Operator identity (comma-separated hex pubkeys) // Operator identity (comma-separated hex pubkeys)
operatorPubkeys: process.env.VITE_OPERATOR_PUBKEYS || '', operatorPubkeys: process.env.VITE_OPERATOR_PUBKEYS || '',
// Maintenance mode — show "out of service" screen
maintenanceMode: process.env.VITE_MAINTENANCE_MODE === 'true',
} }
}) })

View file

@ -26,6 +26,7 @@ export interface RuntimeConfig {
cassettes?: string cassettes?: string
allowMockFallback: boolean allowMockFallback: boolean
operatorPubkeys: string operatorPubkeys: string
maintenanceMode: boolean
} }
/** /**

View file

@ -33,6 +33,16 @@ onMounted(async () => {
}) })
} }
// Check maintenance mode before initializing anything
const config = isElectron ? await window.electronAPI?.getConfig() : null
const maintenanceMode =
config?.maintenanceMode || import.meta.env.VITE_MAINTENANCE_MODE === 'true'
if (maintenanceMode) {
atmStore.initError = 'maintenance'
return
}
try { try {
if (isElectron) { if (isElectron) {
await atmStore.initializeForProduction() await atmStore.initializeForProduction()
@ -88,11 +98,20 @@ function toggleLiveServices() {
<line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" /> <line x1="12" y1="17" x2="12.01" y2="17" />
</svg> </svg>
<h1 class="text-2xl lg:text-[3.5rem] font-bold">ATM Unavailable</h1> <h1 class="text-2xl lg:text-[3.5rem] font-bold">
{{ atmStore.initError === 'maintenance' ? 'Under Service' : 'ATM Unavailable' }}
</h1>
<p class="max-w-md text-center text-base lg:text-2xl text-muted-foreground"> <p class="max-w-md text-center text-base lg:text-2xl text-muted-foreground">
This machine is temporarily out of service. Please try again later or use another machine. {{
atmStore.initError === 'maintenance'
? 'This machine is currently being serviced. We will be back shortly.'
: 'This machine is temporarily out of service. Please try again later or use another machine.'
}}
</p> </p>
<p v-if="atmStore.debugMode" class="max-w-lg text-center font-mono text-sm text-destructive"> <p
v-if="atmStore.debugMode && atmStore.initError !== 'maintenance'"
class="max-w-lg text-center font-mono text-sm text-destructive"
>
{{ atmStore.initError }} {{ atmStore.initError }}
</p> </p>
</div> </div>

View file

@ -15,6 +15,7 @@ export interface RuntimeConfig {
cassettes?: string cassettes?: string
allowMockFallback: boolean allowMockFallback: boolean
operatorPubkeys: string operatorPubkeys: string
maintenanceMode: boolean
} }
export interface AtmSecrets { export interface AtmSecrets {