From 9e17b05444ea8b411d189935702e9c3c53ca671c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Fri, 15 May 2026 02:41:02 +0300 Subject: [PATCH] feat(community): auto-detect AWG_PROFILES for PRO install Before spawning helper, scan running amnezia-awg* containers and build AWG_PROFILES JSON labeled by UDP port; pass via env so PRO install.sh writes it into new panel and /root snapshot. Co-authored-by: Cursor --- package.json | 2 +- server.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 25c6404..2883bf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amnezia-admin", - "version": "1.2.11", + "version": "1.2.12", "private": true, "description": "amnezia_web — базовая панель AmneziaWG (FREE: просмотр и удаление клиентов; полная версия — PRO).", "license": "MIT", diff --git a/server.js b/server.js index 3dec68b..f441ee2 100644 --- a/server.js +++ b/server.js @@ -442,6 +442,65 @@ fi `.trim(); } +/** + * Авто-обнаружение профилей `amnezia-awg*` до старта PRO `install.sh`. + * Возвращает JSON, готовый для `-e AWG_PROFILES=...` или пустую строку, + * если найден ровно один контейнер (fallback panel-side тогда корректен). + * + * Метки берём из публичных портов (`443/udp` → "AmneziaWG (443)"), порядок — + * по возрастанию первого UDP-порта (если совпадает — по имени). + */ +function autoDiscoverAwgProfilesJson() { + if (envTruthy(process.env.COMMUNITY_DISABLE_AWG_PROFILES_AUTODETECT)) return ""; + if (process.env.AWG_PROFILES?.trim()) return process.env.AWG_PROFILES.trim(); + try { + const namesOut = execFileSync( + "docker", + ["ps", "--format", "{{.Names}}", "--filter", "name=^amnezia-awg"], + { stdio: ["ignore", "pipe", "ignore"], timeout: 5000 }, + ) + .toString() + .trim(); + const names = namesOut.split("\n").map((x) => x.trim()).filter(Boolean); + if (names.length < 2) return ""; + const entries = names + .map((name) => { + let firstUdp = null; + let portLabel = ""; + try { + const ports = execFileSync("docker", ["port", name], { + stdio: ["ignore", "pipe", "ignore"], + timeout: 5000, + }) + .toString() + .trim(); + const udpLine = ports.split("\n").find((l) => /\/udp\b/.test(l)); + if (udpLine) { + const m = udpLine.match(/^(\d+)\/udp/); + if (m) { + firstUdp = Number(m[1]); + portLabel = ` (${m[1]})`; + } + } + } catch { + // + } + const idSuffix = name.replace(/^amnezia-/, "") || name; + return { + id: idSuffix, + label: `AmneziaWG${portLabel}`, + container: name, + firstUdp: firstUdp ?? 1 << 30, + }; + }) + .sort((a, b) => a.firstUdp - b.firstUdp || a.container.localeCompare(b.container)) + .map(({ id, label, container }) => ({ id, label, container })); + return JSON.stringify(entries); + } catch { + return ""; + } +} + function privateInstallScriptUrlLogged() { try { const u = new URL(COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL); @@ -1616,6 +1675,13 @@ app.post("/api/community/run-private-install", requireAuth, async (req, res) => const dataHost = HOST_DATA_DIR; const relStage = path.relative(DATA_DIR, tmpDir); const stageInHelper = `/mnt/data/${relStage}`; + const autoAwgProfilesJson = autoDiscoverAwgProfilesJson(); + if (autoAwgProfilesJson) { + fs.appendFileSync( + logAbs, + `→ AWG_PROFILES для PRO собран автоматически из живых amnezia-awg* контейнеров: ${autoAwgProfilesJson}\n`, + ); + } const prelude = COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO ? "sleep 2" : [ @@ -1661,6 +1727,7 @@ exit "\${rv}" "GH_TOKEN", "-e", "GIT_TERMINAL_PROMPT", + ...(autoAwgProfilesJson ? ["-e", "AWG_PROFILES"] : []), helperImage, "sh", "-lc", @@ -1674,6 +1741,7 @@ exit "\${rv}" GITHUB_TOKEN: token, GH_TOKEN: token, GIT_TERMINAL_PROMPT: "0", + ...(autoAwgProfilesJson ? { AWG_PROFILES: autoAwgProfilesJson } : {}), }, }, );