Backend: SQLite storage, EvoBGP integration, filters in SQL
Made-with: Cursor
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Роуты для бэкапов конфигурации MikroTik
|
||||
* Хранят сырый текст конфигурации (например, вывод /export) в S3.
|
||||
* Хранят сырый текст конфигурации (например, вывод /export) на локальной ФС.
|
||||
*
|
||||
* Ключи в S3: backups/mikrotik/{serverId}/{timestamp}-{rand}.rsc
|
||||
* Логические ключи: backups/mikrotik/{serverId}/{timestamp}-{rand}.rsc
|
||||
*
|
||||
* Задачи:
|
||||
* - создать бэкап (ручной или из планировщика)
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
const crypto = require('crypto');
|
||||
const { sendError, sendOk } = require('../middleware/errorHandler');
|
||||
const { writeS3TextObject, readS3TextObject, listS3Objects } = require('../services/s3Service');
|
||||
const { writeBackupFile, readBackupFile, listBackupObjects } = require('../services/backupFsService');
|
||||
const { readServersFromS3 } = require('./serversRoutes');
|
||||
const { createRosClient, fetchExportViaFile } = require('../services/mikrotikApplyService');
|
||||
const { encrypt, decrypt } = require('../utils/encryption');
|
||||
@@ -61,7 +61,7 @@ async function saveBackupForServer(serverId, config, { source, comment } = {}) {
|
||||
|
||||
let content = `${headerLines.join('\n')}\n${String(config || '').replace(/^\uFEFF/, '')}`;
|
||||
|
||||
// Шифруем содержимое на S3, если задан ENCRYPTION_KEY
|
||||
// Шифруем содержимое файла, если задан ENCRYPTION_KEY
|
||||
if (process.env.ENCRYPTION_KEY) {
|
||||
try {
|
||||
content = ENCRYPTED_PREFIX + encrypt(content);
|
||||
@@ -71,15 +71,15 @@ async function saveBackupForServer(serverId, config, { source, comment } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const meta = await writeS3TextObject(key, content, 'text/plain');
|
||||
return { key, createdAt: now.toISOString(), s3: meta };
|
||||
const meta = await writeBackupFile(key, content, 'text/plain');
|
||||
return { key, createdAt: now.toISOString(), storage: meta };
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mikrotik/backups
|
||||
* Body: { serverId: string, config: string, source?: string, comment?: string }
|
||||
*
|
||||
* Сохраняет текст конфигурации (обычно вывод /export) в S3.
|
||||
* Сохраняет текст конфигурации (обычно вывод /export) в каталог бэкапов на диске.
|
||||
* Автоматический бэкап можно организовать внешним планировщиком, дергающим этот эндпоинт.
|
||||
*/
|
||||
async function createBackup(req, res) {
|
||||
@@ -93,14 +93,14 @@ async function createBackup(req, res) {
|
||||
return sendError(res, 400, 'config (MikroTik export text) is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
const { key, createdAt, s3 } = await saveBackupForServer(serverId, config, { source, comment });
|
||||
const { key, createdAt, storage } = await saveBackupForServer(serverId, config, { source, comment });
|
||||
|
||||
return res.json({
|
||||
ok: true,
|
||||
key,
|
||||
serverId,
|
||||
createdAt,
|
||||
s3,
|
||||
storage,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('createBackup error:', error);
|
||||
@@ -120,12 +120,11 @@ async function listBackups(req, res) {
|
||||
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
const prefix = `${BACKUP_PREFIX}/${String(serverId).replace(/[^a-zA-Z0-9._-]/g, '_')}/`;
|
||||
const objects = await listS3Objects(prefix, { maxKeys: 200 });
|
||||
const safeId = String(serverId).replace(/[^a-zA-Z0-9._-]/g, '_');
|
||||
const objects = await listBackupObjects(safeId, 200);
|
||||
|
||||
const items = objects
|
||||
.filter(o => o.key && o.key.startsWith(prefix))
|
||||
.map(o => ({
|
||||
.map((o) => ({
|
||||
key: o.key,
|
||||
serverId,
|
||||
createdAt: o.lastModified,
|
||||
@@ -156,7 +155,7 @@ async function getBackup(req, res) {
|
||||
return sendError(res, 400, 'Invalid backup key', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
const data = await readS3TextObject(key);
|
||||
const data = await readBackupFile(key);
|
||||
const config = decryptBackupContent(data.body || '');
|
||||
|
||||
return res.json({
|
||||
@@ -191,8 +190,8 @@ async function diffBackups(req, res) {
|
||||
}
|
||||
|
||||
const [a, b] = await Promise.all([
|
||||
readS3TextObject(keyA),
|
||||
readS3TextObject(keyB),
|
||||
readBackupFile(keyA),
|
||||
readBackupFile(keyB),
|
||||
]);
|
||||
|
||||
const bodyA = decryptBackupContent(a.body || '');
|
||||
@@ -265,7 +264,7 @@ async function getMikrotikClientForServerId(serverId) {
|
||||
* POST /api/mikrotik/backups/run
|
||||
* Body: { serverId: string }
|
||||
*
|
||||
* Немедленно делает /rest/export compact на указанном MikroTik и сохраняет результат в S3.
|
||||
* Немедленно делает /rest/export compact на указанном MikroTik и сохраняет результат на диск.
|
||||
*/
|
||||
async function runBackupNow(req, res) {
|
||||
try {
|
||||
@@ -293,7 +292,7 @@ async function runBackupNow(req, res) {
|
||||
return sendError(res, 502, 'Empty export result from MikroTik', 'E_EMPTY_EXPORT');
|
||||
}
|
||||
|
||||
const { key, createdAt, s3 } = await saveBackupForServer(serverId, config, {
|
||||
const { key, createdAt, storage } = await saveBackupForServer(serverId, config, {
|
||||
source: 'manual',
|
||||
comment: 'Manual backup from UI (/api/mikrotik/backups/run)',
|
||||
});
|
||||
@@ -303,7 +302,7 @@ async function runBackupNow(req, res) {
|
||||
key,
|
||||
serverId,
|
||||
createdAt,
|
||||
s3,
|
||||
storage,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('runBackupNow error:', error?.message, error?.stack, error?.response?.data);
|
||||
|
||||
Reference in New Issue
Block a user