fix: runtime auto-discovery of AmneziaWG container

Tester hit 'No such container: amnezia-awg' after a stock install
on a VPS where Amnezia created the WG container as amnezia-awg2.
Install-time detection was not enough.

The panel now resolves the real container at runtime: if the
configured AWG_CONTAINER lacks the conf file (or does not exist),
it scans running containers for one holding awg0.conf, preferring
amnezia-awg* names. All docker exec paths (clients list/create/
enable/disable/delete, WARP, host-setup) and the API container
field use the resolved name. Clear error lists running containers
if none match.

Result: install -> login -> change password -> create client works
with no manual AWG_CONTAINER/AWG_PROFILES on single-instance setups.
This commit is contained in:
andrey271192
2026-06-16 21:11:36 +03:00
parent e498e98e12
commit f1b7fa522c

View File

@@ -450,7 +450,7 @@ async function dockerRestartContainer(container) {
async function warpFileExists(rt, remotePath) {
try {
await execDocker(["exec", rt.profile.container, "test", "-f", remotePath]);
await execDocker(["exec", await rt.resolveContainer(), "test", "-f", remotePath]);
return true;
} catch {
return false;
@@ -459,7 +459,7 @@ async function warpFileExists(rt, remotePath) {
async function warpInterfaceUp(rt) {
try {
await execDocker(["exec", rt.profile.container, "ip", "addr", "show", "warp"]);
await execDocker(["exec", await rt.resolveContainer(), "ip", "addr", "show", "warp"]);
return true;
} catch {
return false;
@@ -497,7 +497,7 @@ ip route flush table 100 2>/dev/null || true
exit 0
`;
try {
await dockerExecStdin(rt.profile.container, sh);
await dockerExecStdin(await rt.resolveContainer(), sh);
} catch {
/* ignore */
}
@@ -583,7 +583,7 @@ async function warpPatchStartSh(rt, ips) {
"fi",
"",
].join("\n");
await dockerExecStdin(rt.profile.container, remote);
await dockerExecStdin(await rt.resolveContainer(), remote);
}
async function warpPersistAndRestart(rt, selectedIps) {
@@ -680,25 +680,87 @@ function peerUsesWarp(peer, selectedSet) {
return false;
}
async function listRunningContainerNames() {
try {
const { stdout } = await execDocker(["ps", "--format", "{{.Names}}"]);
return stdout.split("\n").map((x) => x.trim()).filter(Boolean);
} catch {
return [];
}
}
async function containerHasFile(name, filePath) {
try {
await execDocker(["exec", name, "test", "-f", filePath]);
return true;
} catch {
return false;
}
}
// Find a running container that actually holds the AmneziaWG config file.
// Lets the panel work even if AWG_CONTAINER points at the wrong name
// (e.g. configured "amnezia-awg" but Amnezia created "amnezia-awg2").
async function discoverAwgContainer(confPath, preferredName) {
const names = await listRunningContainerNames();
const score = (n) => {
if (preferredName && n === preferredName) return 0;
if (/^amnezia-?awg/i.test(n)) return 1;
if (/^amnezia/i.test(n)) return 2;
if (/awg|wireguard|wg/i.test(n)) return 3;
return 4;
};
const ordered = [...names].sort((a, b) => score(a) - score(b));
for (const n of ordered) {
if (await containerHasFile(n, confPath)) return n;
}
return null;
}
function createRuntime(profile) {
const container = profile.container;
let resolvedContainer = null;
const confPath = profile.confPath;
const clientsPath = profile.clientsPath;
const iface = profile.iface;
const wgBinary = profile.wgBinary;
const pskPath = profile.pskPath;
async function resolveContainer() {
if (resolvedContainer) return resolvedContainer;
if (profile.container && (await containerHasFile(profile.container, confPath))) {
resolvedContainer = profile.container;
return resolvedContainer;
}
const found = await discoverAwgContainer(confPath, profile.container);
if (found) {
resolvedContainer = found;
if (found !== profile.container) {
console.log(
`\u2192 AWG container авто: «${found}» (профиль «${profile.label || profile.id}» указывал «${profile.container}»)`,
);
}
return resolvedContainer;
}
const running = (await listRunningContainerNames()).join(", ") || "(нет запущенных)";
throw new Error(
`Контейнер AmneziaWG не найден. Профиль указывает «${profile.container}», но контейнера с таким именем нет и ни в одном запущенном нет файла ${confPath}. Запущенные контейнеры: ${running}. Проверьте, что инстанс Amnezia запущен, или задайте AWG_CONTAINER/AWG_PROFILES.`,
);
}
async function dockerExec(cmd) {
const container = await resolveContainer();
const { stdout, stderr } = await execDocker(["exec", container, "sh", "-c", cmd]);
return stdout + stderr;
}
async function dockerReadFile(remotePath) {
const container = await resolveContainer();
const { stdout } = await execDocker(["exec", container, "cat", remotePath]);
return stdout;
}
async function dockerWriteFile(remotePath, content) {
const container = await resolveContainer();
await execDocker(
[
"exec",
@@ -749,6 +811,7 @@ function createRuntime(profile) {
return {
profile,
resolveContainer,
dockerExec,
dockerReadFile,
dockerWriteFile,
@@ -1785,7 +1848,7 @@ app.post("/api/warp/host-setup", requireAuth, requireProTier, async (req, res) =
return res.status(400).json({ error: "Ожидается cmd: install или uninstall" });
}
const rt = runtimeForRequest(req);
const container = String(rt.profile.container || "").trim();
const container = String((await rt.resolveContainer()) || "").trim();
if (!/^[a-zA-Z0-9_.-]+$/.test(container)) {
return res.status(400).json({ error: "Некорректное имя контейнера в профиле AWG" });
}
@@ -1941,7 +2004,7 @@ app.get("/api/clients", requireAuth, async (req, res) => {
res.json({
profileId: rt.profile.id,
profileLabel: rt.profile.label,
container: rt.profile.container,
container: (await rt.resolveContainer().catch(() => rt.profile.container)),
protocol: "AmneziaWG",
peerCount: conf.peers.length,
clients: rows,