83 lines
2.6 KiB
JavaScript
83 lines
2.6 KiB
JavaScript
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}`);
|
|
});
|
|
}
|