/** * Бэкапы MikroTik на локальной ФС (вместо S3). * Логические ключи сохраняют вид backups/mikrotik/{serverId}/{ts}-{rand}.rsc */ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const BACKUP_PREFIX = 'backups/mikrotik'; function backupRootDir() { const raw = process.env.MIKROTIK_BACKUP_DIR || path.join(__dirname, '..', 'data', 'backups', 'mikrotik'); if (!fs.existsSync(raw)) { fs.mkdirSync(raw, { recursive: true }); } return raw; } function parseBackupKey(key) { const m = String(key || '').match(/^backups\/mikrotik\/([^/]+)\/(.+)$/); if (!m) return null; return { safeId: m[1], fileName: m[2] }; } function fsPathForKey(key) { const p = parseBackupKey(key); if (!p) return null; return path.join(backupRootDir(), p.safeId, p.fileName); } function buildBackupKey(serverId, createdAt, suffix) { const safeId = String(serverId || 'unknown').replace(/[^a-zA-Z0-9._-]/g, '_'); const ts = createdAt.toISOString().replace(/[:-]/g, '').replace(/\.\d+Z$/, 'Z'); const rand = suffix || crypto.randomBytes(3).toString('hex'); return `${BACKUP_PREFIX}/${safeId}/${ts}-${rand}.rsc`; } function etagForContent(content) { const h = crypto.createHash('sha256').update(Buffer.from(String(content), 'utf8')).digest('hex'); return `"${h}"`; } async function writeBackupFile(key, content, contentType = 'text/plain') { const p = parseBackupKey(key); if (!p) throw new Error('Invalid backup key'); const dir = path.join(backupRootDir(), p.safeId); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const fp = path.join(dir, p.fileName); await fs.promises.writeFile(fp, String(content), 'utf8'); const st = await fs.promises.stat(fp); const etag = etagForContent(String(content)); return { etag, lastModified: st.mtime.toISOString(), contentLength: st.size, }; } async function readBackupFile(key) { const fp = fsPathForKey(key); if (!fp) throw new Error('Invalid backup key'); const body = await fs.promises.readFile(fp, 'utf8'); const st = await fs.promises.stat(fp); const etag = etagForContent(body); return { body, etag, lastModified: st.mtime.toISOString(), contentLength: st.size, }; } async function listBackupObjects(serverId, maxKeys = 200) { const safeId = String(serverId || '').replace(/[^a-zA-Z0-9._-]/g, '_'); const dir = path.join(backupRootDir(), safeId); if (!fs.existsSync(dir)) return []; const names = await fs.promises.readdir(dir); const out = []; for (const name of names) { if (out.length >= maxKeys) break; const fp = path.join(dir, name); const st = await fs.promises.stat(fp); if (!st.isFile()) continue; const key = `${BACKUP_PREFIX}/${safeId}/${name}`; out.push({ key, size: st.size, lastModified: st.mtime.toISOString(), etag: etagForContent(await fs.promises.readFile(fp, 'utf8')), }); } return out; } module.exports = { BACKUP_PREFIX, buildBackupKey, writeBackupFile, readBackupFile, listBackupObjects, backupRootDir, };