diff --git a/src/main.ts b/src/main.ts index 514c703..15d346c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,32 +3,35 @@ import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import * as bitcoinjs from 'bitcoinjs-lib'; import { useContainer } from 'class-validator'; -import { readFileSync } from 'fs'; +import { readFileSync, watch } from 'fs'; +import * as path from 'path'; import * as ecc from 'tiny-secp256k1'; import { AppModule } from './app.module'; async function bootstrap() { - if (process.env.API_PORT == null) { console.error('It appears your environment is not configured, create and populate an .env file.'); return; } - let options = {}; - const secure = process.env.API_SECURE?.toLowerCase() == 'true'; + const secure = process.env.API_SECURE?.toLowerCase() === 'true'; + const currentDirectory = process.cwd(); + const keyPath = path.join(currentDirectory, 'secrets', 'key.pem'); + const certPath = path.join(currentDirectory, 'secrets', 'cert.pem'); + + let options: any = {}; if (secure) { - const currentDirectory = process.cwd(); options = { https: { - key: readFileSync(`${currentDirectory}/secrets/key.pem`), - cert: readFileSync(`${currentDirectory}/secrets/cert.pem`), + key: readFileSync(keyPath), + cert: readFileSync(certPath), } }; } const app = await NestFactory.create(AppModule, new FastifyAdapter(options)); - app.setGlobalPrefix('api') + app.setGlobalPrefix('api'); app.useGlobalPipes( new ValidationPipe({ transform: true, @@ -51,13 +54,53 @@ async function bootstrap() { app.enableCors(); useContainer(app.select(AppModule), { fallbackOnErrors: true }); - //Taproot + // Taproot bitcoinjs.initEccLib(ecc); await app.listen(process.env.API_PORT, '0.0.0.0', (err, address) => { + if (err) { + console.error(err); + process.exit(1); + } console.log(`API listening on ${address}`); }); + // --- Live-reload TLS certs/keys when they change on disk --- + if (secure) { + // Fastify's underlying Node https server + const server: any = app.getHttpServer(); + + // Guard: only HTTPS servers expose setSecureContext + if (typeof server?.setSecureContext === 'function') { + let reloadTimer: NodeJS.Timeout | null = null; + + const scheduleReload = () => { + if (reloadTimer) clearTimeout(reloadTimer); + // Debounce multiple fs events during a single write/replace + reloadTimer = setTimeout(() => { + try { + const key = readFileSync(keyPath); + const cert = readFileSync(certPath); + server.setSecureContext({ key, cert }); + console.log(`[TLS] Reloaded certificate @ ${new Date().toISOString()}`); + } catch (e) { + console.error('[TLS] Failed to reload certificate:', e); + } + }, 500); + }; + + // Watch both files; handle 'change' and 'rename' (rename often fired on atomic replace) + try { + watch(keyPath, { persistent: true }, scheduleReload); + watch(certPath, { persistent: true }, scheduleReload); + console.log('[TLS] Watching cert/key for changes'); + } catch (e) { + console.error('[TLS] Failed to watch cert/key files:', e); + } + } else { + console.warn('[TLS] Dynamic cert reload not available (non-HTTPS server?)'); + } + } } bootstrap();