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});