Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m27s
186 lines
6.6 KiB
JavaScript
186 lines
6.6 KiB
JavaScript
/**
|
|
* Простейший планировщик автоматических бэкапов MikroTik.
|
|
*
|
|
* Работает внутри Node-процесса backend:
|
|
* - периодически обходит jumphost-сервера из servers.json
|
|
* - вызывает RouterOS REST API /rest/export (compact)
|
|
* - сохраняет конфиг в S3 через saveBackupForServer
|
|
*
|
|
* Управляется через переменные окружения:
|
|
* - MIKROTIK_BACKUP_ENABLED=true|false (по умолчанию true)
|
|
* - MIKROTIK_BACKUP_INTERVAL_MINUTES=60 (интервал между запусками)
|
|
* - MIKROTIK_BACKUP_SERVERS="id1,id2" (если не указано — все jumphost)
|
|
*/
|
|
|
|
const { readServersFromS3 } = require('../routes/serversRoutes');
|
|
const { createRosClient, fetchExportViaFile } = require('./mikrotikApplyService');
|
|
const { saveBackupForServer } = require('../routes/mikrotikBackupRoutes');
|
|
const { decrypt } = require('../utils/encryption');
|
|
const { readS3TextObject } = require('../services/s3Service');
|
|
|
|
const DEFAULT_INTERVAL_MIN = 60;
|
|
|
|
function parseServerIds(envValue) {
|
|
if (!envValue) return null;
|
|
const parts = String(envValue)
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean);
|
|
return parts.length > 0 ? parts : null;
|
|
}
|
|
|
|
/**
|
|
* Прочитать список серверов для автобэкапов из UI-настроек (rt_ui_settings.json).
|
|
* Если поле mikrotikBackupServers отсутствует или пустое — вернуть null.
|
|
*/
|
|
async function getServerIdsFromUiSettings(logger) {
|
|
try {
|
|
const data = await readS3TextObject('bgp_data/rt_ui_settings.json').catch(() => null);
|
|
if (!data || !data.body) return null;
|
|
const parsed = JSON.parse(data.body || '{}');
|
|
const list = parsed && Array.isArray(parsed.mikrotikBackupServers)
|
|
? parsed.mikrotikBackupServers
|
|
: null;
|
|
if (!list || list.length === 0) return null;
|
|
return list.map((v) => String(v)).filter(Boolean);
|
|
} catch (e) {
|
|
(logger || console).warn(
|
|
{ component: 'mikrotik-backup', err: e && e.message },
|
|
'Failed to read mikrotikBackupServers from ui-settings',
|
|
);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchRouterExport(client) {
|
|
// API не возвращает текст /export напрямую — экспортируем в файл, читаем, удаляем
|
|
return fetchExportViaFile(client);
|
|
}
|
|
|
|
async function runBackupOnce(logger) {
|
|
const log = logger || console;
|
|
try {
|
|
const allServers = await readServersFromS3();
|
|
const uiIds = await getServerIdsFromUiSettings(log);
|
|
const envIds = parseServerIds(process.env.MIKROTIK_BACKUP_SERVERS);
|
|
const wantedIds = (uiIds && uiIds.length > 0) ? uiIds : envIds;
|
|
|
|
const jumphosts = (allServers || []).filter((s) => {
|
|
if (!s || String(s.type || '').toLowerCase() !== 'jumphost') return false;
|
|
if (!wantedIds) return true;
|
|
const id = s.id || s.dns || s.ip;
|
|
return id && wantedIds.includes(String(id));
|
|
});
|
|
|
|
if (jumphosts.length === 0) {
|
|
log.info({ component: 'mikrotik-backup' }, 'No jumphost servers found for backup');
|
|
return;
|
|
}
|
|
|
|
for (const server of jumphosts) {
|
|
const id = server.id || server.dns || server.ip || 'unknown';
|
|
try {
|
|
const host = server.mikrotikHost || server.ip || server.dns;
|
|
const port = server.mikrotikPort || 80;
|
|
const user = server.mikrotikUser || 'admin';
|
|
|
|
if (!host) {
|
|
log.warn({ component: 'mikrotik-backup', serverId: id }, 'Skip backup: no MikroTik host');
|
|
continue;
|
|
}
|
|
|
|
let password = '';
|
|
if (server.encryptedMikrotikPassword) {
|
|
try {
|
|
password = decrypt(server.encryptedMikrotikPassword);
|
|
} catch (e) {
|
|
log.error(
|
|
{ component: 'mikrotik-backup', serverId: id, err: e && e.message },
|
|
'Failed to decrypt MikroTik password, skipping backup',
|
|
);
|
|
continue;
|
|
}
|
|
} else {
|
|
log.warn(
|
|
{ component: 'mikrotik-backup', serverId: id },
|
|
'Skip backup: no encryptedMikrotikPassword configured',
|
|
);
|
|
continue;
|
|
}
|
|
const creds = {
|
|
host,
|
|
port: Number(port) || 80,
|
|
user,
|
|
password,
|
|
secure: false,
|
|
};
|
|
|
|
const client = createRosClient(creds);
|
|
log.info({ component: 'mikrotik-backup', serverId: id, host: creds.host }, 'Starting automatic backup via REST /export');
|
|
const configText = await fetchRouterExport(client);
|
|
|
|
if (!configText || !configText.trim()) {
|
|
log.warn({ component: 'mikrotik-backup', serverId: id }, 'Skip backup: empty export result');
|
|
continue;
|
|
}
|
|
|
|
const result = await saveBackupForServer(id, configText, {
|
|
source: 'scheduler',
|
|
comment: 'Automatic MikroTik backup (REST /export)',
|
|
});
|
|
|
|
log.info(
|
|
{ component: 'mikrotik-backup', serverId: id, key: result.key },
|
|
'Automatic backup saved to S3',
|
|
);
|
|
} catch (err) {
|
|
log.error(
|
|
{ component: 'mikrotik-backup', serverId: id, err: err && err.message },
|
|
'Failed to create automatic backup',
|
|
);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
const msg = err && err.message ? err.message : String(err);
|
|
(logger || console).error({ component: 'mikrotik-backup', err: msg }, 'Backup run failed');
|
|
}
|
|
}
|
|
|
|
function initMikrotikBackupScheduler(logger) {
|
|
const enabledEnv = String(process.env.MIKROTIK_BACKUP_ENABLED || 'true').toLowerCase();
|
|
const enabled = enabledEnv !== 'false' && enabledEnv !== '0' && enabledEnv !== 'off';
|
|
const log = logger || console;
|
|
|
|
if (!enabled) {
|
|
log.info({ component: 'mikrotik-backup' }, 'Automatic MikroTik backup scheduler is disabled');
|
|
return;
|
|
}
|
|
|
|
const intervalMin =
|
|
Number(process.env.MIKROTIK_BACKUP_INTERVAL_MINUTES || DEFAULT_INTERVAL_MIN) || DEFAULT_INTERVAL_MIN;
|
|
const intervalMs = Math.max(5, intervalMin) * 60 * 1000;
|
|
|
|
const intervalMinutes = intervalMs / 60000;
|
|
log.info(
|
|
{
|
|
component: 'mikrotik-backup',
|
|
intervalMinutes,
|
|
nextRunInMinutes: intervalMinutes,
|
|
servers: process.env.MIKROTIK_BACKUP_SERVERS || 'all jumphost',
|
|
},
|
|
'Automatic MikroTik backup scheduler started (no backup on startup, first run in %d min)',
|
|
intervalMinutes,
|
|
);
|
|
|
|
// Только по расписанию — без бэкапа при перезапуске контейнера
|
|
setInterval(() => {
|
|
runBackupOnce(log);
|
|
}, intervalMs);
|
|
}
|
|
|
|
module.exports = {
|
|
initMikrotikBackupScheduler,
|
|
runBackupOnce,
|
|
};
|
|
|