import cors from 'cors'; import express from 'express'; import crypto from 'node:crypto'; import http from 'node:http'; import https from 'node:https'; import { readFile } from 'node:fs/promises'; import { config } from './config.js'; import { backupRouter } from './routes/backup.js'; import { healthRouter } from './routes/health.js'; import { jobsRouter } from './routes/jobs.js'; import { restoreRouter } from './routes/restore.js'; import { schedulesRouter } from './routes/schedules.js'; import { settingsRouter } from './routes/settings.js'; import { snapshotsRouter } from './routes/snapshots.js'; import { vmsRouter } from './routes/vms.js'; import { startScheduler } from './scheduler.js'; const app = express(); app.use(cors()); app.use(express.json()); app.use((req, res, next) => { if (config.allowedManagementIps.length && !config.allowedManagementIps.includes(normalizeIp(req.ip))) { res.status(403).json({ error: 'Forbidden management source.' }); return; } const header = req.get('authorization') || ''; if (config.apiToken && timingSafeEqual(header, `Bearer ${config.apiToken}`)) { next(); return; } res.status(401).json({ error: 'Unauthorized.' }); }); function normalizeIp(value) { return String(value || '').replace(/^::ffff:/, ''); } function timingSafeEqual(a, b) { const bufA = Buffer.from(String(a)); const bufB = Buffer.from(String(b)); if (bufA.length !== bufB.length) return false; return crypto.timingSafeEqual(bufA, bufB); } app.use('/api/health', healthRouter); app.use('/api/vms', vmsRouter); app.use('/api/snapshots', snapshotsRouter); app.use('/api/jobs', jobsRouter); app.use('/api/backup', backupRouter); app.use('/api/restore', restoreRouter); app.use('/api/schedules', schedulesRouter); app.use('/api/settings', settingsRouter); app.use((error, _req, res, _next) => { const status = error.status || 500; res.status(status).json({ error: error.message || 'Internal server error.' }); }); startServer().catch((error) => { console.error(`Failed to start Incus backup API: ${error.message}`); process.exit(1); }); startScheduler().catch((error) => { console.error(`Failed to start scheduler: ${error.message}`); }); async function startServer() { const protocol = config.httpsEnabled ? 'https' : 'http'; const server = config.httpsEnabled ? https.createServer({ cert: await readFile(config.tlsCertFile), key: await readFile(config.tlsKeyFile), }, app) : http.createServer(app); server.listen(config.port, config.host, () => { console.log(`Incus backup API listening on ${protocol}://${config.host}:${config.port}`); }); }