mirror of
https://github.com/andrey271192/amnezia_web.git
synced 2026-09-20 14:41:59 +00:00
fix(community): PRO install from UI uses docker-cli helper to free :8080
Run install.sh in a disposable docker:26-cli container after docker rm FREE/landing/stale PRO; keeps working after API response. Legacy in-panel bash retained when COMMUNITY_DISABLE_DOCKER_CLI_HELPER=1. Persist related env vars in install.sh from previous container. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -48,6 +48,10 @@ cd /opt/amnezia-admin && chmod +x scripts/install.sh && sudo SKIP_DOWNLOAD=1 bas
|
||||
| `COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL` | `https://raw.githubusercontent.com/andrey271192/amnezia_web-pro/main/scripts/install.sh` | Сырой URL `scripts/install.sh` в **вашем** приватном репозитории PRO |
|
||||
| `COMMUNITY_INSTALL_FETCH_MS` | `60000` | Таймаут HTTP при скачивании установщика из GitHub |
|
||||
| `PRIVATE_INSTALL_SCRIPT_MAX_BYTES` | `2097152` | Максимальный размер скачанного скрипта (байты) |
|
||||
| `COMMUNITY_DISABLE_DOCKER_CLI_HELPER` | _(не задано)_ | Если `1`, установка из UI снова только «bash внутри панели» (часто конфликт **:8080** с PRO). По умолчанию используется одноразовый контейнер **docker:cli**, который **снимает FREE** и зависший PRO перед `install.sh`. |
|
||||
| `COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO` | `0` | `1` — не выполнять `docker rm` FREE/лендинга перед install (если знаете, что делаете). |
|
||||
| `COMMUNITY_PRO_INSTALL_HELPER_IMAGE` | `docker:26-cli` | Образ с бинарём `docker` для фонового контейнера (нужен доступ к демону по сокету). |
|
||||
| `FREE_PANEL_CONTAINER_FOR_PRO_INSTALL` и др. | см. `server.js` | Имена контейнеров для `docker rm` перед install (по умолчанию `amnezia-admin`, `amnezia-web-landing`, `amnezia-admin-pro`). |
|
||||
|
||||
Чтобы включить форму установки из панели, задайте в контейнере **`AMNEZIA_EDITION=community`** и **`ALLOW_COMMUNITY_GITHUB_ACTIVATION=1`** (`install.sh`: добавьте `-e ALLOW_COMMUNITY_GITHUB_ACTIVATION=1` или пересоздайте контейнер после правки переменных). URL приватного `install.sh` при необходимости переопределите через **`COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL`**.
|
||||
|
||||
@@ -90,7 +94,7 @@ curl -fsSL https://raw.githubusercontent.com/andrey271192/amnezia_web/main/scrip
|
||||
|
||||
### `Bind for 0.0.0.0:8080 failed: port is already allocated` у `amnezia-admin-pro`
|
||||
|
||||
На **8080** уже слушает **FREE**‑панель (**`amnezia-admin`**). Перед запуском образа PRO (**`docker compose`**, **`amnezia-web-pro-deploy`**, свой скрипт): **`docker rm -f amnezia-admin`**, затем снова **`up`**. Два контейнера панели на один и тот же порт не поднять.
|
||||
На **8080** уже слушает **FREE**‑панель (**`amnezia-admin`**). Пока она работает, второй контейнер панели (PRO) на тот же порт не поднимется. **Кнопка «Установить PRO» из UI по умолчанию** использует одноразовый контейнер **`docker:cli`** (пауза → **`docker rm`** FREE / лендинг / возможный **`amnezia-admin-pro` в Created** → затем ваш **`install.sh`**). Иначе вручную: **`docker rm -f amnezia-admin`**, затем установщик PRO. Отключить авто-снос: **`COMMUNITY_DISABLE_DOCKER_CLI_HELPER=1`**.
|
||||
|
||||
### Лендинг: порт 80 занят
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amnezia-admin",
|
||||
"version": "1.1.9",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"description": "amnezia_web — базовая панель AmneziaWG (FREE: просмотр и удаление клиентов; полная версия — PRO).",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -125,6 +125,8 @@ fi
|
||||
for __reuse_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE WARP_SSH_INSTALL_DIR \
|
||||
ALLOW_COMMUNITY_GITHUB_ACTIVATION COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL COMMUNITY_INSTALL_FETCH_MS PRIVATE_INSTALL_SCRIPT_MAX_BYTES \
|
||||
COMMUNITY_UPGRADE_URL COMMUNITY_UPGRADE_PITCH AMNEZIA_EDITION \
|
||||
COMMUNITY_DISABLE_DOCKER_CLI_HELPER COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO COMMUNITY_PRO_INSTALL_HELPER_IMAGE \
|
||||
FREE_PANEL_CONTAINER_FOR_PRO_INSTALL FREE_LANDING_CONTAINER_FOR_PRO_INSTALL STALE_PRO_PANEL_CONTAINER_FOR_PRO_INSTALL \
|
||||
EXPORT_CONFIG_SECRET CLIENT_CONFIG_ENDPOINT CLIENT_EXPORT_DNS1 CLIENT_EXPORT_DNS2; do
|
||||
if [[ -z "${!__reuse_var:-}" ]] && [[ -n "${PREV_CONTAINER_ENV}" ]]; then
|
||||
PREV_VAL=""
|
||||
@@ -200,7 +202,7 @@ elif [[ -f "${INSTALL_DIR}/.amnezia-panel-edition" ]]; then
|
||||
echo "→ AMNEZIA_EDITION из ${INSTALL_DIR}/.amnezia-panel-edition: ${__PE}"
|
||||
fi
|
||||
fi
|
||||
for __ce_var in COMMUNITY_UPGRADE_URL COMMUNITY_UPGRADE_PITCH ALLOW_COMMUNITY_GITHUB_ACTIVATION COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL COMMUNITY_INSTALL_FETCH_MS PRIVATE_INSTALL_SCRIPT_MAX_BYTES; do
|
||||
for __ce_var in COMMUNITY_UPGRADE_URL COMMUNITY_UPGRADE_PITCH ALLOW_COMMUNITY_GITHUB_ACTIVATION COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL COMMUNITY_INSTALL_FETCH_MS PRIVATE_INSTALL_SCRIPT_MAX_BYTES COMMUNITY_DISABLE_DOCKER_CLI_HELPER COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO COMMUNITY_PRO_INSTALL_HELPER_IMAGE FREE_PANEL_CONTAINER_FOR_PRO_INSTALL FREE_LANDING_CONTAINER_FOR_PRO_INSTALL STALE_PRO_PANEL_CONTAINER_FOR_PRO_INSTALL; do
|
||||
if [[ -n "${!__ce_var:-}" ]]; then
|
||||
RUN_ENV+=( -e "${__ce_var}=${!__ce_var}" )
|
||||
fi
|
||||
|
||||
131
server.js
131
server.js
@@ -47,6 +47,10 @@ const COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL =
|
||||
const PRIVATE_INSTALL_SCRIPT_MAX_BYTES = Number(process.env.PRIVATE_INSTALL_SCRIPT_MAX_BYTES || 2_097_152) || 2_097_152;
|
||||
/** Флаг выполнения одноразового bash install из UI (держим второй параллельный запрос). */
|
||||
let communityPrivateInstallBusy = false;
|
||||
const COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO = envTruthy(
|
||||
process.env.COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO,
|
||||
);
|
||||
const COMMUNITY_DISABLE_DOCKER_CLI_HELPER = envTruthy(process.env.COMMUNITY_DISABLE_DOCKER_CLI_HELPER);
|
||||
const COMMUNITY_UPGRADE_URL =
|
||||
process.env.COMMUNITY_UPGRADE_URL?.trim() ||
|
||||
"https://boosty.to/andrey27/purchase/3906453?ssource=DIRECT&share=subscription_link";
|
||||
@@ -367,6 +371,13 @@ function validateGithubBearerToken(tok) {
|
||||
return /^[!-~]+$/.test(s);
|
||||
}
|
||||
|
||||
/** Имя контейнера для встраивания в `sh -lc` во внешнем helper (docker rm -f …). */
|
||||
function shellSafeDockerContainerName(raw, fallback) {
|
||||
const s = typeof raw === "string" ? raw.trim() : "";
|
||||
if (/^[a-zA-Z][a-zA-Z0-9_.-]{0,127}$/.test(s)) return s;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function privateInstallScriptUrlLogged() {
|
||||
try {
|
||||
const u = new URL(COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL);
|
||||
@@ -1474,21 +1485,35 @@ app.post("/api/community/run-private-install", requireAuth, async (req, res) =>
|
||||
const header = `\n${"=".repeat(60)}\n${new Date().toISOString()} — старт install из UI (COMMUNITY)\nURL: ${COMMUNITY_PRIVATE_INSTALL_SCRIPT_URL}\n${"=".repeat(60)}\n`;
|
||||
fs.appendFileSync(logAbs, header);
|
||||
|
||||
const logStream = fs.createWriteStream(logAbs, { flags: "a" });
|
||||
const useDockerCliHelper = !COMMUNITY_DISABLE_DOCKER_CLI_HELPER;
|
||||
const helperImage =
|
||||
process.env.COMMUNITY_PRO_INSTALL_HELPER_IMAGE?.trim() || "docker:26-cli";
|
||||
const freePanelName = shellSafeDockerContainerName(
|
||||
process.env.FREE_PANEL_CONTAINER_FOR_PRO_INSTALL,
|
||||
"amnezia-admin",
|
||||
);
|
||||
const freeLandingName = shellSafeDockerContainerName(
|
||||
process.env.FREE_LANDING_CONTAINER_FOR_PRO_INSTALL,
|
||||
"amnezia-web-landing",
|
||||
);
|
||||
const staleProName = shellSafeDockerContainerName(
|
||||
process.env.STALE_PRO_PANEL_CONTAINER_FOR_PRO_INSTALL,
|
||||
"amnezia-admin-pro",
|
||||
);
|
||||
|
||||
const child = spawn("bash", [scriptPath], {
|
||||
cwd: tmpDir,
|
||||
env: {
|
||||
...process.env,
|
||||
GITHUB_TOKEN: token,
|
||||
GH_TOKEN: token,
|
||||
GIT_TERMINAL_PROMPT: "0",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
if (useDockerCliHelper) {
|
||||
fs.appendFileSync(
|
||||
logAbs,
|
||||
`\n→ Одноразовый установщик: ${helperImage}; FREE=${freePanelName}, лендинг=${freeLandingName}, удаление зависшего PRO=${staleProName}. Отключить слой: COMMUNITY_DISABLE_DOCKER_CLI_HELPER=1\n`,
|
||||
);
|
||||
} else {
|
||||
fs.appendFileSync(
|
||||
logAbs,
|
||||
"\n→ COMMUNITY_DISABLE_DOCKER_CLI_HELPER=1 — install идёт в том же контейнере; FREE не удаляется до скрипта (частый конфликт :8080 с PRO).\n",
|
||||
);
|
||||
}
|
||||
|
||||
child.stdout.pipe(logStream, { end: false });
|
||||
child.stderr.pipe(logStream, { end: false });
|
||||
const logStream = useDockerCliHelper ? null : fs.createWriteStream(logAbs, { flags: "a" });
|
||||
|
||||
let finalized = false;
|
||||
const finalize = (code = null, signal = null, errText = "") => {
|
||||
@@ -1503,26 +1528,88 @@ app.post("/api/community/run-private-install", requireAuth, async (req, res) =>
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
logStream.end(() => {
|
||||
const rmTmp = () => {
|
||||
try {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
});
|
||||
};
|
||||
if (logStream) {
|
||||
logStream.end(() => rmTmp());
|
||||
} else {
|
||||
rmTmp();
|
||||
}
|
||||
};
|
||||
|
||||
child.once("exit", (code, signal) => finalize(code, signal));
|
||||
child.once("error", (err) => {
|
||||
console.warn("community-private-install bash:", err?.message || err);
|
||||
finalize(null, null, String(err.message || err));
|
||||
});
|
||||
if (useDockerCliHelper) {
|
||||
const dataAbs = path.resolve(DATA_DIR);
|
||||
const prelude = COMMUNITY_SKIP_REMOVE_FREE_BEFORE_PRIVATE_PRO
|
||||
? "sleep 2"
|
||||
: `sleep 3; docker rm -f ${freePanelName} ${freeLandingName} 2>/dev/null || true; docker rm -f ${staleProName} 2>/dev/null || true`;
|
||||
const sh = `
|
||||
set -eu
|
||||
${prelude}
|
||||
cd /mnt/stage && exec bash ./install.sh >> /mnt/data/community-install-last.log 2>&1
|
||||
`.trim();
|
||||
|
||||
const dr = spawn(
|
||||
"docker",
|
||||
[
|
||||
"run",
|
||||
"--rm",
|
||||
"--pull=missing",
|
||||
"-v",
|
||||
"/var/run/docker.sock:/var/run/docker.sock",
|
||||
`-v`,
|
||||
`${tmpDir}:/mnt/stage:ro`,
|
||||
`-v`,
|
||||
`${dataAbs}:/mnt/data`,
|
||||
"-e",
|
||||
`GITHUB_TOKEN=${token}`,
|
||||
"-e",
|
||||
`GH_TOKEN=${token}`,
|
||||
"-e",
|
||||
"GIT_TERMINAL_PROMPT=0",
|
||||
helperImage,
|
||||
"sh",
|
||||
"-lc",
|
||||
sh,
|
||||
],
|
||||
{ detached: true, stdio: "ignore" },
|
||||
);
|
||||
dr.unref();
|
||||
dr.once("exit", (code, signal) => finalize(code, signal));
|
||||
dr.once("error", (err) => {
|
||||
console.warn("community-private-install docker-cli helper:", err?.message || err);
|
||||
finalize(null, null, String(err.message || err));
|
||||
});
|
||||
} else {
|
||||
const child = spawn("bash", [scriptPath], {
|
||||
cwd: tmpDir,
|
||||
env: {
|
||||
...process.env,
|
||||
GITHUB_TOKEN: token,
|
||||
GH_TOKEN: token,
|
||||
GIT_TERMINAL_PROMPT: "0",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
child.stdout.pipe(logStream, { end: false });
|
||||
child.stderr.pipe(logStream, { end: false });
|
||||
child.once("exit", (code, signal) => finalize(code, signal));
|
||||
child.once("error", (err) => {
|
||||
console.warn("community-private-install bash:", err?.message || err);
|
||||
finalize(null, null, String(err.message || err));
|
||||
});
|
||||
}
|
||||
|
||||
res.status(202).json({
|
||||
ok: true,
|
||||
queued: true,
|
||||
message:
|
||||
"Установка PRO запущена. Панель может прерваться — через 2–5 минут откройте её снова по тому же адресу. Журнал пишется в /data/community-install-last.log в том контейнере, где была FREE-панель (до возможной замены образа).",
|
||||
message: useDockerCliHelper
|
||||
? "Установка PRO: отдельный контейнер сначала снимает FREE (освобождается порт панели) и зависший PRO при наличии, затем выполняется install.sh. Подождите 3–10 мин и откройте адрес снова. Журнал: на VPS каталог данных панели (часто /opt/amnezia-admin-data/community-install-last.log)."
|
||||
: "Режим без docker-helper: при «port already allocated» снимите FREE вручную. Журнал: /data/community-install-last.log.",
|
||||
logInsideContainer: "community-install-last.log",
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user