Enables the CCP to manage CML instances on remote servers via a lightweight HTTP agent. Key components: - ExecutionDriver abstraction (local-driver.ts / remote-driver.ts) routes operations to local Docker or remote agent transparently - Remote agent package (agent/) with mTLS authentication, Docker Compose operations, file management, backup/upgrade delegation - Certificate service using openssl CLI for CA management and cert issuance - Phone-home registration: remote agents register via invite code, CCP admin approves, agent receives mTLS cert bundle automatically - config.sh integration with configure_control_panel() section - ccp-agent Docker Compose service (profile-gated) - Frontend: AgentRegistrationsPage, InviteCodesPage, Remote Agents sidebar menu - Security hardened: cert bundle wiped after delivery, shell injection prevention via execFile, command allowlist with metachar rejection, rate-limited public endpoints, auto-populated fingerprint pinning Also wires ENABLE_SOCIAL/PEOPLE/ANALYTICS through env.ts, seed.ts, and docker-compose env passthrough (from previous session). Bunker Admin
86 lines
3.0 KiB
TypeScript
86 lines
3.0 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 agentRoutes from './modules/agents/agents.routes';
|
|
import certificateRoutes from './modules/certificates/certificates.routes';
|
|
import inviteCodeRoutes from './modules/invite-codes/invite-codes.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);
|
|
app.use('/api/agents', agentRoutes);
|
|
app.use('/api/certificates', certificateRoutes);
|
|
app.use('/api/invite-codes', inviteCodeRoutes);
|
|
|
|
// 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;
|