diff --git a/app/(main)/wireguard/page.tsx b/app/(main)/wireguard/page.tsx index b9145625..513a36dd 100644 --- a/app/(main)/wireguard/page.tsx +++ b/app/(main)/wireguard/page.tsx @@ -967,6 +967,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf server.id === exportIface?.serverId)?.host} initialTab={exportInitialTab} peerId={exportPeerId} onClose={() => { diff --git a/backend/src/routes/wireguard.ts b/backend/src/routes/wireguard.ts index 0ad0516d..ca869436 100644 --- a/backend/src/routes/wireguard.ts +++ b/backend/src/routes/wireguard.ts @@ -6,6 +6,7 @@ import { wgImportRequestSchema, wgPatchInterfaceSchema, wgPatchPeerSchema, + resolveWgClientEndpoint, type WgCreatePeerRequest, type WgIfaceDto, } from "@mmapp/contracts/wireguard" @@ -451,23 +452,13 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => { if (!iface.publicKey) { return reply.status(400).send({ error: "У интерфейса нет public-key" }) } - const endpoint = - peer.clientEndpoint || - (peer.endpoint - ? peer.endpoint - : undefined) + const endpoint = resolveWgClientEndpoint(peer.clientEndpoint, server.host, iface.listenPort) const content = generatePeerClientConf({ peerAddress: peer.clientAddress, peerDns: peer.clientDns, serverPublicKey: iface.publicKey, - allowedIps: peer.allowedIps.length ? peer.allowedIps : ["0.0.0.0/0"], - endpoint: - endpoint || - (peer.clientEndpoint - ? peer.clientEndpoint.includes(":") - ? peer.clientEndpoint - : `${peer.clientEndpoint}:${iface.listenPort}` - : undefined), + // Server allowed-address describes the remote peer, not the client's routes. + endpoint, persistentKeepalive: peer.persistentKeepalive ?? 25, }) return reply.send({ diff --git a/backend/src/services/wireguard-config.ts b/backend/src/services/wireguard-config.ts index 78d2d638..19569e3c 100644 --- a/backend/src/services/wireguard-config.ts +++ b/backend/src/services/wireguard-config.ts @@ -291,13 +291,16 @@ export function generatePeerClientConf(args: { ? `PrivateKey = ${args.peerPrivateKey}` : `# PrivateKey = <ключ клиента>`, ) - if (args.peerAddress) lines.push(`Address = ${args.peerAddress}`) + if (args.peerAddress?.trim()) lines.push(`Address = ${args.peerAddress.trim()}`) + else lines.push(`# Address = <укажите адрес клиента>`) if (args.peerDns) lines.push(`DNS = ${args.peerDns}`) lines.push(``) lines.push(`[Peer]`) lines.push(`PublicKey = ${args.serverPublicKey}`) - lines.push(`AllowedIPs = ${(args.allowedIps?.length ? args.allowedIps : ["0.0.0.0/0"]).join(", ")}`) + if (args.allowedIps?.length) lines.push(`AllowedIPs = ${args.allowedIps.join(", ")}`) + else lines.push(`# AllowedIPs = <укажите сети через туннель>`) if (args.endpoint) lines.push(`Endpoint = ${args.endpoint}`) + else lines.push(`# Endpoint = <укажите адрес сервера и порт>`) if (args.persistentKeepalive != null && args.persistentKeepalive > 0) { lines.push(`PersistentKeepalive = ${args.persistentKeepalive}`) } diff --git a/components/wireguard/wg-export-sheet.tsx b/components/wireguard/wg-export-sheet.tsx index 888fef6b..2551019b 100644 --- a/components/wireguard/wg-export-sheet.tsx +++ b/components/wireguard/wg-export-sheet.tsx @@ -1,6 +1,7 @@ "use client" import { useMemo } from "react" +import { resolveWgClientEndpoint } from "@mmapp/contracts/wireguard" import { selectWgExportPeer } from "@/lib/wg-export-peer" import type { WgIfaceWithServer } from "@/components/data-grids/wireguard-data-grid" import { @@ -22,6 +23,7 @@ function WgExportSheet({ onRequestLiveExport, initialTab = "rsc", peerId, + serverHost, }: { open: boolean iface: WgIfaceWithServer | null @@ -32,6 +34,7 @@ function WgExportSheet({ initialTab?: "rsc" | "conf" | "peer" /** Selected peer for Peer .conf (defaults to first peer) */ peerId?: string | null + serverHost?: string }) { const formats = useMemo((): CodeExportFormat[] => { if (!iface) { @@ -90,8 +93,7 @@ function WgExportSheet({ peerAddress: peer.clientAddress, peerDns: peer.clientDns, serverPublicKey: iface.publicKey, - allowedIps: peer.allowedIps, - endpoint: peer.clientEndpoint || peer.endpoint || undefined, + endpoint: resolveWgClientEndpoint(peer.clientEndpoint, serverHost, iface.listenPort), persistentKeepalive: peer.persistentKeepalive ?? 25, }) } @@ -120,7 +122,7 @@ function WgExportSheet({ isLive: Boolean(liveContent?.peerConf), }, ] - }, [iface, liveContent, peerId]) + }, [iface, liveContent, peerId, serverHost]) return ( Peer .conf — шаблон для клиента. Заполните его приватный ключ, адрес и сети AllowedIPs. При NAT укажите доступный извне client-endpoint; endpoint удалённого пира здесь не используется.

} initialFormatId={initialTab} liveBusy={liveBusy} liveLabel="С роутера" diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 630cb991..43180309 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -513,3 +513,15 @@ RSC/CONF совпали с native address/listenPort/MTU и выбранным p UI live export не запускался: его текущий handler запрашивает includePrivateKey:true дляRSC/CONF. Этот блок доказывает API false redaction и выбранные параметры, не безопасность всех UI/export режимов. Native/API snapshot GET, plaintext memory сравнения и отсутствие чувствительных полей в публичном JSON проверены. Product source не менялся, общая регрессия не запускалась для внешней read-only проверки. Финальный fixture cleanup exit0, selected WG/peers/IP/routes/IPv4 Firewall baseline совпал. Own peers/IP явно удалялись harness перед интерфейсом; полное обычное UI удаление уже отдельно подтверждено в60/62/64. Эфемерный контейнер удалён, остались три основные службы, backend healthy. [DB controls](ux-ui/acceptance/wg-export-db-controls-2026-10-08.json) до/после servers2/speedHistory9/retention14/migrations9 совпали, audit revisions намеренно сохранены. Runtime backend3722fad/frontendbf43263 не менялся. Настоящий import roundtrip, export private-key modes/UI download, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и остальные протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.66 — правильный client endpoint и явный шаблон Peer .conf (2026-10-08) + +Ошибка65 исправлена локально в backend и preview. Общий resolveWgClientEndpoint в packages/contracts/src/wireguard.ts приоритетно использует явный client-endpoint, добавляет listenPort для hostname/IPv4 без порта и brackets для IPv6; иначе берёт host/listenPort экспортирующего устройства. Неизвестный host/неподходящий формат оставляют Endpoint placeholder. Backend export route больше не использует peer.endpoint; frontend WgExportSheet получает host выбранного устройства из текущего каталога серверов и использует тот же resolver. + +Server allowed-address не обозначает клиентские маршруты: API/preview больше не передают его в Peer .conf. Оба codec generatePeerClientConf сохраняют явно переданные client параметры, но без client Address/AllowedIPs/Endpoint выводят комментарии-подсказки вместо активных выдуманных значений; default0.0.0.0/0 не создаётся. PrivateKey клиента остаётся placeholder. Существующие client-address/client-dns/client-endpoint overrides сохраняются. UI сообщает, что Peer .conf — шаблон: заполнить собственный ключ/адрес/сети и проверить endpoint заNAT. Политика поддерживаемых client routes не расширена новыми RouterOS полями; ready client config без этих данных не обещается. + +Схема после исправления: экспорт интерфейсаA для клиентаB → PublicKey интерфейсаA + Endpoint устройстваA:listenPort; Address клиентаB и его AllowedIPs задаются явно. Backend/frontend изменения согласованы через общий contracts helper, Zod wire format и HTTP response shape не менялись. Общие ReUI primitives не менялись; beforeCode — существующий slot CodeExportSheet. + +Проверки lib/wg-client-template.test.ts —5/5: endpoint exporting server/явный override/hostname/IPv6/неизвестные значения, одинаковые backend/frontend placeholders, отсутствие активного Address/AllowedIPs и default route без явных данных, сохранение явно заданных client параметров. Existing backend wireguard-config.test.ts passed; contracts build, backend TypeScript, scoped frontend TypeScript и ESLint всех изменённых файлов passed. Profile wireguard-links.spec.ts --grep экспорт —4/4: late response isolation/error, неправильный формат, выбор второго peer/ROS ID, его client-address/client-endpoint и отсутствие remote endpoint/server allowed-address в preview. Первый запуск старого теста ожидал ошибочно скопированные peer.allowedIps; fixture и assertions заменены на отличающиеся client metadata, защита выбора второго пира сохранена. ReUI checklist checked. + +Исправление локальное: runtime backend3722fad/frontendbf43263 не менялся. Следующий12.5.67 — production Docker build/deploy backend и frontend с обновлённым contracts source, новая собственная WG пара, native/API export false redaction и правильный endpoint/шаблон, browser preview соответствие, cleanup/baseline. Поддержка client-allowed-address, настоящий client import/connectivity, import roundtrip/private-key modes, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и другие протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index bab29e3e..a12bd8a8 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-08: 12.5.65 проверил API export includePrivateKey=false на новой собственной WG паре: RSC/CONF/native поля совпали, все6 ответов200 без приватных ключей, конфиги/ключи не сохранены. Peer-conf содержит выбранный server key, но при отсутствии client metadata ошибочно использует endpoint удалённого пира; клиентский шаблон не принят как готовый. Own объекты удалены, selected baseline/DB controls совпали, backend healthy. Следующий12.5.66 — исправить client endpoint/defaults и явное состояние шаблона в API/preview, профильные тесты; import roundtrip после этого. Runtime backend3722fad/frontendbf43263. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.66 локально исправил Peer .conf: общий resolver использует client-endpoint или host/listenPort экспортирующего сервера; remote peer endpoint и server allowed-address не подставляются как клиентские значения. Недостающие Address/AllowedIPs/Endpoint явно отмечены шаблоном, UI поясняет ручное заполнение. Unit5/5, codec, export UI4/4, contracts/backend/frontend TypeScript и ESLint passed. Runtime backend3722fad/frontendbf43263 ещё прежний. Следующий12.5.67 — Docker build/deploy обеих частей с обновлённым contracts, реальный API/preview Peer .conf на собственной WG паре с очисткой; import после этого. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-08. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-08 -Этапы0–11 завершены в описанных локальных границах. Этап12 открыт. На тестовом сервере161.104.46.222 работают backend3722fad/frontendbf43263 и два CHR. Внешние проверки подтвердили PG backup/restore/rollback, остановку фоновой работы для одного экземпляра, API JWT/RBAC и браузерные состояния восстановления. Native TCP/UDP speed, история и UI проверены в границах журнала; доступ ко второму CHR восстановлен пользователем. Очередь Telegram проверена на реальном PG: dedupe, конкурентный claim, retry/version, ошибки сохранения и неопределённый исход; ответы отправителя подменялись, реальной доставки это не доказывает. Блоки12.5.43–45 подтвердили browser retry/checkpoint и развернули понятное пояснение HTTP500. 12.5.46 подтвердил browser JWT/RBAC очереди на реальном HTTP/PG; 12.5.47 подтвердил изоляцию checkpoint при смене двух реальных HTTP/PG источников в конфигурации local; 12.5.48 подтвердил scoped axe/клавиатуру в обеих темах и320px; 12.5.49 собрал Q-матрицу и подтвердил отсутствие настройки Telegram; 12.5.50 подтвердил GET-only инвентаризацию: VPN/BGP/OSPF пусты, роли home-router/home-router; По уточнению пользователя12.5.51 создал реальные GRE туннели и подтвердил lifecycle/связность; обнаружена потеря peer IP, 12.5.52 локально исправил сохранение peer metadata, профильные проверки passed; 12.5.53 развернул исправление и подтвердил его на реальной GRE паре/API/UI; 12.5.54 локально исправил устойчивость меню GRE, профильные проверки passed; 12.5.55 развернул frontend731803b и подтвердил устойчивость меню на реальной GRE паре; 12.5.56 локально адаптировал внутренние IP поля GRE для320px, обе темы и профильные проверки passed; 12.5.57 развернул frontendfd521f6 и подтвердил мобильную GRE форму на реальной паре CHR; 12.5.58 подтвердил настоящую WireGuard пару, handshake/Ping/native/UI и финальную очистку; 12.5.59 подтвердил оставшиеся peers/IP после удаления WG и локально исправил backend; 12.5.60 развернул backend/frontend3722fad и подтвердил полное обычное удаление WG; 12.5.61 подтвердил UI создание WG и локально исправил remount меню; переключение осталось открытым. 12.5.62 развернул frontend6a46fb1 и подтвердил полный WG UI lifecycle; 12.5.63 локально адаптировал ширину трёх WireGuard Sheets и подтвердил footer/клавиатуру в обеих темах; 12.5.64 развернул frontendbf43263 и подтвердил live mobile Sheets/UI lifecycle; 12.5.65 подтвердил API export false redaction/RSC-CONF параметры и нашёл ошибочный client endpoint вpeer-conf; следующий12.5.66 — исправить client template defaults/API/preview, import roundtrip после этого. Реальная Telegram доставка, Portal SSO, физический TCP disconnect, multi-instance и остальные профильные сетевые сценарии остаются открытыми. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) содержат доказательства и ограничения. +Этапы0–11 завершены в описанных локальных границах. Этап12 открыт. На тестовом сервере161.104.46.222 работают backend3722fad/frontendbf43263 и два CHR. Внешние проверки подтвердили PG backup/restore/rollback, остановку фоновой работы для одного экземпляра, API JWT/RBAC и браузерные состояния восстановления. Native TCP/UDP speed, история и UI проверены в границах журнала; доступ ко второму CHR восстановлен пользователем. Очередь Telegram проверена на реальном PG: dedupe, конкурентный claim, retry/version, ошибки сохранения и неопределённый исход; ответы отправителя подменялись, реальной доставки это не доказывает. Блоки12.5.43–45 подтвердили browser retry/checkpoint и развернули понятное пояснение HTTP500. 12.5.46 подтвердил browser JWT/RBAC очереди на реальном HTTP/PG; 12.5.47 подтвердил изоляцию checkpoint при смене двух реальных HTTP/PG источников в конфигурации local; 12.5.48 подтвердил scoped axe/клавиатуру в обеих темах и320px; 12.5.49 собрал Q-матрицу и подтвердил отсутствие настройки Telegram; 12.5.50 подтвердил GET-only инвентаризацию: VPN/BGP/OSPF пусты, роли home-router/home-router; По уточнению пользователя12.5.51 создал реальные GRE туннели и подтвердил lifecycle/связность; обнаружена потеря peer IP, 12.5.52 локально исправил сохранение peer metadata, профильные проверки passed; 12.5.53 развернул исправление и подтвердил его на реальной GRE паре/API/UI; 12.5.54 локально исправил устойчивость меню GRE, профильные проверки passed; 12.5.55 развернул frontend731803b и подтвердил устойчивость меню на реальной GRE паре; 12.5.56 локально адаптировал внутренние IP поля GRE для320px, обе темы и профильные проверки passed; 12.5.57 развернул frontendfd521f6 и подтвердил мобильную GRE форму на реальной паре CHR; 12.5.58 подтвердил настоящую WireGuard пару, handshake/Ping/native/UI и финальную очистку; 12.5.59 подтвердил оставшиеся peers/IP после удаления WG и локально исправил backend; 12.5.60 развернул backend/frontend3722fad и подтвердил полное обычное удаление WG; 12.5.61 подтвердил UI создание WG и локально исправил remount меню; переключение осталось открытым. 12.5.62 развернул frontend6a46fb1 и подтвердил полный WG UI lifecycle; 12.5.63 локально адаптировал ширину трёх WireGuard Sheets и подтвердил footer/клавиатуру в обеих темах; 12.5.64 развернул frontendbf43263 и подтвердил live mobile Sheets/UI lifecycle; 12.5.65 подтвердил API export false redaction/RSC-CONF параметры и нашёл ошибочный client endpoint вpeer-conf; 12.5.66 локально исправил client endpoint и явные template placeholders; следующий12.5.67 — Docker deploy обеих частей/contracts и повторный реальный Peer .conf export/preview; import после этого. Реальная Telegram доставка, Portal SSO, физический TCP disconnect, multi-instance и остальные профильные сетевые сценарии остаются открытыми. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) содержат доказательства и ограничения. Выполненные внешние сценарии не заменяют приёмку оставшихся интеграций и условий эксплуатации. Датированные продолжения ниже — история; точный следующий шаг указан в начале [журнала](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 4ebc6b17..457d3351 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-08: 12.5.65 проверил API export includePrivateKey=false на новой собственной WG паре: RSC/CONF/native поля совпали, все6 ответов200 без приватных ключей, конфиги/ключи не сохранены. Peer-conf содержит выбранный server key, но при отсутствии client metadata ошибочно использует endpoint удалённого пира; клиентский шаблон не принят как готовый. Own объекты удалены, selected baseline/DB controls совпали, backend healthy. Следующий12.5.66 — исправить client endpoint/defaults и явное состояние шаблона в API/preview, профильные тесты; import roundtrip после этого. Runtime backend3722fad/frontendbf43263. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.66 локально исправил Peer .conf: общий resolver использует client-endpoint или host/listenPort экспортирующего сервера; remote peer endpoint и server allowed-address не подставляются как клиентские значения. Недостающие Address/AllowedIPs/Endpoint явно отмечены шаблоном, UI поясняет ручное заполнение. Unit5/5, codec, export UI4/4, contracts/backend/frontend TypeScript и ESLint passed. Runtime backend3722fad/frontendbf43263 ещё прежний. Следующий12.5.67 — Docker build/deploy обеих частей с обновлённым contracts, реальный API/preview Peer .conf на собственной WG паре с очисткой; import после этого. Этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -34,7 +34,19 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | -| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–65 и ограничения ниже. Runtime backend3722fad/frontendbf43263. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–66 и ограничения ниже. Runtime backend3722fad/frontendbf43263. | + +## 12.5.66 — правильный client endpoint и явный шаблон Peer .conf (2026-10-08) + +Ошибка65 исправлена локально в backend и preview. Общий resolveWgClientEndpoint в packages/contracts/src/wireguard.ts приоритетно использует явный client-endpoint, добавляет listenPort для hostname/IPv4 без порта и brackets для IPv6; иначе берёт host/listenPort экспортирующего устройства. Неизвестный host/неподходящий формат оставляют Endpoint placeholder. Backend export route больше не использует peer.endpoint; frontend WgExportSheet получает host выбранного устройства из текущего каталога серверов и использует тот же resolver. + +Server allowed-address не обозначает клиентские маршруты: API/preview больше не передают его в Peer .conf. Оба codec generatePeerClientConf сохраняют явно переданные client параметры, но без client Address/AllowedIPs/Endpoint выводят комментарии-подсказки вместо активных выдуманных значений; default0.0.0.0/0 не создаётся. PrivateKey клиента остаётся placeholder. Существующие client-address/client-dns/client-endpoint overrides сохраняются. UI сообщает, что Peer .conf — шаблон: заполнить собственный ключ/адрес/сети и проверить endpoint заNAT. Политика поддерживаемых client routes не расширена новыми RouterOS полями; ready client config без этих данных не обещается. + +Схема после исправления: экспорт интерфейсаA для клиентаB → PublicKey интерфейсаA + Endpoint устройстваA:listenPort; Address клиентаB и его AllowedIPs задаются явно. Backend/frontend изменения согласованы через общий contracts helper, Zod wire format и HTTP response shape не менялись. Общие ReUI primitives не менялись; beforeCode — существующий slot CodeExportSheet. + +Проверки lib/wg-client-template.test.ts —5/5: endpoint exporting server/явный override/hostname/IPv6/неизвестные значения, одинаковые backend/frontend placeholders, отсутствие активного Address/AllowedIPs и default route без явных данных, сохранение явно заданных client параметров. Existing backend wireguard-config.test.ts passed; contracts build, backend TypeScript, scoped frontend TypeScript и ESLint всех изменённых файлов passed. Profile wireguard-links.spec.ts --grep экспорт —4/4: late response isolation/error, неправильный формат, выбор второго peer/ROS ID, его client-address/client-endpoint и отсутствие remote endpoint/server allowed-address в preview. Первый запуск старого теста ожидал ошибочно скопированные peer.allowedIps; fixture и assertions заменены на отличающиеся client metadata, защита выбора второго пира сохранена. ReUI checklist checked. + +Исправление локальное: runtime backend3722fad/frontendbf43263 не менялся. Следующий12.5.67 — production Docker build/deploy backend и frontend с обновлённым contracts source, новая собственная WG пара, native/API export false redaction и правильный endpoint/шаблон, browser preview соответствие, cleanup/baseline. Поддержка client-allowed-address, настоящий client import/connectivity, import roundtrip/private-key modes, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и другие протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. ## 12.5.65 — экспорт WireGuard без приватных ключей и ошибка client endpoint (2026-10-08) diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index c08e1fc8..2e081183 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-08: 12.5.65 проверил API export includePrivateKey=false на новой собственной WG паре: RSC/CONF/native поля совпали, все6 ответов200 без приватных ключей, конфиги/ключи не сохранены. Peer-conf содержит выбранный server key, но при отсутствии client metadata ошибочно использует endpoint удалённого пира; клиентский шаблон не принят как готовый. Own объекты удалены, selected baseline/DB controls совпали, backend healthy. Следующий12.5.66 — исправить client endpoint/defaults и явное состояние шаблона в API/preview, профильные тесты; import roundtrip после этого. Runtime backend3722fad/frontendbf43263. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.66 локально исправил Peer .conf: общий resolver использует client-endpoint или host/listenPort экспортирующего сервера; remote peer endpoint и server allowed-address не подставляются как клиентские значения. Недостающие Address/AllowedIPs/Endpoint явно отмечены шаблоном, UI поясняет ручное заполнение. Unit5/5, codec, export UI4/4, contracts/backend/frontend TypeScript и ESLint passed. Runtime backend3722fad/frontendbf43263 ещё прежний. Следующий12.5.67 — Docker build/deploy обеих частей с обновлённым contracts, реальный API/preview Peer .conf на собственной WG паре с очисткой; import после этого. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -1678,3 +1678,15 @@ RSC/CONF совпали с native address/listenPort/MTU и выбранным p UI live export не запускался: его текущий handler запрашивает includePrivateKey:true дляRSC/CONF. Этот блок доказывает API false redaction и выбранные параметры, не безопасность всех UI/export режимов. Native/API snapshot GET, plaintext memory сравнения и отсутствие чувствительных полей в публичном JSON проверены. Product source не менялся, общая регрессия не запускалась для внешней read-only проверки. Финальный fixture cleanup exit0, selected WG/peers/IP/routes/IPv4 Firewall baseline совпал. Own peers/IP явно удалялись harness перед интерфейсом; полное обычное UI удаление уже отдельно подтверждено в60/62/64. Эфемерный контейнер удалён, остались три основные службы, backend healthy. [DB controls](ux-ui/acceptance/wg-export-db-controls-2026-10-08.json) до/после servers2/speedHistory9/retention14/migrations9 совпали, audit revisions намеренно сохранены. Runtime backend3722fad/frontendbf43263 не менялся. Настоящий import roundtrip, export private-key modes/UI download, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и остальные протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.66 — правильный client endpoint и явный шаблон Peer .conf (2026-10-08) + +Ошибка65 исправлена локально в backend и preview. Общий resolveWgClientEndpoint в packages/contracts/src/wireguard.ts приоритетно использует явный client-endpoint, добавляет listenPort для hostname/IPv4 без порта и brackets для IPv6; иначе берёт host/listenPort экспортирующего устройства. Неизвестный host/неподходящий формат оставляют Endpoint placeholder. Backend export route больше не использует peer.endpoint; frontend WgExportSheet получает host выбранного устройства из текущего каталога серверов и использует тот же resolver. + +Server allowed-address не обозначает клиентские маршруты: API/preview больше не передают его в Peer .conf. Оба codec generatePeerClientConf сохраняют явно переданные client параметры, но без client Address/AllowedIPs/Endpoint выводят комментарии-подсказки вместо активных выдуманных значений; default0.0.0.0/0 не создаётся. PrivateKey клиента остаётся placeholder. Существующие client-address/client-dns/client-endpoint overrides сохраняются. UI сообщает, что Peer .conf — шаблон: заполнить собственный ключ/адрес/сети и проверить endpoint заNAT. Политика поддерживаемых client routes не расширена новыми RouterOS полями; ready client config без этих данных не обещается. + +Схема после исправления: экспорт интерфейсаA для клиентаB → PublicKey интерфейсаA + Endpoint устройстваA:listenPort; Address клиентаB и его AllowedIPs задаются явно. Backend/frontend изменения согласованы через общий contracts helper, Zod wire format и HTTP response shape не менялись. Общие ReUI primitives не менялись; beforeCode — существующий slot CodeExportSheet. + +Проверки lib/wg-client-template.test.ts —5/5: endpoint exporting server/явный override/hostname/IPv6/неизвестные значения, одинаковые backend/frontend placeholders, отсутствие активного Address/AllowedIPs и default route без явных данных, сохранение явно заданных client параметров. Existing backend wireguard-config.test.ts passed; contracts build, backend TypeScript, scoped frontend TypeScript и ESLint всех изменённых файлов passed. Profile wireguard-links.spec.ts --grep экспорт —4/4: late response isolation/error, неправильный формат, выбор второго peer/ROS ID, его client-address/client-endpoint и отсутствие remote endpoint/server allowed-address в preview. Первый запуск старого теста ожидал ошибочно скопированные peer.allowedIps; fixture и assertions заменены на отличающиеся client metadata, защита выбора второго пира сохранена. ReUI checklist checked. + +Исправление локальное: runtime backend3722fad/frontendbf43263 не менялся. Следующий12.5.67 — production Docker build/deploy backend и frontend с обновлённым contracts source, новая собственная WG пара, native/API export false redaction и правильный endpoint/шаблон, browser preview соответствие, cleanup/baseline. Поддержка client-allowed-address, настоящий client import/connectivity, import roundtrip/private-key modes, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и другие протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 00222321..cb18170d 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -521,3 +521,15 @@ RSC/CONF совпали с native address/listenPort/MTU и выбранным p UI live export не запускался: его текущий handler запрашивает includePrivateKey:true дляRSC/CONF. Этот блок доказывает API false redaction и выбранные параметры, не безопасность всех UI/export режимов. Native/API snapshot GET, plaintext memory сравнения и отсутствие чувствительных полей в публичном JSON проверены. Product source не менялся, общая регрессия не запускалась для внешней read-only проверки. Финальный fixture cleanup exit0, selected WG/peers/IP/routes/IPv4 Firewall baseline совпал. Own peers/IP явно удалялись harness перед интерфейсом; полное обычное UI удаление уже отдельно подтверждено в60/62/64. Эфемерный контейнер удалён, остались три основные службы, backend healthy. [DB controls](ux-ui/acceptance/wg-export-db-controls-2026-10-08.json) до/после servers2/speedHistory9/retention14/migrations9 совпали, audit revisions намеренно сохранены. Runtime backend3722fad/frontendbf43263 не менялся. Настоящий import roundtrip, export private-key modes/UI download, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и остальные протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.66 — правильный client endpoint и явный шаблон Peer .conf (2026-10-08) + +Ошибка65 исправлена локально в backend и preview. Общий resolveWgClientEndpoint в packages/contracts/src/wireguard.ts приоритетно использует явный client-endpoint, добавляет listenPort для hostname/IPv4 без порта и brackets для IPv6; иначе берёт host/listenPort экспортирующего устройства. Неизвестный host/неподходящий формат оставляют Endpoint placeholder. Backend export route больше не использует peer.endpoint; frontend WgExportSheet получает host выбранного устройства из текущего каталога серверов и использует тот же resolver. + +Server allowed-address не обозначает клиентские маршруты: API/preview больше не передают его в Peer .conf. Оба codec generatePeerClientConf сохраняют явно переданные client параметры, но без client Address/AllowedIPs/Endpoint выводят комментарии-подсказки вместо активных выдуманных значений; default0.0.0.0/0 не создаётся. PrivateKey клиента остаётся placeholder. Существующие client-address/client-dns/client-endpoint overrides сохраняются. UI сообщает, что Peer .conf — шаблон: заполнить собственный ключ/адрес/сети и проверить endpoint заNAT. Политика поддерживаемых client routes не расширена новыми RouterOS полями; ready client config без этих данных не обещается. + +Схема после исправления: экспорт интерфейсаA для клиентаB → PublicKey интерфейсаA + Endpoint устройстваA:listenPort; Address клиентаB и его AllowedIPs задаются явно. Backend/frontend изменения согласованы через общий contracts helper, Zod wire format и HTTP response shape не менялись. Общие ReUI primitives не менялись; beforeCode — существующий slot CodeExportSheet. + +Проверки lib/wg-client-template.test.ts —5/5: endpoint exporting server/явный override/hostname/IPv6/неизвестные значения, одинаковые backend/frontend placeholders, отсутствие активного Address/AllowedIPs и default route без явных данных, сохранение явно заданных client параметров. Existing backend wireguard-config.test.ts passed; contracts build, backend TypeScript, scoped frontend TypeScript и ESLint всех изменённых файлов passed. Profile wireguard-links.spec.ts --grep экспорт —4/4: late response isolation/error, неправильный формат, выбор второго peer/ROS ID, его client-address/client-endpoint и отсутствие remote endpoint/server allowed-address в preview. Первый запуск старого теста ожидал ошибочно скопированные peer.allowedIps; fixture и assertions заменены на отличающиеся client metadata, защита выбора второго пира сохранена. ReUI checklist checked. + +Исправление локальное: runtime backend3722fad/frontendbf43263 не менялся. Следующий12.5.67 — production Docker build/deploy backend и frontend с обновлённым contracts source, новая собственная WG пара, native/API export false redaction и правильный endpoint/шаблон, browser preview соответствие, cleanup/baseline. Поддержка client-allowed-address, настоящий client import/connectivity, import roundtrip/private-key modes, throughput/IPv6/RBAC/внешние failures/multi-instance/Telegram/Portal и другие протоколы/интеграции остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/lib/wg-client-template.test.ts b/lib/wg-client-template.test.ts new file mode 100644 index 00000000..45e9c6cd --- /dev/null +++ b/lib/wg-client-template.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { resolveWgClientEndpoint } from '../packages/contracts/src/wireguard' +import { generatePeerClientConf as client } from './wg-config' +import { generatePeerClientConf as backend } from '../backend/src/services/wireguard-config' + +test('client endpoint uses exporting server or explicit client metadata', () => { + assert.equal(resolveWgClientEndpoint(undefined, '192.0.2.1', 51879), '192.0.2.1:51879') + assert.equal(resolveWgClientEndpoint('vpn.example:443', '192.0.2.1', 51879), 'vpn.example:443') + assert.equal(resolveWgClientEndpoint('vpn.example', '192.0.2.1', 51879), 'vpn.example:51879') + assert.equal(resolveWgClientEndpoint(undefined, '2001:db8::1', 51879), '[2001:db8::1]:51879') + assert.equal(resolveWgClientEndpoint('[2001:db8::2]:443', '192.0.2.1', 51879), '[2001:db8::2]:443') + assert.equal(resolveWgClientEndpoint(undefined, undefined, 51879), undefined) + assert.equal(resolveWgClientEndpoint('vpn.example:0', '192.0.2.1', 51879), undefined) + assert.equal(resolveWgClientEndpoint('https://vpn.example', '192.0.2.1', 51879), undefined) +}) +for (const [name, generate] of [['frontend', client], ['backend', backend]] as const) { + test(`${name}: incomplete client data stays a template without invented routes`, () => { + const code = generate({ serverPublicKey: 'fixture-server-key', endpoint: resolveWgClientEndpoint(undefined, '192.0.2.1', 51879) }) + assert(code.includes('Endpoint = 192.0.2.1:51879')) + assert(code.includes('# Address = <')) + assert(code.includes('# AllowedIPs = <')) + assert(code.includes('# PrivateKey = <')) + assert(!/^AllowedIPs =|^Address =|^PrivateKey =/m.test(code)) + assert(!code.includes('0.0.0.0/0')) + }) + test(`${name}: explicit client parameters remain unchanged`, () => { + const code = generate({ serverPublicKey: 'fixture-server-key', peerAddress: '198.18.255.242/30', peerDns: '192.0.2.53', allowedIps: ['198.18.255.240/30'], endpoint: 'vpn.example:443', persistentKeepalive: 5 }) + assert(code.includes('Address = 198.18.255.242/30')) + assert(code.includes('AllowedIPs = 198.18.255.240/30')) + assert(code.includes('DNS = 192.0.2.53')) + assert(code.includes('Endpoint = vpn.example:443')) + }) +} diff --git a/lib/wg-config.ts b/lib/wg-config.ts index b5f7340c..12fb5d8c 100644 --- a/lib/wg-config.ts +++ b/lib/wg-config.ts @@ -324,13 +324,16 @@ export function generatePeerClientConf(args: { const lines: string[] = [] lines.push(`[Interface]`) lines.push(`# PrivateKey = <ключ клиента>`) - if (args.peerAddress) lines.push(`Address = ${args.peerAddress}`) + if (args.peerAddress?.trim()) lines.push(`Address = ${args.peerAddress.trim()}`) + else lines.push(`# Address = <укажите адрес клиента>`) if (args.peerDns) lines.push(`DNS = ${args.peerDns}`) lines.push(``) lines.push(`[Peer]`) lines.push(`PublicKey = ${args.serverPublicKey}`) - lines.push(`AllowedIPs = ${(args.allowedIps?.length ? args.allowedIps : ["0.0.0.0/0"]).join(", ")}`) + if (args.allowedIps?.length) lines.push(`AllowedIPs = ${args.allowedIps.join(", ")}`) + else lines.push(`# AllowedIPs = <укажите сети через туннель>`) if (args.endpoint) lines.push(`Endpoint = ${args.endpoint}`) + else lines.push(`# Endpoint = <укажите адрес сервера и порт>`) if (args.persistentKeepalive != null && args.persistentKeepalive > 0) { lines.push(`PersistentKeepalive = ${args.persistentKeepalive}`) } diff --git a/packages/contracts/src/wireguard.ts b/packages/contracts/src/wireguard.ts index 2c08dd65..6ad6f456 100644 --- a/packages/contracts/src/wireguard.ts +++ b/packages/contracts/src/wireguard.ts @@ -1,5 +1,16 @@ import { z } from "zod" +/** The client's remote endpoint belongs to the exporting interface, not its peer. */ +export function resolveWgClientEndpoint(clientEndpoint: string | undefined, serverHost: string | undefined, listenPort: number): string | undefined { + const value = clientEndpoint?.trim() || serverHost?.trim() + if (!value || /[\s/]/.test(value) || !Number.isInteger(listenPort) || listenPort < 1 || listenPort > 65535) return undefined + const explicit = value.match(/^(\[[^\]]+\]|[^:]+):(\d+)$/) + if (explicit) return Number(explicit[2]) >= 1 && Number(explicit[2]) <= 65535 ? value : undefined + if (value.startsWith("[")) return value.endsWith("]") ? `${value}:${listenPort}` : undefined + if (value.includes(":")) return value.split(":").length > 2 && /^[\da-f:]+$/i.test(value) ? `[${value}]:${listenPort}` : undefined + return `${value}:${listenPort}` +} + export const wgStatusSchema = z.enum(["up", "down"]) export const wgPeerDtoSchema = z.object({ diff --git a/tests/e2e/wireguard-links.spec.ts b/tests/e2e/wireguard-links.spec.ts index 427b9c77..0996725b 100644 --- a/tests/e2e/wireguard-links.spec.ts +++ b/tests/e2e/wireguard-links.spec.ts @@ -419,8 +419,8 @@ test("WireGuard: экспорт отклоняет повреждённый от test("WireGuard: экспорт второго пира использует его RouterOS ID", async ({ page }) => { await setup(page, "/wireguard?server=1") const fixture = wgListResponseSchema.parse({ interfaces: [{ id: "iface", rosId: "*I", serverId: "1", serverName: "Источник", name: "wg-peers", publicKey: "fixture-server-key", listenPort: 13231, mtu: 1420, enabled: true, status: "up", comment: "", peers: [ - { id: "first", rosId: "*P1", publicKey: "fixture-peer-one", allowedIps: ["10.0.1.1/32"] }, - { id: "second", rosId: "*P2", publicKey: "fixture-peer-two", allowedIps: ["10.0.2.1/32"] }, + { id: "first", rosId: "*P1", publicKey: "fixture-peer-one", allowedIps: ["10.0.1.1/32"], clientAddress: "10.0.1.9/32" }, + { id: "second", rosId: "*P2", publicKey: "fixture-peer-two", allowedIps: ["10.0.2.1/32"], clientAddress: "10.0.2.9/32", clientEndpoint: "vpn-second.example:51879", endpoint: "remote-peer.example:1111" }, ] }] }) await page.route("**/api/wireguard", route => route.fulfill({ json: fixture })) let body: Record | undefined @@ -433,8 +433,13 @@ test("WireGuard: экспорт второго пира использует е await expect(page.locator("tr[data-row-id]")).toHaveCount(2) await page.locator("tr[data-row-id]").filter({ hasText: "10.0.2.1/32" }).getByRole("button", { name: "Экспорт peer .conf" }).click() const dialog = page.getByRole("dialog", { name: "Экспорт WireGuard" }) - await expect(dialog.locator("pre")).toContainText("10.0.2.1/32") - await expect(dialog.locator("pre")).not.toContainText("10.0.1.1/32") + await expect(dialog.locator("pre")).toContainText("Address = 10.0.2.9/32") + await expect(dialog.locator("pre")).toContainText("Endpoint = vpn-second.example:51879") + await expect(dialog.locator("pre")).toContainText("# AllowedIPs = <") + await expect(dialog.locator("pre")).not.toContainText("remote-peer.example") + await expect(dialog).toContainText("шаблон для клиента") + await expect(dialog.locator("pre")).not.toContainText("10.0.1.9/32") + await expect(dialog.locator("pre")).not.toContainText("10.0.2.1/32") await dialog.getByRole("button", { name: "Подтянуть конфиг с роутера с private-key" }).click() await expect(dialog.locator("pre")).toContainText("SECOND_PEER_EXPORT") expect(body).toMatchObject({ serverId: "1", interfaceName: "wg-peers", peerId: "*P2", format: "peer-conf", includePrivateKey: false })