Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
1import app from './server'; 2import { serve } from '@hono/node-server'; 3import { FirehoseWorker } from './lib/firehose'; 4import { createLogger, initializeGrafanaExporters } from '@wisp/observability'; 5import { mkdirSync, existsSync } from 'fs'; 6import { backfillCache } from './lib/backfill'; 7import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode, closeDatabase } from './lib/db'; 8 9// Initialize Grafana exporters if configured 10initializeGrafanaExporters({ 11 serviceName: 'hosting-service', 12 serviceVersion: '1.0.0' 13}); 14 15const logger = createLogger('hosting-service'); 16 17const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001; 18const CACHE_DIR = process.env.CACHE_DIR || './cache/sites'; 19const BACKFILL_CONCURRENCY = process.env.BACKFILL_CONCURRENCY 20 ? parseInt(process.env.BACKFILL_CONCURRENCY) 21 : undefined; // Let backfill.ts default (10) apply 22 23// Parse CLI arguments 24const args = process.argv.slice(2); 25const hasBackfillFlag = args.includes('--backfill'); 26const backfillOnStartup = hasBackfillFlag || process.env.BACKFILL_ON_STARTUP === 'true'; 27 28// Cache-only mode: service will only cache files locally, no DB writes 29const hasCacheOnlyFlag = args.includes('--cache-only'); 30export const CACHE_ONLY_MODE = hasCacheOnlyFlag || process.env.CACHE_ONLY_MODE === 'true'; 31 32// Configure cache-only mode in database module 33if (CACHE_ONLY_MODE) { 34 setCacheOnlyMode(true); 35} 36 37// Ensure cache directory exists 38if (!existsSync(CACHE_DIR)) { 39 mkdirSync(CACHE_DIR, { recursive: true }); 40 console.log('Created cache directory:', CACHE_DIR); 41} 42 43// Start domain cache cleanup 44startDomainCacheCleanup(); 45 46// Start firehose worker with observability logger 47const firehose = new FirehoseWorker((msg, data) => { 48 logger.info(msg, data); 49}); 50 51firehose.start(); 52 53// Run backfill if requested 54if (backfillOnStartup) { 55 console.log('🔄 Backfill requested, starting cache backfill...'); 56 backfillCache({ 57 skipExisting: true, 58 concurrency: BACKFILL_CONCURRENCY, 59 }).then((stats) => { 60 console.log('✅ Cache backfill completed'); 61 }).catch((err) => { 62 console.error('❌ Cache backfill error:', err); 63 }); 64} 65 66// Add health check endpoint 67app.get('/health', (c) => { 68 const firehoseHealth = firehose.getHealth(); 69 return c.json({ 70 status: 'ok', 71 firehose: firehoseHealth, 72 }); 73}); 74 75// Start HTTP server with Node.js adapter 76const server = serve({ 77 fetch: app.fetch, 78 port: PORT, 79}); 80 81console.log(` 82Wisp Hosting Service 83 84Server: http://localhost:${PORT} 85Health: http://localhost:${PORT}/health 86Cache: ${CACHE_DIR} 87Firehose: Connected to Firehose 88Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'} 89Backfill: ${backfillOnStartup ? `ENABLED (concurrency: ${BACKFILL_CONCURRENCY || 10})` : 'DISABLED'} 90`); 91 92// Graceful shutdown 93process.on('SIGINT', async () => { 94 console.log('\n🛑 Shutting down...'); 95 firehose.stop(); 96 stopDomainCacheCleanup(); 97 await closeDatabase(); 98 server.close(); 99 process.exit(0); 100}); 101 102process.on('SIGTERM', async () => { 103 console.log('\n🛑 Shutting down...'); 104 firehose.stop(); 105 stopDomainCacheCleanup(); 106 await closeDatabase(); 107 server.close(); 108 process.exit(0); 109});