feat: install warp on enable

This commit is contained in:
Андрей Бобырев
2026-06-06 14:30:51 +03:00
parent e534108f1e
commit f4457288af
4 changed files with 77 additions and 14 deletions

View File

@@ -94,12 +94,12 @@ PCATELEGRAM_WEB_ADMIN_PASSWORD='strong-password' bash bootstrap.sh
- `Off` — WARP выключен.
- `WARP` — обычный Cloudflare WARP.
- `WARP+` — WARP+ с license key.
- `All clients` — применяет WARP на весь proxy-трафик через `warp-cli`, если Cloudflare WARP установлен на сервере.
- `One client` — сохраняет WARP/WARP+ профиль и key для выбранного клиента в `/opt/pcatelegram_web/warp.json`.
- `All clients` если `warp-cli` не установлен, web-admin ставит `cloudflare-warp`, затем применяет WARP на весь proxy-трафик через `warp-cli`.
- `One client` если `warp-cli` не установлен, web-admin ставит `cloudflare-warp`, затем сохраняет WARP/WARP+ профиль и key для выбранного клиента в `/opt/pcatelegram_web/warp.json`.
WARP+ key не отдается в API целиком: web показывает только маску. Файл `warp.json` хранится с правами `0600` и входит в backup.
Для реального global WARP на сервере нужен установленный `cloudflare-warp` и доступная команда `warp-cli`. По документации Cloudflare: регистрация `warp-cli registration new`, WARP+ key `warp-cli registration license <KEY>`, подключение `warp-cli connect`.
Для real global WARP web-admin использует официальный Cloudflare Linux repo: добавляет GPG key, repo `pkg.cloudflareclient.com`, ставит пакет `cloudflare-warp`, затем выполняет регистрацию `warp-cli registration new`, WARP+ key `warp-cli registration license <KEY>`, подключение `warp-cli connect`.
Per-client runtime routing в текущем telemt не включается автоматически: публичные параметры telemt дают users/limits/quotas/ad tags, но не документируют привязку upstream к конкретному user. Для настоящего WARP только одному клиенту нужен отдельный telemt route/service или upstream-схема.

View File

@@ -604,15 +604,74 @@ def user_warp_payload(name: str) -> dict[str, Any]:
}
def install_warp_cli() -> dict[str, Any]:
result: dict[str, Any] = {"attempted": False, "installed": bool(shutil.which("warp-cli")), "commands": [], "warnings": []}
if result["installed"]:
return result
if os.geteuid() != 0:
result["warnings"].append("root required to install cloudflare-warp")
return result
def call(label: str, cmd: list[str], timeout: int = 120) -> tuple[int, str, str]:
result["attempted"] = True
code, out, err = run(cmd, timeout=timeout)
result["commands"].append({"cmd": label, "exit_code": code})
return code, out, err
apt_get = shutil.which("apt-get")
dnf = shutil.which("dnf")
yum = shutil.which("yum")
if apt_get:
script = r"""
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl gpg lsb-release ca-certificates
install -d -m 0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/cloudflare-client.list
apt-get update
apt-get install -y cloudflare-warp
"""
code, _, err = call("install cloudflare-warp via apt", ["bash", "-lc", script], timeout=420)
if code != 0:
result["warnings"].append(err.strip() or "apt install failed")
elif dnf or yum:
manager = dnf or yum
repo_url = "https://pkg.cloudflareclient.com/cloudflare-warp-ascii.repo"
script = f"""
set -e
rpm --import https://pkg.cloudflareclient.com/pubkey.gpg || true
curl -fsSL {shlex.quote(repo_url)} > /etc/yum.repos.d/cloudflare-warp.repo
{shlex.quote(manager)} install -y cloudflare-warp
"""
code, _, err = call("install cloudflare-warp via rpm repo", ["bash", "-lc", script], timeout=420)
if code != 0:
result["warnings"].append(err.strip() or "rpm install failed")
else:
result["warnings"].append("supported package manager not found")
result["installed"] = bool(shutil.which("warp-cli"))
if not result["installed"] and not result["warnings"]:
result["warnings"].append("warp-cli still not available after install")
return result
def apply_warp_runtime(cfg: dict[str, Any]) -> dict[str, Any]:
warp_cli = shutil.which("warp-cli")
result: dict[str, Any] = {"applied": False, "commands": [], "warnings": []}
result: dict[str, Any] = {"applied": False, "install": {"attempted": False, "installed": bool(warp_cli)}, "commands": [], "warnings": []}
if cfg["enabled"] and cfg["mode"] != "off" and not warp_cli:
install_result = install_warp_cli()
result["install"] = install_result
result["warnings"].extend(install_result.get("warnings", []))
warp_cli = shutil.which("warp-cli")
if (not cfg["enabled"] or cfg["mode"] == "off") and not warp_cli:
result["applied"] = True
return result
if not warp_cli:
result["warnings"].append("warp-cli not installed")
return result
if cfg["scope"] == "user":
result["warnings"].append("per-user WARP route saved only; telemt per-user upstream routing is not documented")
return result
def call(args: list[str], timeout: int = 20) -> tuple[int, str, str]:
code, out, err = run([warp_cli, *args], timeout=timeout)
@@ -627,6 +686,10 @@ def apply_warp_runtime(cfg: dict[str, Any]) -> dict[str, Any]:
result["applied"] = True
return result
if cfg["scope"] == "user":
result["warnings"].append("per-user WARP route saved only; telemt per-user upstream routing is not documented")
return result
run(["systemctl", "enable", "--now", "warp-svc"], timeout=20)
code, _, _ = call(["registration", "show"], timeout=10)
if code != 0:

View File

@@ -118,9 +118,9 @@ const i18n = {
warpSave: "Save WARP",
warpSaved: "WARP settings saved",
warpInstalled: "warp-cli installed",
warpNotInstalled: "warp-cli not installed",
warpPerUserNote: "One-client WARP profile is stored here. Runtime per-client routing needs a dedicated telemt route; global WARP applies to all clients.",
warpAllNote: "Global WARP applies to all proxy traffic when warp-cli is installed and connected.",
warpNotInstalled: "will install on save",
warpPerUserNote: "One-client WARP profile is stored here. If warp-cli is missing, it will be installed on save. Runtime per-client routing needs a dedicated telemt route; global WARP applies to all clients.",
warpAllNote: "Global WARP applies to all proxy traffic. If warp-cli is missing, it will be installed on save, then connected.",
savedKey: "saved key",
dashboard: "Dashboard",
noKeys: "No keys yet",
@@ -362,9 +362,9 @@ const i18n = {
warpSave: "Сохранить WARP",
warpSaved: "Настройки WARP сохранены",
warpInstalled: "warp-cli установлен",
warpNotInstalled: "warp-cli не установлен",
warpPerUserNote: "Профиль WARP для одного клиента сохраняется здесь. Runtime-маршрут на одного клиента требует отдельный маршрут telemt; global WARP действует на всех клиентов.",
warpAllNote: "Global WARP применится ко всему proxy-трафику, если warp-cli установлен и подключён.",
warpNotInstalled: "установится при сохранении",
warpPerUserNote: "Профиль WARP для одного клиента сохраняется здесь. Если warp-cli нет, он установится при сохранении. Runtime-маршрут на одного клиента требует отдельный маршрут telemt; global WARP действует на всех клиентов.",
warpAllNote: "Global WARP применится ко всему proxy-трафику. Если warp-cli нет, он установится при сохранении и затем подключится.",
savedKey: "ключ сохранён",
dashboard: "Обзор",
noKeys: "Ключей пока нет",

View File

@@ -440,6 +440,6 @@
<button id="qrCopyBtn" type="button" class="soft" data-i18n="copyLink">Copy link</button>
</div>
</div>
<script src="/app.js?v=2.5.0-admin21" type="module"></script>
<script src="/app.js?v=2.5.0-admin22" type="module"></script>
</body>
</html>