From 62f40f92fb08afdab92d0eea0d697e5af7ca8712 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: Thu, 14 May 2026 20:18:23 +0300
Subject: [PATCH] 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
---
README.md | 9 +++--
docs/panel-guide.md | 1 +
package.json | 2 +-
public/app.js | 99 +++++++++++++++++++++++++++++++++++++++++++--
public/index.html | 16 +++++++-
scripts/install.sh | 6 +--
server.js | 47 +++++++++++++++++++++
7 files changed, 169 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 289a75a..12e59b0 100644
--- a/README.md
+++ b/README.md
@@ -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_CLIENTS_LIST` | `{WARP_DIR}/clients.list` | Список строк `10.8.x.x/32` — кому маршрутизировать трафик через интерфейс `warp` |
| `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` | Как часто планировщик проверяет отложенное отключение из туннеля (мс) |
| `TZ` | _(часто UTC в Docker)_ | Пояс строки «Сервер» в панели (IANA, например `Europe/Berlin`). Без `TZ` берётся из образа (часто UTC) — тогда от браузера будет видна разница часов |
| `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 (необязательно)
-**Устанавливать не обязательно.** Панель и обычный 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** — один раз на хосте VPS (root), из каталога с репозиторием (часто `/opt/amnezia-admin`):
+- **Нужен WARP** — в панели кнопка **«Установить WARP на VPS»** (пароль root по SSH, как у синхронизации времени хоста; нужны `sshpass` в образе панели и не отключённый `TIME_SYNC_DISABLED`) **или** один раз на хосте VPS (root), из каталога репозитория (часто `/opt/amnezia-admin`):
```bash
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`, если скачан).
-**Полностью убрать 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
cd /opt/amnezia-admin
diff --git a/docs/panel-guide.md b/docs/panel-guide.md
index de67c91..aa898ef 100644
--- a/docs/panel-guide.md
+++ b/docs/panel-guide.md
@@ -82,6 +82,7 @@
| POST | `/api/clients/disconnect-date` | Даты отключения / расписание |
| 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/warp/host-setup` | Установка/удаление WARP на хосте по SSH: `{ "rootPassword", "cmd": "install" \| "uninstall" }` (как синхронизация времени; каталог скрипта — `WARP_SSH_INSTALL_DIR`) |
| POST | `/api/warp/start` | Поднять WARP |
| POST | `/api/warp/stop` | Остановить WARP |
| POST | `/api/warp/routing` | Политика по клиентам |
diff --git a/package.json b/package.json
index 6b443d0..eb82fbf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "amnezia-admin",
- "version": "1.1.6",
+ "version": "1.1.7",
"private": true,
"description": "Web-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)",
"license": "MIT",
diff --git a/public/app.js b/public/app.js
index ccb9975..85f7770 100644
--- a/public/app.js
+++ b/public/app.js
@@ -47,6 +47,16 @@ const dtExtra = document.querySelector("#dt-dialog-extra");
const dtHint = document.querySelector("#dt-dialog-hint");
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`). */
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() {
stopClocks();
loginGate.classList.remove("hidden");
@@ -661,15 +726,40 @@ function renderWarpPanel(data) {
if (!w.installed) {
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");
hint.className = "muted warp-muted";
hint.innerHTML =
- "Один раз на хосте (root): bash scripts/warp-amnezia.sh install — из каталога клона репозитория на VPS. Если контейнер не угадан автоматически: bash scripts/warp-amnezia.sh install amnezia-awg2.";
+ "Вручную на хосте (root), из каталога репозитория: bash scripts/warp-amnezia.sh install или с именем контейнера: bash scripts/warp-amnezia.sh install amnezia-awg2.";
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");
skip.className = "muted warp-muted";
- skip.textContent =
- "Если Cloudflare WARP не нужен, ничего не выполняйте — AmneziaWG и панель работают без него. Полное удаление: bash scripts/warp-amnezia.sh uninstall (см. README).";
+ skip.textContent = "Если выход через Cloudflare не нужен, ничего не нажимайте — VPN уже работает без этого блока.";
warpActionsEl.appendChild(skip);
return;
}
@@ -771,6 +861,9 @@ function renderWarpPanel(data) {
}
}),
);
+ warpActionsEl.appendChild(
+ btn("Удалить WARP с VPS", "btn small warn", () => openWarpHostSetup("uninstall")),
+ );
}
function escapeHtml(s) {
diff --git a/public/index.html b/public/index.html
index 50a2e42..002fa7b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -103,7 +103,7 @@
Необязательно. Если достаточно обычного AmneziaWG без выхода части клиентов через Cloudflare — раздел можно не трогать (статус «Не установлен» — норма).
Если WARP нужен: выход в интернет через Cloudflare для выбранных клиентов (правила маршрутизации и NAT в контейнере).
Один раз на хосте VPS под root: в каталоге с репозиторием выполните bash scripts/warp-amnezia.sh install.
- Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Убрать WARP полностью: bash scripts/warp-amnezia.sh uninstall — см. README.
+ Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Установить или удалить WARP с хоста можно кнопками ниже (пароль root по SSH — как при синхронизации времени). Убрать вручную: bash scripts/warp-amnezia.sh uninstall — см. README.
@@ -220,6 +220,20 @@
+
+