fix(traffic): сделать JH сервером WG и патчить traffic-flow по id
Docker images / prepare-release (push) Successful in 11s
Docker images / backend-image (push) Successful in 1m43s
Docker images / frontend-image (push) Successful in 2m52s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 45s
Docker images / publish-release (push) Successful in 9s
Docker images / prepare-release (push) Successful in 11s
Docker images / backend-image (push) Successful in 1m43s
Docker images / frontend-image (push) Successful in 2m52s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 45s
Docker images / publish-release (push) Successful in 9s
RouterOS не принимает PATCH без .id у singleton. Пир на jump-host без endpoint: клиент — хост MM с Endpoint JH:13232. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -16,46 +16,44 @@ export function buildHostWgQuickConf(): string {
|
|||||||
publicKey: p.publicKey,
|
publicKey: p.publicKey,
|
||||||
allowedIps: p.allowedIps,
|
allowedIps: p.allowedIps,
|
||||||
comment: p.name,
|
comment: p.name,
|
||||||
|
endpoint: p.endpoint,
|
||||||
|
persistentKeepalive: p.endpoint ? 25 : undefined,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostComposeSnippet(): string {
|
export function buildHostComposeSnippet(): string {
|
||||||
const row = getTrafficFlowSettingsRow()
|
const row = getTrafficFlowSettingsRow()
|
||||||
return `# IPFIX listener: публиковать UDP только на WG-адресе хоста, не на 0.0.0.0
|
return `# Вставить в /opt/cdn-mm/docker-compose.yml под services.backend
|
||||||
# Поднимите wg-quick@wg-flow, затем раскомментируйте ports у backend.
|
# На хосте сначала: wg-quick up wg-flow (адрес ${row.collectorIp})
|
||||||
|
# затем: docker compose up -d backend
|
||||||
|
# Traefik не трогать. UDP ${row.flowListenPort} не публиковать на 0.0.0.0.
|
||||||
|
|
||||||
services:
|
|
||||||
backend:
|
|
||||||
ports:
|
|
||||||
- "${row.collectorIp}:${row.flowListenPort}:${row.flowListenPort}/udp"
|
|
||||||
environment:
|
environment:
|
||||||
FLOW_LISTEN_HOST: "0.0.0.0"
|
FLOW_LISTEN_HOST: "0.0.0.0"
|
||||||
|
ports:
|
||||||
|
- "${row.collectorIp}:${row.flowListenPort}:${row.flowListenPort}/udp"
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostNftSnippet(): string {
|
export function buildHostNftSnippet(): string {
|
||||||
const row = getTrafficFlowSettingsRow()
|
const row = getTrafficFlowSettingsRow()
|
||||||
return `# Firewall хоста Docker MM (nftables). UDP ${row.flowListenPort} наружу НЕ открывать.
|
return `# Firewall хоста Docker MM. WG — клиент к JH:13232 (исходящий).
|
||||||
|
# UDP ${row.flowListenPort} наружу НЕ открывать.
|
||||||
table inet filter {
|
table inet filter {
|
||||||
chain input {
|
chain input {
|
||||||
type filter hook input priority 0;
|
type filter hook input priority 0;
|
||||||
iifname "wg-flow" udp dport ${row.flowListenPort} accept
|
iifname "wg-flow" udp dport ${row.flowListenPort} accept
|
||||||
udp dport ${row.wgListenPort} accept comment "WireGuard handshake"
|
|
||||||
udp dport ${row.flowListenPort} drop
|
udp dport ${row.flowListenPort} drop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ufw (если используете):
|
|
||||||
# ufw allow ${row.wgListenPort}/udp comment 'mm-wg-flow'
|
|
||||||
# ufw deny ${row.flowListenPort}/udp comment 'ipfix-not-public'
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildHostUfwSnippet(): string {
|
export function buildHostUfwSnippet(): string {
|
||||||
const row = getTrafficFlowSettingsRow()
|
const row = getTrafficFlowSettingsRow()
|
||||||
return [
|
return [
|
||||||
`ufw allow ${row.wgListenPort}/udp comment 'mm-wg-flow'`,
|
`# WG клиент: входящий listen не нужен`,
|
||||||
`ufw deny ${row.flowListenPort}/udp comment 'ipfix-not-public'`,
|
`ufw deny ${row.flowListenPort}/udp comment 'ipfix-not-public'`,
|
||||||
].join("\n")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { eq } from "drizzle-orm"
|
|||||||
import { db } from "../db/index.js"
|
import { db } from "../db/index.js"
|
||||||
import { servers } from "../db/schema.js"
|
import { servers } from "../db/schema.js"
|
||||||
import type { TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
|
import type { TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
|
||||||
import { MikrotikClient, MikrotikError } from "./mikrotik.js"
|
import { encodeRosId, MikrotikClient, MikrotikError } from "./mikrotik.js"
|
||||||
import { getEnabledServerById, listWireGuardInterfaces } from "./wireguard-live.js"
|
import { getEnabledServerById, listWireGuardInterfaces } from "./wireguard-live.js"
|
||||||
import {
|
import {
|
||||||
asRosArray,
|
asRosArray,
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ensureHostKeys,
|
ensureHostKeys,
|
||||||
getTrafficFlowSettingsRow,
|
getTrafficFlowSettingsRow,
|
||||||
updateTrafficFlowSettings,
|
|
||||||
upsertHostPeer,
|
upsertHostPeer,
|
||||||
} from "./traffic-flow-settings.js"
|
} from "./traffic-flow-settings.js"
|
||||||
import { listTrafficFlowHostFiles } from "./traffic-flow-host-files.js"
|
import { listTrafficFlowHostFiles } from "./traffic-flow-host-files.js"
|
||||||
@@ -42,11 +41,13 @@ export function allocateOverlayAddress(prefix: string, collectorIp: string, serv
|
|||||||
throw new Error("Нет свободных адресов в префиксе wg-flow")
|
throw new Error("Нет свободных адресов в префиксе wg-flow")
|
||||||
}
|
}
|
||||||
|
|
||||||
function linuxPeerBlock(publicKey: string, address: string, comment: string): string {
|
function linuxPeerBlock(publicKey: string, address: string, comment: string, endpoint: string): string {
|
||||||
return [
|
return [
|
||||||
`[Peer]`,
|
`[Peer]`,
|
||||||
`PublicKey = ${publicKey}`,
|
`PublicKey = ${publicKey}`,
|
||||||
`AllowedIPs = ${address}/32`,
|
`AllowedIPs = ${address}/32`,
|
||||||
|
`Endpoint = ${endpoint}:${JH_LISTEN_PORT}`,
|
||||||
|
`PersistentKeepalive = 25`,
|
||||||
comment ? `# ${comment}` : "",
|
comment ? `# ${comment}` : "",
|
||||||
].filter(Boolean).join("\n")
|
].filter(Boolean).join("\n")
|
||||||
}
|
}
|
||||||
@@ -91,51 +92,34 @@ async function ensureWgInputAccept(client: MikrotikClient, listenPort: number):
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listFlowInterfaces(client: MikrotikClient): Promise<string> {
|
|
||||||
const ifaces = asRosArray<{ name?: string; type?: string; disabled?: string }>(await client.get("/interface"))
|
|
||||||
const names = ifaces
|
|
||||||
.filter((i) => {
|
|
||||||
if ((i.disabled ?? "false") === "true") return false
|
|
||||||
const name = i.name ?? ""
|
|
||||||
if (!name || name === IFACE_NAME || /^lo/i.test(name)) return false
|
|
||||||
const type = (i.type ?? "").toLowerCase()
|
|
||||||
return type.includes("ether") || type.includes("gre") || type === "vlan"
|
|
||||||
})
|
|
||||||
.map((i) => i.name ?? "")
|
|
||||||
.filter(Boolean)
|
|
||||||
.slice(0, 8)
|
|
||||||
return names.join(",") || "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureTrafficFlow(client: MikrotikClient, collectorIp: string, port: number): Promise<void> {
|
async function ensureTrafficFlow(client: MikrotikClient, collectorIp: string, port: number): Promise<void> {
|
||||||
const interfaces = await listFlowInterfaces(client)
|
const body = toRosBody({
|
||||||
try {
|
enabled: "yes",
|
||||||
await client.patch("/ip/traffic-flow", toRosBody({
|
interfaces: "all",
|
||||||
enabled: "yes",
|
"active-flow-timeout": "1m",
|
||||||
interfaces,
|
"inactive-flow-timeout": "15s",
|
||||||
"active-flow-timeout": "1m",
|
})
|
||||||
"inactive-flow-timeout": "15s",
|
const rows = asRosArray<Record<string, unknown>>(await client.get("/ip/traffic-flow"))
|
||||||
}))
|
const id = rows[0] ? rosRowId(rows[0]) : ""
|
||||||
} catch {
|
if (id) {
|
||||||
await client.put("/ip/traffic-flow", toRosBody({
|
await patchRosPath(client, `/ip/traffic-flow/${encodeRosId(id)}`, body)
|
||||||
enabled: "yes",
|
} else {
|
||||||
interfaces,
|
await client.post("/ip/traffic-flow/set", body)
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const targets = asRosArray<Record<string, unknown>>(await client.get("/ip/traffic-flow/target"))
|
const targets = asRosArray<Record<string, unknown>>(await client.get("/ip/traffic-flow/target"))
|
||||||
const existing = targets.find((t) => String(t["dst-address"] ?? "") === collectorIp)
|
const existing = targets.find((t) => String(t["dst-address"] ?? "") === collectorIp)
|
||||||
const body = toRosBody({
|
const targetBody = toRosBody({
|
||||||
"dst-address": collectorIp,
|
"dst-address": collectorIp,
|
||||||
port: String(port),
|
port: String(port),
|
||||||
version: "ipfix",
|
version: "ipfix",
|
||||||
})
|
})
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const id = rosRowId(existing)
|
const targetId = rosRowId(existing)
|
||||||
if (id) await patchRosPath(client, `/ip/traffic-flow/target/${encodeURIComponent(id)}`, body)
|
if (targetId) await patchRosPath(client, `/ip/traffic-flow/target/${encodeRosId(targetId)}`, targetBody)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await client.put("/ip/traffic-flow/target", body)
|
await client.put("/ip/traffic-flow/target", targetBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usablePublicHost(raw: string | undefined): string {
|
export function usablePublicHost(raw: string | undefined): string {
|
||||||
@@ -155,30 +139,22 @@ export async function applyFlowOverlay(
|
|||||||
): Promise<TrafficFlowOverlayResult> {
|
): Promise<TrafficFlowOverlayResult> {
|
||||||
const steps: string[] = []
|
const steps: string[] = []
|
||||||
const keys = ensureHostKeys()
|
const keys = ensureHostKeys()
|
||||||
let settings = getTrafficFlowSettingsRow()
|
const settings = getTrafficFlowSettingsRow()
|
||||||
const hostPublicKey = settings.hostPublicKey || keys.publicKey
|
const hostPublicKey = settings.hostPublicKey || keys.publicKey
|
||||||
if (!hostPublicKey) {
|
if (!hostPublicKey) {
|
||||||
throw Object.assign(new Error("Не удалось создать ключи хоста MM"), { statusCode: 500 })
|
throw Object.assign(new Error("Не удалось создать ключи хоста MM"), { statusCode: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpointHost = (
|
|
||||||
opts?.publicEndpoint?.trim()
|
|
||||||
|| settings.publicEndpoint.trim()
|
|
||||||
|| usablePublicHost(opts?.requestHost)
|
|
||||||
).trim()
|
|
||||||
if (!endpointHost) {
|
|
||||||
throw Object.assign(new Error("Укажите публичный endpoint хоста MM (IP или DNS)"), { statusCode: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = getEnabledServerById(String(serverIdRaw))
|
const server = getEnabledServerById(String(serverIdRaw))
|
||||||
if (!server || !server.enabled) {
|
if (!server || !server.enabled) {
|
||||||
throw Object.assign(new Error("Сервер не найден или выключен"), { statusCode: 404 })
|
throw Object.assign(new Error("Сервер не найден или выключен"), { statusCode: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpointHost !== settings.publicEndpoint.trim()) {
|
const endpointHost = (opts?.publicEndpoint?.trim() || server.host.trim()).trim()
|
||||||
updateTrafficFlowSettings({ publicEndpoint: endpointHost })
|
if (!endpointHost) {
|
||||||
settings = getTrafficFlowSettingsRow()
|
throw Object.assign(new Error("Укажите публичный IP или DNS jump-host"), { statusCode: 400 })
|
||||||
}
|
}
|
||||||
|
const peerEndpoint = `${endpointHost}:${JH_LISTEN_PORT}`
|
||||||
|
|
||||||
const taken = new Set(
|
const taken = new Set(
|
||||||
db.select({ ip: servers.mgmtTunnelIp }).from(servers).all()
|
db.select({ ip: servers.mgmtTunnelIp }).from(servers).all()
|
||||||
@@ -218,19 +194,23 @@ export async function applyFlowOverlay(
|
|||||||
interface: IFACE_NAME,
|
interface: IFACE_NAME,
|
||||||
"public-key": hostPublicKey,
|
"public-key": hostPublicKey,
|
||||||
"allowed-address": `${settings.collectorIp}/32`,
|
"allowed-address": `${settings.collectorIp}/32`,
|
||||||
"endpoint-address": endpointHost,
|
|
||||||
"endpoint-port": String(settings.wgListenPort),
|
|
||||||
"persistent-keepalive": "25",
|
|
||||||
comment: "MM traffic-flow collector",
|
comment: "MM traffic-flow collector",
|
||||||
name: "mm-collector",
|
name: "mm-collector",
|
||||||
}
|
}
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
await putWireguardPeer(client, peerBody)
|
await putWireguardPeer(client, peerBody)
|
||||||
steps.push("Добавлен пир на pubkey хоста MM")
|
steps.push("Добавлен пир на pubkey хоста MM (сервер, без endpoint)")
|
||||||
} else {
|
} else {
|
||||||
const id = rosRowId(peer)
|
const id = rosRowId(peer)
|
||||||
if (id) await patchRosPath(client, `/interface/wireguard/peers/${encodeURIComponent(id)}`, peerBody)
|
const hadEndpoint = Boolean(String(peer["endpoint-address"] ?? "").trim())
|
||||||
steps.push("Пир хоста MM обновлён")
|
if (hadEndpoint && id) {
|
||||||
|
await client.delete(`/interface/wireguard/peers/${encodeURIComponent(id)}`)
|
||||||
|
await putWireguardPeer(client, peerBody)
|
||||||
|
steps.push("Пир пересоздан как сервер (endpoint снят)")
|
||||||
|
} else if (id) {
|
||||||
|
await patchRosPath(client, `/interface/wireguard/peers/${encodeURIComponent(id)}`, peerBody)
|
||||||
|
steps.push("Пир хоста MM обновлён")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeDst = `${settings.collectorIp}/32`
|
const routeDst = `${settings.collectorIp}/32`
|
||||||
@@ -273,6 +253,7 @@ export async function applyFlowOverlay(
|
|||||||
publicKey,
|
publicKey,
|
||||||
allowedIps: [`${address}/32`],
|
allowedIps: [`${address}/32`],
|
||||||
address,
|
address,
|
||||||
|
endpoint: peerEndpoint,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -281,7 +262,7 @@ export async function applyFlowOverlay(
|
|||||||
interfaceName: IFACE_NAME,
|
interfaceName: IFACE_NAME,
|
||||||
address,
|
address,
|
||||||
publicKey,
|
publicKey,
|
||||||
linuxPeerBlock: linuxPeerBlock(publicKey, address, server.name || server.host),
|
linuxPeerBlock: linuxPeerBlock(publicKey, address, server.name || server.host, endpointHost),
|
||||||
trafficFlow: true,
|
trafficFlow: true,
|
||||||
steps,
|
steps,
|
||||||
hostFiles: listTrafficFlowHostFiles(),
|
hostFiles: listTrafficFlowHostFiles(),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Sheet, SheetContent, SheetHeader, SheetTitle,
|
Sheet, SheetContent, SheetHeader, SheetTitle,
|
||||||
SheetDescription, SheetFooter, SheetClose,
|
SheetDescription, SheetFooter, SheetClose,
|
||||||
} from "@/components/ui/sheet"
|
} from "@/components/ui/sheet"
|
||||||
import { applyTrafficFlowOverlay, getTrafficFlowSettings } from "@/shared/api/traffic-flow"
|
import { applyTrafficFlowOverlay } from "@/shared/api/traffic-flow"
|
||||||
import type { ServerRead } from "@mmapp/contracts/servers"
|
import type { ServerRead } from "@mmapp/contracts/servers"
|
||||||
import type { TrafficFlowHostFile, TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
|
import type { TrafficFlowHostFile, TrafficFlowOverlayResult } from "@mmapp/contracts/traffic-flow"
|
||||||
import {
|
import {
|
||||||
@@ -52,11 +52,17 @@ function FlowOverlaySheet({
|
|||||||
setResult(null)
|
setResult(null)
|
||||||
setCopied(false)
|
setCopied(false)
|
||||||
setTab("wg-quick")
|
setTab("wg-quick")
|
||||||
setServerId(jumpHosts[0] ? String(jumpHosts[0].id) : "")
|
const first = jumpHosts[0]
|
||||||
void getTrafficFlowSettings(backendUrl)
|
const nextId = first ? String(first.id) : ""
|
||||||
.then((s) => setEndpoint(s.publicEndpoint))
|
setServerId(nextId)
|
||||||
.catch(() => setEndpoint(""))
|
setEndpoint(first?.host ?? "")
|
||||||
}, [open, jumpHosts, backendUrl])
|
}, [open, jumpHosts])
|
||||||
|
|
||||||
|
function handleServerChange(id: string) {
|
||||||
|
setServerId(id)
|
||||||
|
const selected = jumpHosts.find((s) => String(s.id) === id)
|
||||||
|
if (selected) setEndpoint(selected.host)
|
||||||
|
}
|
||||||
|
|
||||||
const formats = useMemo((): TrafficFlowHostFile[] => {
|
const formats = useMemo((): TrafficFlowHostFile[] => {
|
||||||
if (!result) return []
|
if (!result) return []
|
||||||
@@ -106,7 +112,7 @@ function FlowOverlaySheet({
|
|||||||
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
<SheetHeader className="px-6 pt-6 pb-4 border-b shrink-0">
|
||||||
<SheetTitle>Подключить jump-host</SheetTitle>
|
<SheetTitle>Подключить jump-host</SheetTitle>
|
||||||
<SheetDescription>
|
<SheetDescription>
|
||||||
Настроит wg-flow и Traffic Flow на MikroTik и сразу выдаст файлы для Linux-хоста Docker MM (wg-quick / compose / firewall).
|
Создаст wg-flow на выбранном MikroTik (сервер, listen 13232) и сразу выдаст wg-quick / compose для Linux-хоста Docker MM (клиент).
|
||||||
</SheetDescription>
|
</SheetDescription>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
<div className="flex-1 min-h-0 overflow-y-auto px-6 py-5 flex flex-col gap-5">
|
||||||
@@ -114,7 +120,7 @@ function FlowOverlaySheet({
|
|||||||
<select
|
<select
|
||||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none"
|
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none"
|
||||||
value={serverId}
|
value={serverId}
|
||||||
onChange={(e) => setServerId(e.target.value)}
|
onChange={(e) => handleServerChange(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Выберите сервер…</option>
|
<option value="">Выберите сервер…</option>
|
||||||
{jumpHosts.map((s) => (
|
{jumpHosts.map((s) => (
|
||||||
@@ -125,15 +131,15 @@ function FlowOverlaySheet({
|
|||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
label="Публичный IP или DNS хоста Docker MM"
|
label="Публичный IP или DNS jump-host"
|
||||||
required
|
required
|
||||||
hint="Откуда JH стучится на WG listen (51821). Хост с wg-quick, не контейнер backend."
|
hint="Куда хост MM (wg-quick) стучится по UDP 13232. Не контейнер backend."
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
value={endpoint}
|
value={endpoint}
|
||||||
onChange={(e) => setEndpoint(e.target.value)}
|
onChange={(e) => setEndpoint(e.target.value)}
|
||||||
placeholder="203.0.113.10"
|
placeholder="jh.example.com"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
@@ -143,7 +149,7 @@ function FlowOverlaySheet({
|
|||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
<AlertTitle>Ключи и UDP 4739</AlertTitle>
|
<AlertTitle>Ключи и UDP 4739</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Приватный ключ хоста в SQLite панели — не кладите в git. UDP 4739 публикуйте только на WG-IP, не на 0.0.0.0.
|
Приватный ключ хоста в SQLite панели — не кладите в git. UDP 4739 публикуйте только на WG-IP хоста, не на 0.0.0.0 контейнера.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<ul className="text-xs text-muted-foreground flex flex-col gap-1">
|
<ul className="text-xs text-muted-foreground flex flex-col gap-1">
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ services:
|
|||||||
AUTH_JWT_SECRET: ${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET in .env}
|
AUTH_JWT_SECRET: ${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET in .env}
|
||||||
AUTH_ISSUER: ${AUTH_ISSUER:-https://auth.shnt.top}
|
AUTH_ISSUER: ${AUTH_ISSUER:-https://auth.shnt.top}
|
||||||
AUTH_PORTAL_URL: ${AUTH_PORTAL_URL:-https://auth.shnt.top}
|
AUTH_PORTAL_URL: ${AUTH_PORTAL_URL:-https://auth.shnt.top}
|
||||||
|
# IPFIX: внутри контейнера слушать все iface; на хосте bind только WG-IP после wg-quick@wg-flow
|
||||||
|
FLOW_LISTEN_HOST: "0.0.0.0"
|
||||||
|
# ports:
|
||||||
|
# - "10.255.254.1:4739:4739/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/mm:/app/data
|
- ./data/mm:/app/data
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const flowHostPeerSchema = z.object({
|
|||||||
publicKey: z.string().min(1),
|
publicKey: z.string().min(1),
|
||||||
allowedIps: z.array(z.string().min(1)).min(1),
|
allowedIps: z.array(z.string().min(1)).min(1),
|
||||||
address: z.string().min(1),
|
address: z.string().min(1),
|
||||||
|
endpoint: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const trafficFlowSettingsDtoSchema = z.object({
|
export const trafficFlowSettingsDtoSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user