mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-09-20 14:42:00 +00:00
feat(warp): host install/uninstall via panel SSH + clarify status
POST /api/warp/host-setup runs warp-amnezia.sh on VPS root SSH. UI buttons when sshpass available; explain missing warp.conf status. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -62,6 +62,7 @@ cd /opt/amnezia-admin && chmod +x scripts/install.sh && sudo SKIP_DOWNLOAD=1 bas
|
|||||||
| `WARP_CONF_PATH` | `{WARP_DIR}/warp.conf` | Нестандартный путь к конфигу WARP |
|
| `WARP_CONF_PATH` | `{WARP_DIR}/warp.conf` | Нестандартный путь к конфигу WARP |
|
||||||
| `WARP_CLIENTS_LIST` | `{WARP_DIR}/clients.list` | Список строк `10.8.x.x/32` — кому маршрутизировать трафик через интерфейс `warp` |
|
| `WARP_CLIENTS_LIST` | `{WARP_DIR}/clients.list` | Список строк `10.8.x.x/32` — кому маршрутизировать трафик через интерфейс `warp` |
|
||||||
| `AMNEZIA_START_SCRIPT` | `/opt/amnezia/start.sh` | Куда встраивается блок автоподъёма WARP после перезапуска (маркеры совместимы с прежним `warp-manager`) |
|
| `AMNEZIA_START_SCRIPT` | `/opt/amnezia/start.sh` | Куда встраивается блок автоподъёма WARP после перезапуска (маркеры совместимы с прежним `warp-manager`) |
|
||||||
|
| `WARP_SSH_INSTALL_DIR` | `/opt/amnezia-admin` | Каталог на **хосте VPS**, откуда по SSH выполняется `scripts/warp-amnezia.sh` при установке/удалении WARP из панели (`POST /api/warp/host-setup`) |
|
||||||
| `SCHEDULE_DISCONNECT_MS` | `60000` | Как часто планировщик проверяет отложенное отключение из туннеля (мс) |
|
| `SCHEDULE_DISCONNECT_MS` | `60000` | Как часто планировщик проверяет отложенное отключение из туннеля (мс) |
|
||||||
| `TZ` | _(часто UTC в Docker)_ | Пояс строки «Сервер» в панели (IANA, например `Europe/Berlin`). Без `TZ` берётся из образа (часто UTC) — тогда от браузера будет видна разница часов |
|
| `TZ` | _(часто UTC в Docker)_ | Пояс строки «Сервер» в панели (IANA, например `Europe/Berlin`). Без `TZ` берётся из образа (часто UTC) — тогда от браузера будет видна разница часов |
|
||||||
| `TIME_SYNC_SSH_HOST` | `172.17.0.1` | Хост для SSH root при синхронизации времени из панели (часто шлюз Docker к хосту) |
|
| `TIME_SYNC_SSH_HOST` | `172.17.0.1` | Хост для SSH root при синхронизации времени из панели (часто шлюз Docker к хосту) |
|
||||||
@@ -108,10 +109,12 @@ curl -fsSL https://raw.githubusercontent.com/andrey271192/Amnezia_web/main/scrip
|
|||||||
|
|
||||||
### Cloudflare WARP (необязательно)
|
### Cloudflare WARP (необязательно)
|
||||||
|
|
||||||
**Устанавливать не обязательно.** Панель и обычный AmneziaWG работают без WARP. Раздел **Cloudflare WARP** в веб-интерфейсе нужен только если вы хотите, чтобы **выбранные** клиенты (с IPv4 `10.8.x.x/32` в AllowedIPs) выходили в интернет через туннель Cloudflare внутри контейнера AWG.
|
**Устанавливать не обязательно.** Панель и обычный AmneziaWG работают без WARP. Статус **«Не установлен»** значит: в контейнере AWG ещё **нет** `warp.conf` (скрипт на VPS не запускали) — это не ошибка.
|
||||||
|
|
||||||
|
Раздел **Cloudflare WARP** в веб-интерфейсе нужен только если вы хотите, чтобы **выбранные** клиенты (с IPv4 `10.8.x.x/32` в AllowedIPs) выходили в интернет через туннель Cloudflare внутри контейнера AWG.
|
||||||
|
|
||||||
- **Не нужен WARP** — ничего на хосте не запускайте. В панели будет статус **«Не установлен»** — это нормально, блок можно не трогать.
|
- **Не нужен WARP** — ничего на хосте не запускайте. В панели будет статус **«Не установлен»** — это нормально, блок можно не трогать.
|
||||||
- **Нужен WARP** — один раз на хосте VPS (root), из каталога с репозиторием (часто `/opt/amnezia-admin`):
|
- **Нужен WARP** — в панели кнопка **«Установить WARP на VPS»** (пароль root по SSH, как у синхронизации времени хоста; нужны `sshpass` в образе панели и не отключённый `TIME_SYNC_DISABLED`) **или** один раз на хосте VPS (root), из каталога репозитория (часто `/opt/amnezia-admin`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/amnezia-admin
|
cd /opt/amnezia-admin
|
||||||
@@ -128,7 +131,7 @@ chmod +x scripts/warp-amnezia.sh
|
|||||||
|
|
||||||
Подкоманды: `install`, `start`, `stop`, `status`, `rekey`, **`uninstall`**. Учёт wgcf на хосте: `/root/wgcf-account.toml` (и бинарник `/root/wgcf`, если скачан).
|
Подкоманды: `install`, `start`, `stop`, `status`, `rekey`, **`uninstall`**. Учёт wgcf на хосте: `/root/wgcf-account.toml` (и бинарник `/root/wgcf`, если скачан).
|
||||||
|
|
||||||
**Полностью убрать WARP** после установки (интерфейс `warp`, правила маршрутизации/NAT, блок `# --- WARP-MANAGER ---` в `start.sh`, `warp.conf` и список клиентов в каталоге WARP в контейнере; затем перезапуск контейнера AWG):
|
**Полностью убрать WARP** — кнопка **«Удалить WARP с VPS»** в веб-панели (SSH с паролем root, как при установке) **или** на хосте вручную (интерфейс `warp`, правила маршрутизации/NAT, блок `# --- WARP-MANAGER ---` в `start.sh`, `warp.conf` и список клиентов в каталоге WARP в контейнере; затем перезапуск контейнера AWG):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/amnezia-admin
|
cd /opt/amnezia-admin
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
| POST | `/api/clients/disconnect-date` | Даты отключения / расписание |
|
| POST | `/api/clients/disconnect-date` | Даты отключения / расписание |
|
||||||
| GET/POST | `/api/clients/export-config` | Скачать `.conf`; GET — прямая ссылка (сессия); опционально `?token=…` если задан `EXPORT_CONFIG_SECRET` |
|
| GET/POST | `/api/clients/export-config` | Скачать `.conf`; GET — прямая ссылка (сессия); опционально `?token=…` если задан `EXPORT_CONFIG_SECRET` |
|
||||||
| POST | `/api/clients/create-cascade` | `{ "endpointHost", "endpointPort?", "tunnelIp?", "clientName?", "profileId?" }` — новый peer и файл `.conf` |
|
| POST | `/api/clients/create-cascade` | `{ "endpointHost", "endpointPort?", "tunnelIp?", "clientName?", "profileId?" }` — новый peer и файл `.conf` |
|
||||||
|
| POST | `/api/warp/host-setup` | Установка/удаление WARP на хосте по SSH: `{ "rootPassword", "cmd": "install" \| "uninstall" }` (как синхронизация времени; каталог скрипта — `WARP_SSH_INSTALL_DIR`) |
|
||||||
| POST | `/api/warp/start` | Поднять WARP |
|
| POST | `/api/warp/start` | Поднять WARP |
|
||||||
| POST | `/api/warp/stop` | Остановить WARP |
|
| POST | `/api/warp/stop` | Остановить WARP |
|
||||||
| POST | `/api/warp/routing` | Политика по клиентам |
|
| POST | `/api/warp/routing` | Политика по клиентам |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.1.6",
|
"version": "1.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Web-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)",
|
"description": "Web-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ const dtExtra = document.querySelector("#dt-dialog-extra");
|
|||||||
const dtHint = document.querySelector("#dt-dialog-hint");
|
const dtHint = document.querySelector("#dt-dialog-hint");
|
||||||
const dtScheduleTunnel = document.querySelector("#dt-dialog-schedule-tunnel");
|
const dtScheduleTunnel = document.querySelector("#dt-dialog-schedule-tunnel");
|
||||||
|
|
||||||
|
const warpSshDialog = document.querySelector("#warp-ssh-dialog");
|
||||||
|
const warpSshTitle = document.querySelector("#warp-ssh-title");
|
||||||
|
const warpSshLead = document.querySelector("#warp-ssh-lead");
|
||||||
|
const warpSshPw = document.querySelector("#warp-ssh-pw");
|
||||||
|
const warpSshCancel = document.querySelector("#warp-ssh-cancel");
|
||||||
|
const warpSshOk = document.querySelector("#warp-ssh-ok");
|
||||||
|
const warpSshErr = document.querySelector("#warp-ssh-err");
|
||||||
|
/** @type {"install" | "uninstall" | null} */
|
||||||
|
let warpSshPendingCmd = null;
|
||||||
|
|
||||||
/** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */
|
/** Какие панели скрыты настройкой сервера (`UI_HIDE_SECTIONS`). */
|
||||||
let uiHidden = { users: false, warp: false, cascade: false };
|
let uiHidden = { users: false, warp: false, cascade: false };
|
||||||
|
|
||||||
@@ -165,6 +175,61 @@ dtOk.addEventListener("click", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function openWarpHostSetup(cmd) {
|
||||||
|
if (!warpSshDialog || !warpSshTitle || !warpSshLead || !warpSshPw || !warpSshOk || !warpSshErr) return;
|
||||||
|
warpSshPendingCmd = cmd;
|
||||||
|
warpSshErr.textContent = "";
|
||||||
|
warpSshPw.value = "";
|
||||||
|
if (cmd === "install") {
|
||||||
|
warpSshTitle.textContent = "Установить Cloudflare WARP";
|
||||||
|
warpSshLead.textContent =
|
||||||
|
"На хосте VPS выполнится scripts/warp-amnezia.sh install для контейнера текущего инстанса. SSH так же, как у блока синхронизации времени (TIME_SYNC_SSH_HOST, часто 172.17.0.1). Пароль root не сохраняется.";
|
||||||
|
warpSshOk.textContent = "Установить";
|
||||||
|
} else {
|
||||||
|
warpSshTitle.textContent = "Удалить Cloudflare WARP";
|
||||||
|
warpSshLead.textContent =
|
||||||
|
"На хосте выполнится scripts/warp-amnezia.sh uninstall (интерфейс warp, правила, автозапуск в start.sh; контейнер AWG перезапустится).";
|
||||||
|
warpSshOk.textContent = "Удалить";
|
||||||
|
}
|
||||||
|
warpSshDialog.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warpSshCancel && warpSshDialog) {
|
||||||
|
warpSshCancel.addEventListener("click", () => {
|
||||||
|
warpSshDialog.close();
|
||||||
|
warpSshPendingCmd = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warpSshOk && warpSshDialog && warpSshPw) {
|
||||||
|
warpSshOk.addEventListener("click", async () => {
|
||||||
|
const cmd = warpSshPendingCmd;
|
||||||
|
if (!cmd) return;
|
||||||
|
const pw = warpSshPw.value;
|
||||||
|
if (!String(pw).trim()) {
|
||||||
|
warpSshErr.textContent = "Введите пароль root.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warpSshErr.textContent = "";
|
||||||
|
try {
|
||||||
|
setStatus(cmd === "install" ? "Устанавливаю WARP на хосте VPS…" : "Удаляю WARP на хосте VPS…", false);
|
||||||
|
await api("/api/warp/host-setup", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ rootPassword: pw, cmd }),
|
||||||
|
});
|
||||||
|
warpSshDialog.close();
|
||||||
|
warpSshPendingCmd = null;
|
||||||
|
warpSshPw.value = "";
|
||||||
|
setStatus("Готово.", false);
|
||||||
|
await loadClients();
|
||||||
|
} catch (e) {
|
||||||
|
const msg = String(e.message || e);
|
||||||
|
warpSshErr.textContent = msg;
|
||||||
|
setStatus(msg, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showLogin() {
|
function showLogin() {
|
||||||
stopClocks();
|
stopClocks();
|
||||||
loginGate.classList.remove("hidden");
|
loginGate.classList.remove("hidden");
|
||||||
@@ -661,15 +726,40 @@ function renderWarpPanel(data) {
|
|||||||
|
|
||||||
if (!w.installed) {
|
if (!w.installed) {
|
||||||
warpStatusLine.textContent = "Не установлен";
|
warpStatusLine.textContent = "Не установлен";
|
||||||
|
const explain = document.createElement("p");
|
||||||
|
explain.className = "muted warp-muted";
|
||||||
|
explain.textContent =
|
||||||
|
"Так и должно быть, пока на VPS не создан файл warp.conf внутри контейнера AWG. После установки статус сменится; без WARP обычный AmneziaWG уже работает.";
|
||||||
|
warpActionsEl.appendChild(explain);
|
||||||
|
|
||||||
|
if (w.hostSshInstall) {
|
||||||
|
warpActionsEl.appendChild(
|
||||||
|
btn("Установить WARP на VPS", "btn small primary", () => openWarpHostSetup("install")),
|
||||||
|
);
|
||||||
|
const sshHint = document.createElement("p");
|
||||||
|
sshHint.className = "muted warp-muted";
|
||||||
|
sshHint.textContent =
|
||||||
|
"Кнопка запускает на хосте тот же скрипт, что в README; понадобится пароль root по SSH (не сохраняется). Альтернатива — команда вручную по SSH.";
|
||||||
|
warpActionsEl.appendChild(sshHint);
|
||||||
|
}
|
||||||
|
|
||||||
const hint = document.createElement("p");
|
const hint = document.createElement("p");
|
||||||
hint.className = "muted warp-muted";
|
hint.className = "muted warp-muted";
|
||||||
hint.innerHTML =
|
hint.innerHTML =
|
||||||
"Один раз на хосте (root): <code class=\"inline\">bash scripts/warp-amnezia.sh install</code> — из каталога клона репозитория на VPS. Если контейнер не угадан автоматически: <code class=\"inline\">bash scripts/warp-amnezia.sh install amnezia-awg2</code>.";
|
"Вручную на хосте (root), из каталога репозитория: <code class=\"inline\">bash scripts/warp-amnezia.sh install</code> или с именем контейнера: <code class=\"inline\">bash scripts/warp-amnezia.sh install amnezia-awg2</code>.";
|
||||||
warpActionsEl.appendChild(hint);
|
warpActionsEl.appendChild(hint);
|
||||||
|
|
||||||
|
if (!w.hostSshInstall) {
|
||||||
|
const noBtn = document.createElement("p");
|
||||||
|
noBtn.className = "muted warp-muted";
|
||||||
|
noBtn.textContent =
|
||||||
|
"Кнопка установки с панели недоступна: нет sshpass в образе панели или включено TIME_SYNC_DISABLED=1 — используйте SSH вручную.";
|
||||||
|
warpActionsEl.appendChild(noBtn);
|
||||||
|
}
|
||||||
|
|
||||||
const skip = document.createElement("p");
|
const skip = document.createElement("p");
|
||||||
skip.className = "muted warp-muted";
|
skip.className = "muted warp-muted";
|
||||||
skip.textContent =
|
skip.textContent = "Если выход через Cloudflare не нужен, ничего не нажимайте — VPN уже работает без этого блока.";
|
||||||
"Если Cloudflare WARP не нужен, ничего не выполняйте — AmneziaWG и панель работают без него. Полное удаление: bash scripts/warp-amnezia.sh uninstall (см. README).";
|
|
||||||
warpActionsEl.appendChild(skip);
|
warpActionsEl.appendChild(skip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -771,6 +861,9 @@ function renderWarpPanel(data) {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
warpActionsEl.appendChild(
|
||||||
|
btn("Удалить WARP с VPS", "btn small warn", () => openWarpHostSetup("uninstall")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(s) {
|
function escapeHtml(s) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<strong>Необязательно.</strong> Если достаточно обычного AmneziaWG без выхода части клиентов через Cloudflare — раздел можно не трогать (статус «Не установлен» — норма).
|
<strong>Необязательно.</strong> Если достаточно обычного AmneziaWG без выхода части клиентов через Cloudflare — раздел можно не трогать (статус «Не установлен» — норма).
|
||||||
Если WARP нужен: выход в интернет через Cloudflare для <em>выбранных</em> клиентов (правила маршрутизации и NAT в контейнере).
|
Если WARP нужен: выход в интернет через Cloudflare для <em>выбранных</em> клиентов (правила маршрутизации и NAT в контейнере).
|
||||||
Один раз на хосте VPS под root: в каталоге с репозиторием выполните <code class="inline">bash scripts/warp-amnezia.sh install</code>.
|
Один раз на хосте VPS под root: в каталоге с репозиторием выполните <code class="inline">bash scripts/warp-amnezia.sh install</code>.
|
||||||
Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Убрать WARP полностью: <code class="inline">bash scripts/warp-amnezia.sh uninstall</code> — см. README.
|
Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Установить или удалить WARP с хоста можно кнопками ниже (пароль root по SSH — как при синхронизации времени). Убрать вручную: <code class="inline">bash scripts/warp-amnezia.sh uninstall</code> — см. README.
|
||||||
</p>
|
</p>
|
||||||
<div id="warp-actions" class="warp-actions"></div>
|
<div id="warp-actions" class="warp-actions"></div>
|
||||||
<div id="warp-client-list" class="warp-client-list"></div>
|
<div id="warp-client-list" class="warp-client-list"></div>
|
||||||
@@ -220,6 +220,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="warp-ssh-dialog" class="dt-dialog">
|
||||||
|
<div class="dt-dialog-inner">
|
||||||
|
<h3 id="warp-ssh-title">WARP на хосте VPS</h3>
|
||||||
|
<p class="muted" id="warp-ssh-lead"></p>
|
||||||
|
<label for="warp-ssh-pw">Пароль root на хосте VPS (не сохраняется)</label>
|
||||||
|
<input id="warp-ssh-pw" type="password" autocomplete="off" class="clock-sync-input" />
|
||||||
|
<div class="dt-dialog-actions">
|
||||||
|
<button type="button" class="btn ghost" id="warp-ssh-cancel">Отмена</button>
|
||||||
|
<button type="button" class="btn primary" id="warp-ssh-ok">Выполнить</button>
|
||||||
|
</div>
|
||||||
|
<p id="warp-ssh-err" class="status err" role="alert"></p>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<script src="/app.js" type="module"></script>
|
<script src="/app.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ PREV_CONTAINER_ENV=""
|
|||||||
if docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
if docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||||
PREV_CONTAINER_ENV="$(docker inspect "${CONTAINER_NAME}" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null || true)"
|
PREV_CONTAINER_ENV="$(docker inspect "${CONTAINER_NAME}" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE; do
|
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE WARP_SSH_INSTALL_DIR; do
|
||||||
if [[ -z "${!__ui_var:-}" ]] && [[ -n "${PREV_CONTAINER_ENV}" ]]; then
|
if [[ -z "${!__ui_var:-}" ]] && [[ -n "${PREV_CONTAINER_ENV}" ]]; then
|
||||||
PREV_VAL=""
|
PREV_VAL=""
|
||||||
while IFS= read -r __line; do
|
while IFS= read -r __line; do
|
||||||
@@ -160,7 +160,7 @@ if [[ -n "${TZ:-}" ]]; then
|
|||||||
RUN_ENV+=( -e "TZ=${TZ}" )
|
RUN_ENV+=( -e "TZ=${TZ}" )
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for __warp_var in WARP_DIR WARP_CONF_PATH WARP_CLIENTS_LIST AMNEZIA_START_SCRIPT; do
|
for __warp_var in WARP_DIR WARP_CONF_PATH WARP_CLIENTS_LIST AMNEZIA_START_SCRIPT WARP_SSH_INSTALL_DIR; do
|
||||||
if [[ -n "${!__warp_var:-}" ]]; then
|
if [[ -n "${!__warp_var:-}" ]]; then
|
||||||
RUN_ENV+=( -e "${__warp_var}=${!__warp_var}" )
|
RUN_ENV+=( -e "${__warp_var}=${!__warp_var}" )
|
||||||
fi
|
fi
|
||||||
@@ -172,7 +172,7 @@ for __export_var in CLIENT_CONFIG_ENDPOINT CLIENT_EXPORT_DNS1 CLIENT_EXPORT_DNS2
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE; do
|
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE WARP_SSH_INSTALL_DIR; do
|
||||||
if [[ -n "${!__ui_var:-}" ]]; then
|
if [[ -n "${!__ui_var:-}" ]]; then
|
||||||
RUN_ENV+=( -e "${__ui_var}=${!__ui_var}" )
|
RUN_ENV+=( -e "${__ui_var}=${!__ui_var}" )
|
||||||
fi
|
fi
|
||||||
|
|||||||
47
server.js
47
server.js
@@ -1400,6 +1400,50 @@ app.post("/api/sync-host-time", requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post("/api/warp/host-setup", requireAuth, async (req, res) => {
|
||||||
|
if (UI_HIDDEN.warp) {
|
||||||
|
return res.status(403).json({ error: MSG_UI_WARP_OFF });
|
||||||
|
}
|
||||||
|
if (!hostTimeSyncConfigured()) {
|
||||||
|
return res.status(503).json({
|
||||||
|
error:
|
||||||
|
"С панели недоступно: в образе панели нет sshpass или задано TIME_SYNC_DISABLED=1. Запустите на хосте VPS вручную: bash /opt/amnezia-admin/scripts/warp-amnezia.sh install",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const pw = req.body?.rootPassword;
|
||||||
|
const cmd = req.body?.cmd;
|
||||||
|
if (typeof pw !== "string" || !pw.trim()) {
|
||||||
|
return res.status(400).json({ error: "Укажите пароль root VPS" });
|
||||||
|
}
|
||||||
|
if (cmd !== "install" && cmd !== "uninstall") {
|
||||||
|
return res.status(400).json({ error: "Ожидается cmd: install или uninstall" });
|
||||||
|
}
|
||||||
|
const rt = runtimeForRequest(req);
|
||||||
|
const container = String(rt.profile.container || "").trim();
|
||||||
|
if (!/^[a-zA-Z0-9_.-]+$/.test(container)) {
|
||||||
|
return res.status(400).json({ error: "Некорректное имя контейнера в профиле AWG" });
|
||||||
|
}
|
||||||
|
let installDir = "/opt/amnezia-admin";
|
||||||
|
try {
|
||||||
|
installDir = assertSafeUnixPath(process.env.WARP_SSH_INSTALL_DIR?.trim() || "/opt/amnezia-admin");
|
||||||
|
} catch {
|
||||||
|
return res.status(500).json({ error: "Некорректная переменная WARP_SSH_INSTALL_DIR на сервере панели" });
|
||||||
|
}
|
||||||
|
const host = process.env.TIME_SYNC_SSH_HOST?.trim() || "172.17.0.1";
|
||||||
|
const remoteCmd = `bash -lc 'cd ${installDir} && chmod +x scripts/warp-amnezia.sh 2>/dev/null || true && AWG_CONTAINER=${container} ./scripts/warp-amnezia.sh ${cmd}'`;
|
||||||
|
try {
|
||||||
|
const out = await sshRootRun(pw.trim(), host, remoteCmd);
|
||||||
|
res.json({ ok: true, output: out.slice(0, 8000) });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("warp host-setup:", e);
|
||||||
|
res.status(400).json({
|
||||||
|
error:
|
||||||
|
String(e.message || e) ||
|
||||||
|
"Не удалось выполнить по SSH. Проверьте пароль root, что вход root по паролю разрешён, переменную TIME_SYNC_SSH_HOST и наличие каталога со скриптом на хосте.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/api/login", (req, res) => {
|
app.post("/api/login", (req, res) => {
|
||||||
const pw = req.body?.password;
|
const pw = req.body?.password;
|
||||||
if (typeof pw !== "string" || !pw) {
|
if (typeof pw !== "string" || !pw) {
|
||||||
@@ -1520,6 +1564,9 @@ app.get("/api/clients", requireAuth, async (req, res) => {
|
|||||||
wgShowWarp: warpMeta.wgShowWarp || "",
|
wgShowWarp: warpMeta.wgShowWarp || "",
|
||||||
selectedAllowedIps: warpMeta.selectedAllowedIps,
|
selectedAllowedIps: warpMeta.selectedAllowedIps,
|
||||||
paths: warpMeta.paths,
|
paths: warpMeta.paths,
|
||||||
|
hostSshInstall: hostTimeSyncConfigured(),
|
||||||
|
sshHost: process.env.TIME_SYNC_SSH_HOST?.trim() || "172.17.0.1",
|
||||||
|
installDir: process.env.WARP_SSH_INSTALL_DIR?.trim() || "/opt/amnezia-admin",
|
||||||
};
|
};
|
||||||
res.json({
|
res.json({
|
||||||
profileId: rt.profile.id,
|
profileId: rt.profile.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user