Keep the node's own panel when it joins a core
A server that already works as panel + VPN can now also be a node of another panel without losing its own: `awg-ctrl join` keeps awg-ui running next to the agent and writes NODE_PANEL=on. `--no-panel` stops it as before. Both panels talk to the same awg-ctrl, which stays the source of truth, so they do not get in each other's way. The CLI runs awg-ctrl, awg-ui and awg-agent on such a node, all behind the awg-ctrl health gate; a node installed by install.sh has no UI_PASS and still runs only awg-ctrl + agent. unjoin also drops NODE_PANEL.
This commit is contained in:
+25
-15
@@ -141,7 +141,13 @@ type SvcName = keyof typeof SERVICES;
|
|||||||
// панель без собственного VPN (только awg-ui). Иначе — обычная установка.
|
// панель без собственного VPN (только awg-ui). Иначе — обычная установка.
|
||||||
const IS_NODE = !!process.env.CORE_HOST;
|
const IS_NODE = !!process.env.CORE_HOST;
|
||||||
const NO_LOCAL = (process.env.LOCAL_NODE ?? "on").toLowerCase() === "off";
|
const NO_LOCAL = (process.env.LOCAL_NODE ?? "on").toLowerCase() === "off";
|
||||||
const ALL: SvcName[] = IS_NODE ? ["awgctrl", "agent"] : NO_LOCAL ? ["ui"] : ["awgctrl", "ui"];
|
// Нода со своей панелью: работающий сервер «панель + VPN» подключили к главной панели, не
|
||||||
|
// выключая его собственную (join без --no-panel). Обе панели ходят в один awg-ctrl — он и
|
||||||
|
// есть источник правды, поэтому друг другу они не мешают. У ноды, поставленной install.sh,
|
||||||
|
// панели нет (UI_PASS не задан).
|
||||||
|
const NODE_PANEL = IS_NODE && !!process.env.UI_PASS && (process.env.NODE_PANEL ?? "on").toLowerCase() !== "off";
|
||||||
|
const ALL: SvcName[] = IS_NODE ? (NODE_PANEL ? ["awgctrl", "ui", "agent"] : ["awgctrl", "agent"])
|
||||||
|
: NO_LOCAL ? ["ui"] : ["awgctrl", "ui"];
|
||||||
|
|
||||||
function readPid(name: SvcName): number | null {
|
function readPid(name: SvcName): number | null {
|
||||||
const { pidFile } = SERVICES[name];
|
const { pidFile } = SERVICES[name];
|
||||||
@@ -319,12 +325,12 @@ async function startAll() {
|
|||||||
if (!ALL.includes("awgctrl")) { for (const n of ALL) await start(n); return; }
|
if (!ALL.includes("awgctrl")) { for (const n of ALL) await start(n); return; }
|
||||||
|
|
||||||
await start("awgctrl");
|
await start("awgctrl");
|
||||||
const next: SvcName = IS_NODE ? "agent" : "ui";
|
const rest = ALL.filter(n => n !== "awgctrl");
|
||||||
if (!isRunning("awgctrl") || !(await waitForHealth("awgctrl"))) {
|
if (!isRunning("awgctrl") || !(await waitForHealth("awgctrl"))) {
|
||||||
console.error(` ${grey("■")} awg-ctrl не отвечает — ${SERVICES[next].label} не запущен (см. ${SERVICES.awgctrl.logFile})`);
|
console.error(` ${grey("■")} awg-ctrl не отвечает — ${rest.map(n => SERVICES[n].label).join(", ")} не запущен (см. ${SERVICES.awgctrl.logFile})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await start(next);
|
for (const n of rest) await start(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopAll() { for (const n of ALL) await stop(n); }
|
async function stopAll() { for (const n of ALL) await stop(n); }
|
||||||
@@ -347,11 +353,12 @@ function restartFresh(): void {
|
|||||||
spawnSync(process.execPath, [...process.execArgv, process.argv[1], "restart"], { stdio: "inherit" });
|
spawnSync(process.execPath, [...process.execArgv, process.argv[1], "restart"], { stdio: "inherit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// `join <awgjoin://…>`: превратить эту установку в ноду. Панель (awg-ui) на этой машине
|
// `join <awgjoin://…> [--no-panel]`: подключить эту установку к главной панели как ноду. Рядом
|
||||||
// останавливается, вместо неё запускается агент; awg-ctrl и пользователи остаются как были.
|
// запускается агент; awg-ctrl и пользователи остаются как были. Своя панель (awg-ui) продолжает
|
||||||
async function joinCore(link?: string) {
|
// работать, если она здесь есть, — с --no-panel она останавливается.
|
||||||
if (!link?.startsWith("awgjoin://")) {
|
async function joinCore(link?: string, flag?: string) {
|
||||||
console.error(" Usage: awg-ctrl join <awgjoin://…> (ссылку даёт панель: «Добавить сервер»)");
|
if (!link?.startsWith("awgjoin://") || (flag !== undefined && flag !== "--no-panel")) {
|
||||||
|
console.error(" Usage: awg-ctrl join <awgjoin://…> [--no-panel] (ссылку даёт панель: «Добавить сервер»)");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
let j: { v?: number; h?: string; p?: number; pin?: string; n?: number; s?: string };
|
let j: { v?: number; h?: string; p?: number; pin?: string; n?: number; s?: string };
|
||||||
@@ -364,15 +371,18 @@ async function joinCore(link?: string) {
|
|||||||
if (!ok) { console.error(" Ссылка неверного формата или от другой версии панели"); process.exit(1); }
|
if (!ok) { console.error(" Ссылка неверного формата или от другой версии панели"); process.exit(1); }
|
||||||
if (IS_NODE && !await confirmYes(` Эта нода уже подключена к ${process.env.CORE_HOST}. Перепривязать?`)) return;
|
if (IS_NODE && !await confirmYes(` Эта нода уже подключена к ${process.env.CORE_HOST}. Перепривязать?`)) return;
|
||||||
|
|
||||||
|
const keepPanel = flag !== "--no-panel" && !!process.env.UI_PASS;
|
||||||
let content = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
let content = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
||||||
const set: Record<string, string> = {
|
const set: Record<string, string> = {
|
||||||
CORE_HOST: j.h!, CORE_PORT: String(j.p), CORE_PIN: j.pin!, NODE_ID: String(j.n), JOIN_SECRET: j.s!,
|
CORE_HOST: j.h!, CORE_PORT: String(j.p), CORE_PIN: j.pin!, NODE_ID: String(j.n), JOIN_SECRET: j.s!,
|
||||||
|
NODE_PANEL: keepPanel ? "on" : "off",
|
||||||
};
|
};
|
||||||
for (const [k, v] of Object.entries(set)) content = updateEnvVar(content, k, v);
|
for (const [k, v] of Object.entries(set)) content = updateEnvVar(content, k, v);
|
||||||
writeFileSync(envFile, content, { mode: 0o600 });
|
writeFileSync(envFile, content, { mode: 0o600 });
|
||||||
|
|
||||||
await stop("ui"); // панель на ноде не нужна
|
if (!keepPanel) await stop("ui");
|
||||||
console.log(`\n ${green("✓")} Настроено: core ${j.h}:${j.p}, нода #${j.n}`);
|
console.log(`\n ${green("✓")} Настроено: core ${j.h}:${j.p}, нода #${j.n}` +
|
||||||
|
(keepPanel ? " · своя панель продолжает работать" : process.env.UI_PASS ? " · своя панель остановлена" : ""));
|
||||||
restartFresh();
|
restartFresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +390,7 @@ async function unjoinCore() {
|
|||||||
if (!IS_NODE) { console.log(" Эта установка не подключена к core"); return; }
|
if (!IS_NODE) { console.log(" Эта установка не подключена к core"); return; }
|
||||||
await stop("agent");
|
await stop("agent");
|
||||||
let content = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
let content = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
||||||
for (const k of ["CORE_HOST", "CORE_PORT", "CORE_PIN", "NODE_ID", "JOIN_SECRET"]) content = removeEnvVar(content, k);
|
for (const k of ["CORE_HOST", "CORE_PORT", "CORE_PIN", "NODE_ID", "JOIN_SECRET", "NODE_PANEL"]) content = removeEnvVar(content, k);
|
||||||
writeFileSync(envFile, content, { mode: 0o600 });
|
writeFileSync(envFile, content, { mode: 0o600 });
|
||||||
console.log(` ${green("✓")} Нода отвязана. Удалите её в панели (сервер) и запустите: awg-ctrl restart`);
|
console.log(` ${green("✓")} Нода отвязана. Удалите её в панели (сервер) и запустите: awg-ctrl restart`);
|
||||||
// Нода, поставленная install.sh с ролью «нода», панели не имеет — без пароля в неё не войти.
|
// Нода, поставленная install.sh с ролью «нода», панели не имеет — без пароля в неё не войти.
|
||||||
@@ -489,9 +499,9 @@ async function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const USAGE = "Usage: cli <start|stop|restart|status|credentials [user|pass]|join <awgjoin://…>|unjoin>";
|
const USAGE = "Usage: cli <start|stop|restart|status|credentials [user|pass]|join <awgjoin://…> [--no-panel]|unjoin>";
|
||||||
|
|
||||||
const [,,, sub] = process.argv;
|
const [,,, sub, arg] = process.argv;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "start": await startAll(); break;
|
case "start": await startAll(); break;
|
||||||
@@ -503,7 +513,7 @@ async function main() {
|
|||||||
else if (sub === "pass") await setCredentials("pass");
|
else if (sub === "pass") await setCredentials("pass");
|
||||||
else await setCredentials();
|
else await setCredentials();
|
||||||
break;
|
break;
|
||||||
case "join": await joinCore(sub); break;
|
case "join": await joinCore(sub, arg); break;
|
||||||
case "unjoin": await unjoinCore(); break;
|
case "unjoin": await unjoinCore(); break;
|
||||||
default: console.log(USAGE); process.exit(1);
|
default: console.log(USAGE); process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user