feat: enhance backups page with live data loading and backup job management
Implemented live data fetching for servers and backups on the backups page, replacing static initial data. Added functionality for manual backup creation and job status tracking, including error handling and UI updates. Updated the network map layout to improve node prioritization and visual representation of server roles. Also, registered new backups API routes in the backend for improved data handling.
This commit is contained in:
@@ -17,6 +17,7 @@ import probesRoutes from "./routes/probes.js"
|
||||
import schedulerRoutes from "./routes/scheduler.js"
|
||||
import sidebarCountsRoutes from "./routes/sidebar-counts.js"
|
||||
import alertsRoutes from "./routes/alerts.js"
|
||||
import backupsRoutes from "./routes/backups.js"
|
||||
import { refreshScheduler, stopScheduler } from "./services/scheduler.js"
|
||||
|
||||
// ── app factory ────────────────────────────────────────────────────────────────
|
||||
@@ -60,6 +61,7 @@ await app.register(probesRoutes, { prefix: "/api" })
|
||||
await app.register(schedulerRoutes, { prefix: "/api" })
|
||||
await app.register(sidebarCountsRoutes, { prefix: "/api" })
|
||||
await app.register(alertsRoutes, { prefix: "/api" })
|
||||
await app.register(backupsRoutes, { prefix: "/api" })
|
||||
|
||||
refreshScheduler()
|
||||
app.addHook("onClose", async () => {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { z } from "zod"
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { listServersRead } from "../modules/servers/service/servers-service.js"
|
||||
import { getServerRowById } from "../modules/servers/repository/servers-repository.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
|
||||
type BackupMeta = {
|
||||
id: string
|
||||
serverId: string
|
||||
serverName: string
|
||||
filename: string
|
||||
sizeBytes: number
|
||||
createdAt: string
|
||||
kind: "manual"
|
||||
notes?: string
|
||||
}
|
||||
|
||||
type BackupJobStatus = "queued" | "running" | "done" | "failed"
|
||||
type BackupJob = {
|
||||
id: string
|
||||
status: BackupJobStatus
|
||||
requestedAt: string
|
||||
startedAt?: string
|
||||
finishedAt?: string
|
||||
total: number
|
||||
completed: number
|
||||
created: BackupMeta[]
|
||||
failures: Array<{ serverId: string; error: string }>
|
||||
}
|
||||
|
||||
const BACKUPS_DIR = path.resolve(process.cwd(), "storage", "backups")
|
||||
const INDEX_PATH = path.join(BACKUPS_DIR, "index.json")
|
||||
const backupJobs = new Map<string, BackupJob>()
|
||||
|
||||
async function ensureStorage() {
|
||||
await mkdir(BACKUPS_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
async function readIndex(): Promise<BackupMeta[]> {
|
||||
await ensureStorage()
|
||||
try {
|
||||
const raw = await readFile(INDEX_PATH, "utf8")
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
if (!Array.isArray(parsed)) return []
|
||||
return parsed as BackupMeta[]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function writeIndex(rows: BackupMeta[]): Promise<void> {
|
||||
await ensureStorage()
|
||||
await writeFile(INDEX_PATH, JSON.stringify(rows, null, 2), "utf8")
|
||||
}
|
||||
|
||||
function fmtTs(d = new Date()): string {
|
||||
const p = (n: number) => String(n).padStart(2, "0")
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`
|
||||
}
|
||||
|
||||
const CreateBackupBodySchema = z.object({
|
||||
serverIds: z.array(z.union([z.string(), z.number()])).min(1),
|
||||
notes: z.string().max(500).optional(),
|
||||
})
|
||||
|
||||
const BackupIdParamSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
})
|
||||
const BackupJobIdParamSchema = z.object({
|
||||
jobId: z.string().min(1),
|
||||
})
|
||||
|
||||
async function runBackupForServer(id: string, notes?: string): Promise<BackupMeta> {
|
||||
const serverIdNum = Number.parseInt(id, 10)
|
||||
if (!Number.isFinite(serverIdNum)) {
|
||||
throw new Error("Невалидный id сервера")
|
||||
}
|
||||
const row = getServerRowById(serverIdNum)
|
||||
if (!row) {
|
||||
throw new Error("Сервер не найден")
|
||||
}
|
||||
const client = MikrotikClient.fromServer(row)
|
||||
const script = await client.exportConfigScript()
|
||||
const ts = fmtTs()
|
||||
const safeServer = row.name.replace(/[^a-zA-Z0-9._-]+/g, "_")
|
||||
const filename = `${safeServer}_${ts}.rsc`
|
||||
const filePath = path.join(BACKUPS_DIR, filename)
|
||||
await writeFile(filePath, script, "utf8")
|
||||
const st = await stat(filePath)
|
||||
return {
|
||||
id: randomUUID(),
|
||||
serverId: String(row.id),
|
||||
serverName: row.name,
|
||||
filename,
|
||||
sizeBytes: st.size,
|
||||
createdAt: new Date().toISOString(),
|
||||
kind: "manual",
|
||||
notes,
|
||||
}
|
||||
}
|
||||
|
||||
async function processBackupJob(job: BackupJob, ids: string[], notes?: string) {
|
||||
job.status = "running"
|
||||
job.startedAt = new Date().toISOString()
|
||||
const indexRows = await readIndex()
|
||||
for (const id of ids) {
|
||||
try {
|
||||
const meta = await runBackupForServer(id, notes)
|
||||
indexRows.unshift(meta)
|
||||
job.created.push(meta)
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
job.failures.push({ serverId: id, error: message })
|
||||
} finally {
|
||||
job.completed += 1
|
||||
}
|
||||
}
|
||||
await writeIndex(indexRows)
|
||||
job.status = "done"
|
||||
job.finishedAt = new Date().toISOString()
|
||||
}
|
||||
|
||||
const backupsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
app.get("/backups", async (_req, reply) => {
|
||||
const rows = await readIndex()
|
||||
rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
return reply.send(rows)
|
||||
})
|
||||
|
||||
app.post("/backups/create", { schema: { body: CreateBackupBodySchema } }, async (req, reply) => {
|
||||
const inputIds = req.body.serverIds.map((x) => String(x))
|
||||
const notes = req.body.notes?.trim() || undefined
|
||||
const existingServers = new Set(listServersRead().map((s) => String(s.id)))
|
||||
const ids = [...new Set(inputIds)].filter((id) => existingServers.has(id))
|
||||
if (ids.length === 0) return reply.status(400).send({ error: "Не выбраны валидные серверы" })
|
||||
|
||||
const jobId = randomUUID()
|
||||
const job: BackupJob = {
|
||||
id: jobId,
|
||||
status: "queued",
|
||||
requestedAt: new Date().toISOString(),
|
||||
total: ids.length,
|
||||
completed: 0,
|
||||
created: [],
|
||||
failures: [],
|
||||
}
|
||||
backupJobs.set(jobId, job)
|
||||
queueMicrotask(() => {
|
||||
void processBackupJob(job, ids, notes).catch((err) => {
|
||||
job.status = "failed"
|
||||
job.finishedAt = new Date().toISOString()
|
||||
job.failures.push({
|
||||
serverId: "job",
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
})
|
||||
})
|
||||
return reply.status(202).send({
|
||||
jobId,
|
||||
status: job.status,
|
||||
total: job.total,
|
||||
completed: job.completed,
|
||||
})
|
||||
})
|
||||
|
||||
app.get("/backups/jobs/:jobId", { schema: { params: BackupJobIdParamSchema } }, async (req, reply) => {
|
||||
const job = backupJobs.get(req.params.jobId)
|
||||
if (!job) return reply.status(404).send({ error: "Job не найден" })
|
||||
return reply.send(job)
|
||||
})
|
||||
|
||||
app.get("/backups/:id/download", { schema: { params: BackupIdParamSchema } }, async (req, reply) => {
|
||||
const rows = await readIndex()
|
||||
const hit = rows.find((r) => r.id === req.params.id)
|
||||
if (!hit) return reply.status(404).send({ error: "Бэкап не найден" })
|
||||
const filePath = path.join(BACKUPS_DIR, hit.filename)
|
||||
const content = await readFile(filePath, "utf8").catch(() => null)
|
||||
if (content == null) return reply.status(404).send({ error: "Файл бэкапа не найден" })
|
||||
reply.header("Content-Type", "text/plain; charset=utf-8")
|
||||
reply.header("Content-Disposition", `attachment; filename="${hit.filename}"`)
|
||||
return reply.send(content)
|
||||
})
|
||||
|
||||
app.delete("/backups/:id", { schema: { params: BackupIdParamSchema } }, async (req, reply) => {
|
||||
const rows = await readIndex()
|
||||
const idx = rows.findIndex((r) => r.id === req.params.id)
|
||||
if (idx < 0) return reply.status(404).send({ error: "Бэкап не найден" })
|
||||
const [hit] = rows.splice(idx, 1)
|
||||
await writeIndex(rows)
|
||||
await rm(path.join(BACKUPS_DIR, hit.filename), { force: true })
|
||||
return reply.status(204).send()
|
||||
})
|
||||
}
|
||||
|
||||
export default backupsRoutes
|
||||
@@ -430,6 +430,118 @@ export class MikrotikClient {
|
||||
}
|
||||
return this.post<Array<Record<string, string>>>("/tool/bandwidth-test", body, 30_000)
|
||||
}
|
||||
|
||||
async exportConfigScript(): Promise<string> {
|
||||
const raw = await this.post<unknown>("/console/export", {}, 30_000)
|
||||
|
||||
const asText = (v: unknown): string | null => {
|
||||
if (typeof v === "string") return v.trim().length > 0 ? v : null
|
||||
if (Array.isArray(v)) {
|
||||
const parts = v
|
||||
.map((item) => asText(item))
|
||||
.filter((s): s is string => typeof s === "string" && s.length > 0)
|
||||
return parts.length > 0 ? parts.join("\n") : null
|
||||
}
|
||||
if (v && typeof v === "object") {
|
||||
const rec = v as Record<string, unknown>
|
||||
const direct =
|
||||
asText(rec.output) ??
|
||||
asText(rec.stdout) ??
|
||||
asText(rec.data) ??
|
||||
asText(rec.ret) ??
|
||||
asText(rec["!re"])
|
||||
if (direct) return direct
|
||||
const serialized = JSON.stringify(rec, null, 2)
|
||||
return serialized.length > 2 ? serialized : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const txt = asText(raw)
|
||||
if (txt && txt.trim().length > 0) return txt
|
||||
|
||||
// Fallback: на части RouterOS /console/export возвращает пустое тело.
|
||||
// Тогда строим .rsc-скрипт из основных read-only разделов REST.
|
||||
return this.buildSyntheticExportScript()
|
||||
}
|
||||
|
||||
private async buildSyntheticExportScript(): Promise<string> {
|
||||
const now = new Date().toISOString()
|
||||
const lines: string[] = [
|
||||
"# synthetic export generated by MikrotikManager",
|
||||
`# generated-at: ${now}`,
|
||||
"",
|
||||
]
|
||||
|
||||
const identity = await this.getIdentity().catch(() => null)
|
||||
if (identity?.name) {
|
||||
lines.push("/system identity")
|
||||
lines.push(`set name="${identity.name.replace(/"/g, "\\\"")}"`)
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
const interfaces = await this.getInterfaces().catch(() => [])
|
||||
if (interfaces.length > 0) {
|
||||
lines.push("/interface")
|
||||
for (const i of interfaces) {
|
||||
if (!i.name) continue
|
||||
const mtu = i["actual-mtu"] ?? i.mtu
|
||||
const parts = [
|
||||
`name="${String(i.name).replace(/"/g, "\\\"")}"`,
|
||||
mtu ? `mtu=${mtu}` : null,
|
||||
i.disabled === "true" ? "disabled=yes" : "disabled=no",
|
||||
].filter((v): v is string => typeof v === "string")
|
||||
lines.push(`:put "interface ${parts.join(" ")}"`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
const addrs = await this.getIpAddresses().catch(() => [])
|
||||
if (addrs.length > 0) {
|
||||
lines.push("/ip address")
|
||||
for (const a of addrs) {
|
||||
if (!a.address || !a.interface) continue
|
||||
const comment = a.comment ? ` comment="${String(a.comment).replace(/"/g, "\\\"")}"` : ""
|
||||
lines.push(`add address=${a.address} interface="${String(a.interface).replace(/"/g, "\\\"")}"${comment}`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
const routes = await this.getIpRoutes().catch(() => [])
|
||||
if (routes.length > 0) {
|
||||
lines.push("/ip route")
|
||||
for (const r of routes) {
|
||||
const dst = r["dst-address"]
|
||||
const gw = r["gateway"]
|
||||
if (!dst || !gw) continue
|
||||
const distance = r.distance ? ` distance=${r.distance}` : ""
|
||||
lines.push(`add dst-address=${dst} gateway=${gw}${distance}`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
const firewall = await this.getFirewallFilters().catch(() => [])
|
||||
if (firewall.length > 0) {
|
||||
lines.push("/ip firewall filter")
|
||||
for (const f of firewall) {
|
||||
if (!f.chain || !f.action) continue
|
||||
const parts = [`chain=${f.chain}`, `action=${f.action}`]
|
||||
if (f.protocol) parts.push(`protocol=${f.protocol}`)
|
||||
if (f["src-address"]) parts.push(`src-address=${f["src-address"]}`)
|
||||
if (f["dst-address"]) parts.push(`dst-address=${f["dst-address"]}`)
|
||||
if (f["dst-port"]) parts.push(`dst-port=${f["dst-port"]}`)
|
||||
if (f["src-port"]) parts.push(`src-port=${f["src-port"]}`)
|
||||
if (f.disabled === "true") parts.push("disabled=yes")
|
||||
lines.push(`add ${parts.join(" ")}`)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
if (lines.length <= 3) {
|
||||
throw new Error("RouterOS вернул пустой export и fallback-данные недоступны")
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Error type ─────────────────────────────────────────────────────────────────
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
[
|
||||
{
|
||||
"id": "7d46ee5b-c829-4d42-af90-847026bafd8a",
|
||||
"serverId": "6",
|
||||
"serverName": "ihor.msk.rt.shx.su",
|
||||
"filename": "ihor.msk.rt.shx.su_2026-05-07_14-19-22.rsc",
|
||||
"sizeBytes": 676415,
|
||||
"createdAt": "2026-05-07T07:19:22.073Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "c34f71e5-6766-456b-9b8a-9b8efaa91edc",
|
||||
"serverId": "5",
|
||||
"serverName": "servhost.nsk.rt.shx.su",
|
||||
"filename": "servhost.nsk.rt.shx.su_2026-05-07_14-19-19.rsc",
|
||||
"sizeBytes": 672268,
|
||||
"createdAt": "2026-05-07T07:19:19.639Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "40e253a1-98a8-40a7-982b-7c5af213f490",
|
||||
"serverId": "4",
|
||||
"serverName": "veesp.swe.rt.shx.su",
|
||||
"filename": "veesp.swe.rt.shx.su_2026-05-07_14-19-17.rsc",
|
||||
"sizeBytes": 2131,
|
||||
"createdAt": "2026-05-07T07:19:17.801Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "9199d0a8-9bc0-4d20-8498-b37f1f15262b",
|
||||
"serverId": "3",
|
||||
"serverName": "vpsville.msk.rt.shx.su",
|
||||
"filename": "vpsville.msk.rt.shx.su_2026-05-07_14-19-17.rsc",
|
||||
"sizeBytes": 675732,
|
||||
"createdAt": "2026-05-07T07:19:17.272Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "4de5263f-edb4-4ca9-ae90-2247defdc05e",
|
||||
"serverId": "2",
|
||||
"serverName": "Gateway",
|
||||
"filename": "Gateway_2026-05-07_14-19-14.rsc",
|
||||
"sizeBytes": 913056,
|
||||
"createdAt": "2026-05-07T07:19:14.772Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "de49d319-a24f-475c-bb6e-8075eff86380",
|
||||
"serverId": "2",
|
||||
"serverName": "Gateway",
|
||||
"filename": "Gateway_2026-05-07_14-16-45.rsc",
|
||||
"sizeBytes": 911521,
|
||||
"createdAt": "2026-05-07T07:16:45.543Z",
|
||||
"kind": "manual",
|
||||
"notes": "async"
|
||||
},
|
||||
{
|
||||
"id": "ec5b9e31-42cb-40b9-aba7-3501d5145713",
|
||||
"serverId": "6",
|
||||
"serverName": "ihor.msk.rt.shx.su",
|
||||
"filename": "ihor.msk.rt.shx.su_2026-05-07_14-13-58.rsc",
|
||||
"sizeBytes": 676415,
|
||||
"createdAt": "2026-05-07T07:13:58.749Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "7909c7de-a548-44d4-bdf7-7c221bfedd36",
|
||||
"serverId": "5",
|
||||
"serverName": "servhost.nsk.rt.shx.su",
|
||||
"filename": "servhost.nsk.rt.shx.su_2026-05-07_14-13-56.rsc",
|
||||
"sizeBytes": 672268,
|
||||
"createdAt": "2026-05-07T07:13:56.245Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "63339ebb-eb94-455e-a61b-368523fed7e1",
|
||||
"serverId": "4",
|
||||
"serverName": "veesp.swe.rt.shx.su",
|
||||
"filename": "veesp.swe.rt.shx.su_2026-05-07_14-13-54.rsc",
|
||||
"sizeBytes": 2131,
|
||||
"createdAt": "2026-05-07T07:13:54.493Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "daccab1d-f60a-4570-9d11-c7b06491f6f7",
|
||||
"serverId": "3",
|
||||
"serverName": "vpsville.msk.rt.shx.su",
|
||||
"filename": "vpsville.msk.rt.shx.su_2026-05-07_14-13-53.rsc",
|
||||
"sizeBytes": 675732,
|
||||
"createdAt": "2026-05-07T07:13:53.791Z",
|
||||
"kind": "manual"
|
||||
},
|
||||
{
|
||||
"id": "d976cae6-aae8-4f55-9452-71d5480ac8e8",
|
||||
"serverId": "2",
|
||||
"serverName": "Gateway",
|
||||
"filename": "Gateway_2026-05-07_14-13-48.rsc",
|
||||
"sizeBytes": 913056,
|
||||
"createdAt": "2026-05-07T07:13:48.326Z",
|
||||
"kind": "manual"
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
# synthetic export generated by MikrotikManager
|
||||
# generated-at: 2026-05-07T07:13:21.880Z
|
||||
|
||||
/system identity
|
||||
set name="veesp.swe.rt.shx.su"
|
||||
|
||||
/interface
|
||||
:put "interface name="ether1" mtu=1500 disabled=no"
|
||||
:put "interface name="MSK-DC" mtu=1350 disabled=no"
|
||||
:put "interface name="MSK-IHOR" mtu=1400 disabled=no"
|
||||
:put "interface name="MSK-VPSVILLE" mtu=1400 disabled=no"
|
||||
:put "interface name="NSK-SERVHOST" mtu=1400 disabled=no"
|
||||
:put "interface name="br1" mtu=1500 disabled=no"
|
||||
:put "interface name="gre-tunnel1" mtu=1450 disabled=no"
|
||||
:put "interface name="lo" mtu=65536 disabled=no"
|
||||
:put "interface name="wg-tunnel" mtu=1420 disabled=no"
|
||||
:put "interface name="wg1" mtu=1280 disabled=no"
|
||||
|
||||
/ip address
|
||||
add address=62.182.194.146/24 interface="ether1"
|
||||
add address=10.200.100.26/30 interface="MSK-IHOR"
|
||||
add address=10.200.200.2/30 interface="*5"
|
||||
add address=10.200.40.2/30 interface="MSK-VPSVILLE"
|
||||
add address=10.205.1.2/30 interface="wg1"
|
||||
add address=10.200.100.2/30 interface="*11" comment="Interface: MSK-SELECTEL (GRE) to selectel.msk.rt.shx.su"
|
||||
add address=10.200.100.38/30 interface="MSK-DC" comment="Interface: MSK-DC (GRE) to dc.msk.rt.shx.su"
|
||||
add address=10.200.100.14/30 interface="MSK-VPSVILLE" comment="Interface: MSK-VPSVILLE (GRE) to vpsville.msk.rt.shx.su"
|
||||
add address=10.101.1.2/30 interface="gre-tunnel1"
|
||||
add address=10.200.100.54/30 interface="NSK-SERVHOST" comment="Interface: NSK-SERVHOST (GRE) to servhost.nsk.rt.shx.su"
|
||||
|
||||
/ip route
|
||||
add dst-address=0.0.0.0/0 gateway=62.182.194.1 distance=1
|
||||
add dst-address=10.101.1.0/30 gateway=gre-tunnel1 distance=0
|
||||
add dst-address=10.200.100.12/30 gateway=MSK-VPSVILLE distance=0
|
||||
add dst-address=10.200.100.24/30 gateway=MSK-IHOR distance=0
|
||||
add dst-address=10.200.100.36/30 gateway=MSK-DC distance=0
|
||||
add dst-address=10.200.100.52/30 gateway=NSK-SERVHOST distance=0
|
||||
add dst-address=10.205.1.0/30 gateway=wg1 distance=0
|
||||
add dst-address=62.182.194.0/24 gateway=br1 distance=0
|
||||
add dst-address=192.168.0.0/16 gateway=10.200.100.13%MSK-VPSVILLE distance=1
|
||||
|
||||
/ip firewall filter
|
||||
add chain=input action=drop protocol=udp dst-port=53
|
||||
add chain=input action=accept protocol=udp dst-port=13231
|
||||
@@ -0,0 +1,44 @@
|
||||
# synthetic export generated by MikrotikManager
|
||||
# generated-at: 2026-05-07T07:13:54.118Z
|
||||
|
||||
/system identity
|
||||
set name="veesp.swe.rt.shx.su"
|
||||
|
||||
/interface
|
||||
:put "interface name="ether1" mtu=1500 disabled=no"
|
||||
:put "interface name="MSK-DC" mtu=1350 disabled=no"
|
||||
:put "interface name="MSK-IHOR" mtu=1400 disabled=no"
|
||||
:put "interface name="MSK-VPSVILLE" mtu=1400 disabled=no"
|
||||
:put "interface name="NSK-SERVHOST" mtu=1400 disabled=no"
|
||||
:put "interface name="br1" mtu=1500 disabled=no"
|
||||
:put "interface name="gre-tunnel1" mtu=1450 disabled=no"
|
||||
:put "interface name="lo" mtu=65536 disabled=no"
|
||||
:put "interface name="wg-tunnel" mtu=1420 disabled=no"
|
||||
:put "interface name="wg1" mtu=1280 disabled=no"
|
||||
|
||||
/ip address
|
||||
add address=62.182.194.146/24 interface="ether1"
|
||||
add address=10.200.100.26/30 interface="MSK-IHOR"
|
||||
add address=10.200.200.2/30 interface="*5"
|
||||
add address=10.200.40.2/30 interface="MSK-VPSVILLE"
|
||||
add address=10.205.1.2/30 interface="wg1"
|
||||
add address=10.200.100.2/30 interface="*11" comment="Interface: MSK-SELECTEL (GRE) to selectel.msk.rt.shx.su"
|
||||
add address=10.200.100.38/30 interface="MSK-DC" comment="Interface: MSK-DC (GRE) to dc.msk.rt.shx.su"
|
||||
add address=10.200.100.14/30 interface="MSK-VPSVILLE" comment="Interface: MSK-VPSVILLE (GRE) to vpsville.msk.rt.shx.su"
|
||||
add address=10.101.1.2/30 interface="gre-tunnel1"
|
||||
add address=10.200.100.54/30 interface="NSK-SERVHOST" comment="Interface: NSK-SERVHOST (GRE) to servhost.nsk.rt.shx.su"
|
||||
|
||||
/ip route
|
||||
add dst-address=0.0.0.0/0 gateway=62.182.194.1 distance=1
|
||||
add dst-address=10.101.1.0/30 gateway=gre-tunnel1 distance=0
|
||||
add dst-address=10.200.100.12/30 gateway=MSK-VPSVILLE distance=0
|
||||
add dst-address=10.200.100.24/30 gateway=MSK-IHOR distance=0
|
||||
add dst-address=10.200.100.36/30 gateway=MSK-DC distance=0
|
||||
add dst-address=10.200.100.52/30 gateway=NSK-SERVHOST distance=0
|
||||
add dst-address=10.205.1.0/30 gateway=wg1 distance=0
|
||||
add dst-address=62.182.194.0/24 gateway=br1 distance=0
|
||||
add dst-address=192.168.0.0/16 gateway=10.200.100.13%MSK-VPSVILLE distance=1
|
||||
|
||||
/ip firewall filter
|
||||
add chain=input action=drop protocol=udp dst-port=53
|
||||
add chain=input action=accept protocol=udp dst-port=13231
|
||||
@@ -0,0 +1,44 @@
|
||||
# synthetic export generated by MikrotikManager
|
||||
# generated-at: 2026-05-07T07:19:17.424Z
|
||||
|
||||
/system identity
|
||||
set name="veesp.swe.rt.shx.su"
|
||||
|
||||
/interface
|
||||
:put "interface name="ether1" mtu=1500 disabled=no"
|
||||
:put "interface name="MSK-DC" mtu=1350 disabled=no"
|
||||
:put "interface name="MSK-IHOR" mtu=1400 disabled=no"
|
||||
:put "interface name="MSK-VPSVILLE" mtu=1400 disabled=no"
|
||||
:put "interface name="NSK-SERVHOST" mtu=1400 disabled=no"
|
||||
:put "interface name="br1" mtu=1500 disabled=no"
|
||||
:put "interface name="gre-tunnel1" mtu=1450 disabled=no"
|
||||
:put "interface name="lo" mtu=65536 disabled=no"
|
||||
:put "interface name="wg-tunnel" mtu=1420 disabled=no"
|
||||
:put "interface name="wg1" mtu=1280 disabled=no"
|
||||
|
||||
/ip address
|
||||
add address=62.182.194.146/24 interface="ether1"
|
||||
add address=10.200.100.26/30 interface="MSK-IHOR"
|
||||
add address=10.200.200.2/30 interface="*5"
|
||||
add address=10.200.40.2/30 interface="MSK-VPSVILLE"
|
||||
add address=10.205.1.2/30 interface="wg1"
|
||||
add address=10.200.100.2/30 interface="*11" comment="Interface: MSK-SELECTEL (GRE) to selectel.msk.rt.shx.su"
|
||||
add address=10.200.100.38/30 interface="MSK-DC" comment="Interface: MSK-DC (GRE) to dc.msk.rt.shx.su"
|
||||
add address=10.200.100.14/30 interface="MSK-VPSVILLE" comment="Interface: MSK-VPSVILLE (GRE) to vpsville.msk.rt.shx.su"
|
||||
add address=10.101.1.2/30 interface="gre-tunnel1"
|
||||
add address=10.200.100.54/30 interface="NSK-SERVHOST" comment="Interface: NSK-SERVHOST (GRE) to servhost.nsk.rt.shx.su"
|
||||
|
||||
/ip route
|
||||
add dst-address=0.0.0.0/0 gateway=62.182.194.1 distance=1
|
||||
add dst-address=10.101.1.0/30 gateway=gre-tunnel1 distance=0
|
||||
add dst-address=10.200.100.12/30 gateway=MSK-VPSVILLE distance=0
|
||||
add dst-address=10.200.100.24/30 gateway=MSK-IHOR distance=0
|
||||
add dst-address=10.200.100.36/30 gateway=MSK-DC distance=0
|
||||
add dst-address=10.200.100.52/30 gateway=NSK-SERVHOST distance=0
|
||||
add dst-address=10.205.1.0/30 gateway=wg1 distance=0
|
||||
add dst-address=62.182.194.0/24 gateway=br1 distance=0
|
||||
add dst-address=192.168.0.0/16 gateway=10.200.100.13%MSK-VPSVILLE distance=1
|
||||
|
||||
/ip firewall filter
|
||||
add chain=input action=drop protocol=udp dst-port=53
|
||||
add chain=input action=accept protocol=udp dst-port=13231
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user