fix: defer wallet encryption key check so server starts without it

Move crypto key init from module-level throw to lazy getKey() — only
errors when encrypt/decrypt are actually called. Downgrade env var
check in index.ts from fatal exit to warning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 10:43:30 +00:00
co-authored by Claude Opus 4.6
parent f6eb7d2845
commit 673961580c
2 changed files with 22 additions and 16 deletions
+18 -11
View File
@@ -1,31 +1,38 @@
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto' import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
const ENCRYPTION_KEY_HEX = process.env.BOTFIGHTS_WALLET_ENCRYPTION_KEY let encryptionKey: Buffer | null = null
let encryptionKey: Buffer
if (ENCRYPTION_KEY_HEX) { function getKey(): Buffer {
encryptionKey = Buffer.from(ENCRYPTION_KEY_HEX, 'hex') if (encryptionKey) return encryptionKey
} else if (process.env.NODE_ENV === 'production') {
throw new Error('CRITICAL: BOTFIGHTS_WALLET_ENCRYPTION_KEY not set. Cannot start in production.') const hex = process.env.BOTFIGHTS_WALLET_ENCRYPTION_KEY
} else { if (hex) {
encryptionKey = randomBytes(32) encryptionKey = Buffer.from(hex, 'hex')
console.warn('[crypto] WARNING: No BOTFIGHTS_WALLET_ENCRYPTION_KEY set. Generated random key — wallet data will be lost on restart.') } else if (process.env.NODE_ENV === 'production') {
throw new Error('BOTFIGHTS_WALLET_ENCRYPTION_KEY not set. Wallet operations unavailable.')
} else {
encryptionKey = randomBytes(32)
console.warn('[crypto] WARNING: No BOTFIGHTS_WALLET_ENCRYPTION_KEY set. Generated random key — wallet data will be lost on restart.')
}
return encryptionKey
} }
export function encrypt(plaintext: string): string { export function encrypt(plaintext: string): string {
const key = getKey()
const iv = randomBytes(16) const iv = randomBytes(16)
const cipher = createCipheriv('aes-256-gcm', encryptionKey, iv) const cipher = createCipheriv('aes-256-gcm', key, iv)
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
const authTag = cipher.getAuthTag() const authTag = cipher.getAuthTag()
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted.toString('hex') return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted.toString('hex')
} }
export function decrypt(ciphertext: string): string { export function decrypt(ciphertext: string): string {
const key = getKey()
const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':') const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':')
const iv = Buffer.from(ivHex, 'hex') const iv = Buffer.from(ivHex, 'hex')
const authTag = Buffer.from(authTagHex, 'hex') const authTag = Buffer.from(authTagHex, 'hex')
const encrypted = Buffer.from(encryptedHex, 'hex') const encrypted = Buffer.from(encryptedHex, 'hex')
const decipher = createDecipheriv('aes-256-gcm', encryptionKey, iv) const decipher = createDecipheriv('aes-256-gcm', key, iv)
decipher.setAuthTag(authTag) decipher.setAuthTag(authTag)
return decipher.update(encrypted) + decipher.final('utf8') return decipher.update(encrypted) + decipher.final('utf8')
} }
+4 -5
View File
@@ -4,13 +4,12 @@ import { runMigrations } from './db/startup.js'
import { seedMockBots, seedClassicBots } from './engine/mock.js' import { seedMockBots, seedClassicBots } from './engine/mock.js'
import { startBackgroundFights } from './engine/background.js' import { startBackgroundFights } from './engine/background.js'
// Production env validation // Production env validation — warn but don't crash (wallet features degrade gracefully)
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
const required = ['BOTFIGHTS_NWC_URL', 'BOTFIGHTS_WALLET_ENCRYPTION_KEY'] const walletVars = ['BOTFIGHTS_NWC_URL', 'BOTFIGHTS_WALLET_ENCRYPTION_KEY']
const missing = required.filter(k => !process.env[k]) const missing = walletVars.filter(k => !process.env[k])
if (missing.length > 0) { if (missing.length > 0) {
console.error(`[FATAL] Missing required env vars for production: ${missing.join(', ')}`) console.warn(`[botfights] WARNING: Missing env vars: ${missing.join(', ')} — wallet/payment features disabled`)
process.exit(1)
} }
} }