All 13 nginx embed proxy ports (8881-8895) are now driven by environment variables instead of being hardcoded. This prevents port conflicts when running multiple Changemaker instances on the same host. Chain: .env → docker-compose port mappings → nginx container env → entrypoint.sh envsubst → services.conf.template listen directives → API /services/config endpoint → frontend buildServiceUrl(). Existing deployments are unaffected (all vars default to current values). Bunker Admin
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
import 'express-async-errors';
|
|
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import compression from 'compression';
|
|
import rateLimit from 'express-rate-limit';
|
|
import { env } from './config/env';
|
|
import { logger } from './utils/logger';
|
|
import { errorHandler } from './middleware/error-handler';
|
|
|
|
// Route imports
|
|
import authRoutes from './modules/auth/auth.routes';
|
|
import instanceRoutes from './modules/instances/instances.routes';
|
|
import settingsRoutes from './modules/settings/settings.routes';
|
|
import healthRoutes from './modules/health/health.routes';
|
|
import auditRoutes from './modules/audit/audit.routes';
|
|
import backupRoutes from './modules/backups/backup.routes';
|
|
import eventsRoutes, { instanceEventsRouter } from './modules/events/events.routes';
|
|
import { startHealthScheduler } from './services/health.service';
|
|
import { autoDiscoverOnStartup } from './services/discovery.service';
|
|
|
|
const app = express();
|
|
|
|
// Global middleware
|
|
app.use(helmet());
|
|
app.use(compression());
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(
|
|
cors({
|
|
origin: env.CORS_ORIGINS.split(',').map((s) => s.trim()),
|
|
credentials: true,
|
|
})
|
|
);
|
|
|
|
// Rate limiters
|
|
const authLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 15, // 15 attempts per window
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { error: { message: 'Too many attempts, please try again later', code: 'RATE_LIMITED' } },
|
|
});
|
|
|
|
// Global API rate limiter — safety net against resource exhaustion
|
|
const apiLimiter = rateLimit({
|
|
windowMs: 60 * 1000, // 1 minute
|
|
max: 300, // 300 req/min per IP (generous for a control panel)
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { error: { message: 'Too many requests, please try again later', code: 'RATE_LIMITED' } },
|
|
});
|
|
|
|
// Routes
|
|
app.use('/api', apiLimiter);
|
|
app.use('/api/auth', authLimiter, authRoutes);
|
|
app.use('/api/instances', instanceRoutes);
|
|
app.use('/api/settings', settingsRoutes);
|
|
app.use('/api/health', healthRoutes);
|
|
app.use('/api/audit', auditRoutes);
|
|
app.use('/api/backups', backupRoutes);
|
|
app.use('/api/events', eventsRoutes);
|
|
app.use('/api/instances/:id/events', instanceEventsRouter);
|
|
|
|
// Error handler (must be last)
|
|
app.use(errorHandler);
|
|
|
|
app.listen(env.PORT, () => {
|
|
logger.info(`CCP API listening on port ${env.PORT} (${env.NODE_ENV})`);
|
|
startHealthScheduler(env.HEALTH_CHECK_INTERVAL_MS);
|
|
|
|
// Auto-discover parent CML instance on first boot (5s delay for DB readiness)
|
|
setTimeout(() => {
|
|
autoDiscoverOnStartup().catch((err) =>
|
|
logger.error(`[discovery] Auto-discovery failed: ${(err as Error).message}`)
|
|
);
|
|
}, 5_000);
|
|
});
|
|
|
|
export default app;
|