From 8e5d51ab2c1417934b7c19451a9ae16ec9765cae Mon Sep 17 00:00:00 2001 From: andrey271192 <76248502+andrey271192@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:46:43 +0300 Subject: [PATCH] fix: auto-detect wg binary in containers --- docs/panel-guide.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- server.js | 54 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/docs/panel-guide.md b/docs/panel-guide.md index f31068b..205d542 100644 --- a/docs/panel-guide.md +++ b/docs/panel-guide.md @@ -44,6 +44,8 @@ Начиная с **v1.2.38** панель сама выбирает рабочие пути внутри контейнера: среди `/opt/amnezia/awg/awg0.conf`, `/opt/amnezia/awg/wg0.conf`, `/opt/amnezia/wireguard/wg0.conf` берётся конфиг с максимальным числом `[Peer]`; среди `/opt/amnezia/awg/clientsTable` и `/opt/amnezia/wireguard/clientsTable` берётся таблица с максимальным числом клиентов. Это закрывает установки, где приложение Amnezia создало старых клиентов в `wg0.conf`, а не в `awg0.conf`. +Начиная с **v1.2.39** панель auto-detect делает не только для путей, но и для binary внутри контейнера. Если профиль указывает `wgBinary=awg`, а в контейнере есть только `wg`, команды `show`, `pubkey`, `genkey`, `syncconf` используют найденный binary. Это закрывает ошибку `sh: awg: not found` на контейнерах вида `amneziawg`. + ## Лендинг не поднимается (порт 80 занят) При ошибке bind `:80` используйте при установке **`SKIP_LANDING=1`** или **`LANDING_PORT=8081`** — админка на `HOST_PORT` (например 8080) от этого не зависит. diff --git a/package-lock.json b/package-lock.json index 3493e96..02de4ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "amnezia-admin", - "version": "1.2.38", + "version": "1.2.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "amnezia-admin", - "version": "1.2.38", + "version": "1.2.39", "license": "MIT", "dependencies": { "express": "^4.21.2" diff --git a/package.json b/package.json index 38e3349..6f6bd78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amnezia-admin", - "version": "1.2.38", + "version": "1.2.39", "private": false, "description": "amnezia_web-PRO — веб-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)", "license": "MIT", diff --git a/server.js b/server.js index 7382a18..8721f8b 100644 --- a/server.js +++ b/server.js @@ -828,6 +828,7 @@ function createRuntime(profile) { let resolvedContainer = null; let resolvedConfPath = null; let resolvedClientsPath = null; + let resolvedWgBinary = null; const confPath = profile.confPath; const clientsPath = profile.clientsPath; const iface = profile.iface; @@ -928,6 +929,30 @@ function createRuntime(profile) { return resolvedClientsPath; } + async function resolveWgBinary() { + if (resolvedWgBinary) return resolvedWgBinary; + const container = await resolveContainer(); + const preferred = uniqueList([ + wgBinary, + ifaceFromConfPath(resolvedConfPath || confPath, iface) === "wg0" ? "wg" : "", + "awg", + "wg", + ]); + for (const candidate of preferred) { + try { + await execDocker(["exec", container, "sh", "-c", `command -v '${candidate}' >/dev/null 2>&1`]); + resolvedWgBinary = candidate; + if (resolvedWgBinary !== wgBinary) { + console.log(`→ WG binary авто: «${resolvedWgBinary}» вместо «${wgBinary}» (${profile.label || profile.id})`); + } + return resolvedWgBinary; + } catch { + /* not present */ + } + } + throw new Error(`Не найден wg/awg binary в контейнере ${container}: проверял ${preferred.join(", ")}`); + } + async function dockerExec(cmd) { const container = await resolveContainer(); const { stdout, stderr } = await execDocker(["exec", container, "sh", "-c", cmd]); @@ -968,8 +993,9 @@ function createRuntime(profile) { async function applySyncconf() { const activeConfPath = await resolveConfPath(); const activeIface = ifaceFromConfPath(activeConfPath, iface); + const activeBinary = await resolveWgBinary(); await dockerExec( - `wg-quick strip '${activeConfPath}' > /tmp/wg-admin-strip.conf && ${wgBinary} syncconf ${activeIface} /tmp/wg-admin-strip.conf` + `wg-quick strip '${activeConfPath}' > /tmp/wg-admin-strip.conf && ${activeBinary} syncconf ${activeIface} /tmp/wg-admin-strip.conf` ); } @@ -1012,8 +1038,12 @@ function createRuntime(profile) { get iface() { return ifaceFromConfPath(resolvedConfPath || confPath, iface); }, + get wgBinary() { + return resolvedWgBinary || wgBinary; + }, resolveConfPath, resolveClientsPath, + resolveWgBinary, get clientsPath() { return resolvedClientsPath || clientsPath; }, @@ -1416,7 +1446,8 @@ async function wgPubkeyFromPrivate(rt, privKeyB64) { throw new Error("Некорректный формат приватного ключа сервера в awg0.conf"); } const q = key.replace(/'/g, `'\\''`); - const out = await rt.dockerExec(`printf '%s\\n' '${q}' | ${rt.profile.wgBinary} pubkey`); + const activeBinary = await rt.resolveWgBinary(); + const out = await rt.dockerExec(`printf '%s\\n' '${q}' | ${activeBinary} pubkey`); const pub = out.trim().split(/\s+/)[0]; if (!pub) throw new Error("Не удалось получить публичный ключ сервера (wg pubkey)."); return pub; @@ -1485,7 +1516,8 @@ async function buildClientConfExport(rt, lc, ifaceMap, req, row, conf) { const endpointHost = resolveExportEndpointHost(lc, req); const listenPort = ifaceMap.ListenPort ? Number(ifaceMap.ListenPort) : NaN; const portNum = Number(pickLc(lc, "port")) || (Number.isFinite(listenPort) ? listenPort : NaN); - const defaultPort = rt.profile.wgBinary === "awg" ? 55424 : 51820; + const activeBinary = await rt.resolveWgBinary(); + const defaultPort = activeBinary === "awg" ? 55424 : 51820; const port = Number.isFinite(portNum) && portNum > 0 ? portNum : defaultPort; const dns1 = String(pickLc(lc, "dns1") || process.env.CLIENT_EXPORT_DNS1?.trim() || "1.1.1.1"); @@ -1495,7 +1527,7 @@ async function buildClientConfExport(rt, lc, ifaceMap, req, row, conf) { const mtuVal = pickLc(lc, "mtu", "MTU"); const mtuLine = mtuVal ? `MTU = ${String(mtuVal).trim()}\n` : ""; - if (rt.profile.wgBinary === "awg") { + if (activeBinary === "awg") { const Jc = String(pickLc(lc, "Jc", "junk_packet_count", "junkPacketCount") ?? AWG_EXPORT_DEFAULTS.Jc); const Jmin = String(pickLc(lc, "Jmin", "junk_packet_min_size", "junkPacketMinSize") ?? AWG_EXPORT_DEFAULTS.Jmin); const Jmax = String(pickLc(lc, "Jmax", "junk_packet_max_size", "junkPacketMaxSize") ?? AWG_EXPORT_DEFAULTS.Jmax); @@ -1587,13 +1619,14 @@ function parseIpv4ToParts(ip) { } async function awgGenKeypair(rt) { - const privOut = await rt.dockerExec(`${rt.profile.wgBinary} genkey`); + const activeBinary = await rt.resolveWgBinary(); + const privOut = await rt.dockerExec(`${activeBinary} genkey`); const priv = privOut.trim().split(/\s+/)[0]; if (!priv || !/^[A-Za-z0-9+/=_-]+$/.test(priv)) { throw new Error("Не удалось сгенерировать ключ клиента (genkey)."); } const q = priv.replace(/'/g, `'\\''`); - const pubOut = await rt.dockerExec(`printf '%s\\n' '${q}' | ${rt.profile.wgBinary} pubkey`); + const pubOut = await rt.dockerExec(`printf '%s\\n' '${q}' | ${activeBinary} pubkey`); const pub = pubOut.trim().split(/\s+/)[0]; if (!pub) throw new Error("Не удалось получить публичный ключ клиента."); return { priv, pub }; @@ -2412,7 +2445,7 @@ app.get("/api/clients", requireAuth, async (req, res) => { try { let wgShow = ""; try { - wgShow = await rt.dockerExec(`${rt.profile.wgBinary} show ${rt.iface}`); + wgShow = await rt.dockerExec(`${await rt.resolveWgBinary()} show ${rt.iface}`); } catch { wgShow = ""; } @@ -2497,6 +2530,7 @@ app.get("/api/clients", requireAuth, async (req, res) => { confPath: activeConfPath || rt.confPath, clientsPath: activeClientsPath || rt.clientsPath, iface: rt.iface, + wgBinary: rt.wgBinary, protocol: "AmneziaWG", peerCount: conf.peers.length, clients: rows, @@ -2557,6 +2591,7 @@ app.get("/api/clients/source-report", requireAuth, async (req, res) => { const container = await rt.resolveContainer(); const activeConfPath = await rt.resolveConfPath(); const activeClientsPath = await rt.resolveClientsPath(); + const activeBinary = await rt.resolveWgBinary(); let confText = ""; let tableText = ""; let conf = { peers: [] }; @@ -2584,6 +2619,7 @@ app.get("/api/clients/source-report", requireAuth, async (req, res) => { container, confPath: activeConfPath, iface: rt.iface, + wgBinary: activeBinary, clientsPath: activeClientsPath, confBytes: Buffer.byteLength(confText, "utf8"), clientsTableBytes: Buffer.byteLength(tableText, "utf8"), @@ -2713,7 +2749,7 @@ app.post("/api/clients/create", requireAuth, requireProTier, async (req, res) => }); return; } - const defaultPort = rt.profile.wgBinary === "awg" ? 55424 : 51820; + const defaultPort = (await rt.resolveWgBinary()) === "awg" ? 55424 : 51820; const endpointPort = Number.isFinite(listenPort) && listenPort > 0 ? listenPort : defaultPort; const psk = await rt.inferPskFromConf(conf); @@ -2819,7 +2855,7 @@ app.post("/api/clients/create-cascade", requireAuth, requireProTier, async (req, endpointPort = Number.isFinite(listenPort) && listenPort > 0 ? listenPort - : rt.profile.wgBinary === "awg" + : (await rt.resolveWgBinary()) === "awg" ? 55424 : 51820; }