mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-09-20 14:42:00 +00:00
Add "Новый клиент" (direct) flow alongside cascade: - server.js: POST /api/clients/create — Endpoint = this server's public IP:ListenPort (CLIENT_CONFIG_ENDPOINT or request host), reuses the same key/peer/conf pipeline as create-cascade. - public/index.html: "Новый клиент" panel with #direct-form (name + optional tunnel IP). - public/app.js: downloadDirectConf() handler + form binding. install.sh hardening so fresh installs work out of the box: - Auto-detect a single amnezia-awg* container (e.g. amnezia-awg2, Amnezia AWG 2.0 default) -> AWG_CONTAINER, instead of the fixed "amnezia-awg" default that mismatched and blocked client ops. - Auto-default CLIENT_CONFIG_ENDPOINT to the host primary IP so direct .conf exports get a correct Endpoint without manual env. Fixes "cannot create users" on servers whose WG container is named amnezia-awg2.
352 lines
24 KiB
HTML
352 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>AmneziaWG — клиенты</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="/styles.css?v=1.2.28">
|
||
</head>
|
||
<body class="page">
|
||
<aside class="panel-promo-strip" role="complementary" aria-label="Поддержка и донат">
|
||
<div class="panel-promo-strip-inner">
|
||
<p class="panel-promo-subtitle muted">Поддержите проект через Boosty, Ozon СБП и Telegram — ниже те же ссылки в подвале.</p>
|
||
<div class="panel-promo-links">
|
||
<a class="panel-promo-a" href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">⭐ GitHub · PRO</a>
|
||
<span class="panel-promo-sep" aria-hidden="true">·</span>
|
||
<a class="panel-promo-a" href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">💖 Boosty</a>
|
||
<span class="panel-promo-sep" aria-hidden="true">·</span>
|
||
<a class="panel-promo-a" href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">💳 Ozon СБП</a>
|
||
<span class="panel-promo-sep" aria-hidden="true">·</span>
|
||
<a class="panel-promo-a" href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram @lot_andrey</a>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<div id="login-gate" class="gate hidden" aria-hidden="true">
|
||
<div class="gate-card">
|
||
<p class="eyebrow">Панель сервера</p>
|
||
<h1>Вход</h1>
|
||
<p class="sub">Введите пароль администратора панели.</p>
|
||
<form id="login-form">
|
||
<label for="login-password">Пароль</label>
|
||
<input id="login-password" type="password" autocomplete="current-password" required>
|
||
<button type="submit" class="btn primary full">Войти</button>
|
||
<p id="login-error" class="status err" role="alert"></p>
|
||
</form>
|
||
<nav class="gate-donate muted" aria-label="Поддержка и донат">
|
||
Поддержка веб‑панелей (вне этого сервера):<br/>
|
||
<a href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">GitHub · PRO</a>
|
||
<span class="gate-donate-dot" aria-hidden="true">·</span>
|
||
<a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">Boosty</a>
|
||
<span class="gate-donate-dot" aria-hidden="true">·</span>
|
||
<a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">Ozon СБП</a>
|
||
<span class="gate-donate-dot" aria-hidden="true">·</span>
|
||
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">Telegram</a>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="app-root" class="shell hidden">
|
||
<header class="top">
|
||
<div>
|
||
<p class="eyebrow">Панель сервера</p>
|
||
<h1>Пользователи AmneziaWG PRO</h1>
|
||
<p class="sub">Дату отключения задаёте сами (локальное время браузера). Кнопка «Выключить» убирает клиента из туннеля сразу. «Задать дату» без галочки — только запись в таблице; с галочкой — клиент остаётся в туннеле до наступления времени. Несколько инстансов задаются переменной <code class="inline">AWG_PROFILES</code>; установщик при апдейте подставляет её из прежнего контейнера или из <code class="inline">/root/amnezia-admin.awg-profiles.json</code>. Конфиг .conf — только если на сервере есть <code class="inline">last_config</code>; иначе используйте приложение Amnezia. Отдельно: раздел <strong>Telegram MTProto‑прокси</strong> ставит официальный Docker‑образ <code class="inline">telegrammessenger/proxy</code> через панель.</p>
|
||
</div>
|
||
<div class="token-box">
|
||
<div class="session-actions">
|
||
<button type="button" id="logout" class="btn ghost full">Выйти</button>
|
||
</div>
|
||
<details class="pw-change">
|
||
<summary>Сменить пароль</summary>
|
||
<form id="pw-form">
|
||
<label for="pw-current">Текущий пароль</label>
|
||
<input id="pw-current" type="password" autocomplete="current-password" required>
|
||
<label for="pw-new">Новый пароль</label>
|
||
<input id="pw-new" type="password" autocomplete="new-password" required minlength="8">
|
||
<label for="pw-new2">Повтор нового пароля</label>
|
||
<input id="pw-new2" type="password" autocomplete="new-password" required minlength="8">
|
||
<button type="submit" class="btn primary full">Сохранить новый пароль</button>
|
||
<p id="pw-msg" class="status" role="status"></p>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
</header>
|
||
|
||
<div id="edition-banner" class="edition-banner hidden" role="region" aria-label="Редакция панели"></div>
|
||
|
||
<section class="toolbar">
|
||
<div class="pill proto-pill"><span class="dot ok"></span><span id="proto-label">Протокол: AmneziaWG</span></div>
|
||
<div id="proto-switch" class="proto-switch hidden">
|
||
<label for="proto-select" class="proto-switch-label muted">Инстанс</label>
|
||
<select id="proto-select" class="proto-select" aria-label="Инстанс AmneziaWG"></select>
|
||
</div>
|
||
<div class="clock-strip" role="status" aria-live="polite">
|
||
<div class="clock-row clock-row--server">
|
||
<span class="muted clock-kind">Сервер <span class="clock-kind-sub">(часы VPS / контейнера)</span></span>
|
||
<time id="clock-server" class="clock-time">—</time>
|
||
</div>
|
||
<div class="clock-row clock-row--your-place">
|
||
<span class="muted clock-kind">Ваше место <span class="clock-kind-sub">(браузер)</span></span>
|
||
<time id="clock-local" class="clock-time">—</time>
|
||
</div>
|
||
<p id="clock-zone-diff" class="clock-zone-diff muted hidden" role="note"></p>
|
||
<p class="clock-hint muted">
|
||
Цифры разнятся, если пояса разные — это нормально для одного момента UTC. «Сверить» подтягивает время с VPS.
|
||
</p>
|
||
<button type="button" id="clock-sync" class="btn small ghost clock-sync-btn">
|
||
Сверить время
|
||
</button>
|
||
<details class="clock-host-sync">
|
||
<summary class="clock-sync-summary">Синхронизация: выставить часы <strong>хоста VPS</strong> под <strong>ваше устройство</strong></summary>
|
||
<p id="sync-host-hint" class="muted clock-sync-hint"></p>
|
||
<label for="sync-root-pw">Пароль root на хосте (не сохраняется)</label>
|
||
<input id="sync-root-pw" type="password" autocomplete="off" class="clock-sync-input">
|
||
<button type="button" id="sync-host-time" class="btn small primary clock-sync-submit">
|
||
Взять время с этого устройства и записать на хост VPS
|
||
</button>
|
||
</details>
|
||
</div>
|
||
<button type="button" id="refresh" class="btn primary">Обновить</button>
|
||
</section>
|
||
|
||
<p id="profile-hint" class="profile-hint muted hidden" role="note"></p>
|
||
|
||
<p id="status" class="status" role="status"></p>
|
||
|
||
<details class="panel-fold warp-panel" id="warp-panel" hidden>
|
||
<summary class="fold-summary">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Cloudflare WARP</span>
|
||
<span class="muted fold-meta" id="warp-status-line"></span>
|
||
</span>
|
||
</summary>
|
||
<div class="panel-fold-body">
|
||
<p class="muted warp-intro">
|
||
<strong>Необязательно.</strong> Если достаточно обычного AmneziaWG без выхода части клиентов через Cloudflare — раздел можно не трогать (статус «Не установлен» — норма).
|
||
Если WARP нужен: выход в интернет через Cloudflare для <em>выбранных</em> клиентов (правила маршрутизации и NAT в контейнере).
|
||
Один раз на хосте VPS под root: в каталоге с репозиторием выполните <code class="inline">bash scripts/warp-amnezia.sh install</code>.
|
||
Затем отметьте клиентов ниже и нажмите «Применить» (контейнер AWG перезапустится). Установить или удалить WARP с хоста можно кнопками ниже (пароль root по SSH — как при синхронизации времени). Убрать вручную: <code class="inline">bash scripts/warp-amnezia.sh uninstall</code> — см. README.
|
||
</p>
|
||
<div id="warp-actions" class="warp-actions"></div>
|
||
<div id="warp-client-list" class="warp-client-list"></div>
|
||
<details class="raw warp-raw">
|
||
<summary>Вывод wg show warp</summary>
|
||
<pre id="warp-wg-show"></pre>
|
||
</details>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="panel-fold mtproto-panel" id="mtproto-panel" hidden>
|
||
<summary class="fold-summary">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Telegram MTProto‑прокси</span>
|
||
<span class="muted fold-meta" id="mtproto-status-line"></span>
|
||
</span>
|
||
</summary>
|
||
<div class="panel-fold-body">
|
||
<p class="muted warp-intro mtproto-intro">
|
||
Отдельный Docker‑контейнер (<strong>telegrammessenger/proxy</strong>), публикация порта на хосте VPS.
|
||
Ссылка <code class="inline">tg://proxy?…</code> подставляет хост из URL панели, если не заданы переменные; для постоянного IP/DNS укажите <code class="inline">MTPRO_PUBLIC_HOST</code> или <code class="inline">CLIENT_CONFIG_ENDPOINT</code>.
|
||
Секрет после установки сохраните вне панели — здесь он показывается маскированно.
|
||
За nginx/Caddy проксируйте префикс <code class="inline">/api/</code> целиком (см. README).
|
||
</p>
|
||
<p id="mtproto-callout" class="mtproto-callout hidden" role="note"></p>
|
||
<div id="mtproto-link-card" class="mtproto-link-card hidden" role="region" aria-label="Ссылка для Telegram">
|
||
<div class="mtproto-link-row">
|
||
<strong class="mtproto-link-label">Ссылка в Telegram</strong>
|
||
<a id="mtproto-link-anchor" class="mtproto-link-anchor" target="_blank" rel="noopener noreferrer"></a>
|
||
</div>
|
||
<div class="mtproto-link-row mtproto-link-actions">
|
||
<button type="button" id="mtproto-link-copy" class="btn small ghost">Скопировать</button>
|
||
<span id="mtproto-link-copy-state" class="muted warp-muted"></span>
|
||
</div>
|
||
</div>
|
||
<div class="mtproto-fields">
|
||
<label for="mtproto-host-port">Порт хоста (необязательно — по умолчанию см. MTPRO_PUBLISH_PORT, обычно 8443)</label>
|
||
<input id="mtproto-host-port" type="number" min="512" max="65535" autocomplete="off" placeholder="8443 или другой свободный порт">
|
||
|
||
<label for="mtproto-secret-opt">Кастомный секрет hex (32 символа, необязательно)</label>
|
||
<input id="mtproto-secret-opt" type="text" maxlength="32" autocomplete="off" placeholder="Пусто — сгенерируется">
|
||
</div>
|
||
<div id="mtproto-actions" class="warp-actions"></div>
|
||
<p id="mtproto-banner" class="status ok hidden" role="status"></p>
|
||
<details class="raw warp-raw">
|
||
<summary>Хвост логов контейнера mtproto‑proxy</summary>
|
||
<pre id="mtproto-logs-tail"></pre>
|
||
</details>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="panel-fold" id="direct-panel" open>
|
||
<summary class="fold-summary">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Новый клиент</span>
|
||
<span class="muted fold-meta">Прямое подключение к этому серверу — Endpoint = публичный IP этого VPS</span>
|
||
</span>
|
||
</summary>
|
||
<div class="panel-fold-body">
|
||
<div class="muted cascade-guide">
|
||
<p class="cascade-intro">
|
||
<strong>Обычное подключение.</strong> Создаёт нового клиента AmneziaWG и сразу скачивает файл
|
||
<code class="inline">.conf</code>, где <strong>Endpoint указывает на этот сервер напрямую</strong>.
|
||
Импортируйте файл в приложение Amnezia на телефоне или ПК. IP в туннеле подберётся автоматически.
|
||
</p>
|
||
</div>
|
||
<form id="direct-form" class="cascade-form">
|
||
<label for="direct-name">Имя клиента</label>
|
||
<input id="direct-name" type="text" placeholder="Например iPhone Андрей" autocomplete="off">
|
||
|
||
<label for="direct-tunnel-ip">IP в VPN-туннеле (необязательно)</label>
|
||
<input id="direct-tunnel-ip" type="text" placeholder="Пусто — подберём свободный в подсети" autocomplete="off">
|
||
|
||
<button type="submit" class="btn primary">Создать и скачать .conf</button>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="panel-fold cascade-panel" id="cascade-panel" open>
|
||
<summary class="fold-summary">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Новый клиент под каскад</span>
|
||
<span class="muted fold-meta">Клиент стучится в «первый» узел → трафик сюда; ключи выдаём на этом VPS</span>
|
||
</span>
|
||
</summary>
|
||
<div class="panel-fold-body">
|
||
<div class="muted cascade-guide">
|
||
<p class="cascade-intro">
|
||
<strong>Что здесь происходит.</strong> Приложение Amnezia всегда смотрит на поле <strong>Endpoint</strong> в конфиге.
|
||
При каскаде клиент «видит» только <strong>промежуточный узел</strong> (часто недорогой VPS поближе к пользователям), а <strong>реальное соединение AmneziaWG живёт на этом сервере</strong>, где крутится ваш основной контейнер.
|
||
Ключ и peer создаём здесь и выдаём файл <code class="inline">.conf</code> — Endpoint в файле будет на промежуточную машину, а не на прямой IP этого VPS в интернете.
|
||
</p>
|
||
<p class="cascade-intro cascade-intro-tight"><strong>Сначала нужна пересылка порта до этого VPS.</strong> На промежуточном сервере настройте проброс DNAT UDP (и TCP при необходимости).
|
||
Удобный вариант с веб-панелью iptables —
|
||
репозиторий <a href="https://github.com/andrey271192/kaskad_web_vpn" target="_blank" rel="noopener noreferrer"><strong>kaskad_web_vpn</strong></a>:
|
||
ставится одной командой ниже; в её панели создаёте правило «входящий порт → UDP на IP:порт этого AmneziaWG». Подробнее — README в том же репозитории.</p>
|
||
<pre class="cascade-snippet" tabindex="0" role="note" aria-label="Установка kaskad_web_vpn на промежуточном VPS">curl -fsSL https://raw.githubusercontent.com/andrey271192/kaskad_web_vpn/main/install.sh | sudo bash</pre>
|
||
<p class="cascade-intro cascade-intro-tight">После установки откройте <code class="inline">http://ПЕРЕХОДНОЙ_IP:8088/setup</code> (или свой <code class="inline">HOST_PORT</code>), задайте пароль админки каскада и добавьте правило проброса на <strong><code class="inline">ListenPort</code> этого инстанса</strong> на данном VPS (видно в шапке панели или в <code class="inline">awg show</code>).</p>
|
||
<ol class="cascade-steps">
|
||
<li>На промежуточном VPS: <strong>kaskad_web_vpn</strong> (команда выше) или свой DNAT / проброс порта на роутере.</li>
|
||
<li>Проверьте, что с промежуточной машины доходит UDP до WG-порта на <strong>этом</strong> сервере (если ниже поле порта Endpoint пустое — это тот же порт, что <code class="inline">ListenPort</code> здесь).</li>
|
||
<li>В форме ниже в <strong>Endpoint</strong> укажите публичный <strong>IP или DNS промежуточного узла</strong> — тот адрес, с которым реально установит туннель клиентское приложение из интернета.</li>
|
||
<li>Нажмите <strong>Создать на сервере и скачать .conf</strong> и импортируйте файл в Amnezia на телефоне или ПК.</li>
|
||
</ol>
|
||
<p class="cascade-intro cascade-intro-tight"><strong>Порт Endpoint</strong> — когда снаружи слушает не тот номер порта, что WG здесь (пусто значит «как у сервера»). <strong>IP в VPN-туннеле</strong> можно не заполнять — подберём свободный адрес в той же подсети, что у остальных клиентов.</p>
|
||
</div>
|
||
<form id="cascade-form" class="cascade-form">
|
||
<label for="cascade-endpoint">Endpoint (хост для клиента)</label>
|
||
<input id="cascade-endpoint" type="text" placeholder="Например 203.0.113.50 или ddns.example.com" required autocomplete="off">
|
||
|
||
<label for="cascade-port">Порт Endpoint</label>
|
||
<input id="cascade-port" type="number" min="1" max="65535" placeholder="Пусто — как у сервера" autocomplete="off">
|
||
|
||
<label for="cascade-tunnel-ip">IP в VPN-туннеле (необязательно)</label>
|
||
<input id="cascade-tunnel-ip" type="text" placeholder="Например 10.8.1.20 — та же подсеть /24 что у других" autocomplete="off">
|
||
|
||
<label for="cascade-name">Имя в таблице</label>
|
||
<input id="cascade-name" type="text" placeholder="Например Телефон через узел X" autocomplete="off">
|
||
|
||
<button type="submit" class="btn primary">Создать на сервере и скачать .conf</button>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="panel-fold" id="users-panel" open>
|
||
<summary class="fold-summary">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Пользователи</span>
|
||
<span class="muted fold-meta" id="peer-count"></span>
|
||
</span>
|
||
</summary>
|
||
<div class="panel-fold-body">
|
||
<div class="table-wrap">
|
||
<table class="clients-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Имя</th>
|
||
<th>IP</th>
|
||
<th>Статус</th>
|
||
<th>Последнее отключение</th>
|
||
<th class="th-actions">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="rows"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="raw panel-fold-raw" id="wg-raw-details">
|
||
<summary class="fold-summary fold-summary--raw">
|
||
<span class="fold-arrow" aria-hidden="true"></span>
|
||
<span class="fold-titles">
|
||
<span class="fold-h">Вывод awg show (отладка)</span>
|
||
</span>
|
||
</summary>
|
||
<pre id="wg-show"></pre>
|
||
</details>
|
||
</div>
|
||
|
||
<footer class="support-footer">
|
||
<div class="support-line">
|
||
<strong class="support-title">amnezia_web-PRO</strong>
|
||
<span class="support-sep" aria-hidden="true">·</span>
|
||
<a href="https://github.com/andrey271192/amnezia_web-PRO" target="_blank" rel="noopener noreferrer">⭐ GitHub · amnezia_web-PRO</a>
|
||
<span class="support-sep" aria-hidden="true">·</span>
|
||
<a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener noreferrer">💖 Boosty</a>
|
||
<span class="support-sep" aria-hidden="true">·</span>
|
||
<a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener noreferrer">💳 Ozon СБП</a>
|
||
<span class="support-sep" aria-hidden="true">·</span>
|
||
<a href="https://t.me/lot_andrey" target="_blank" rel="noopener noreferrer">✉️ Telegram @lot_andrey</a>
|
||
</div>
|
||
<p class="support-blurb">
|
||
Поддержать проект — поставь звезду на GitHub И донат. Связаться с автором — Telegram.
|
||
</p>
|
||
</footer>
|
||
|
||
<dialog id="disconnect-dt-dialog" class="dt-dialog">
|
||
<div class="dt-dialog-inner">
|
||
<h3 id="dt-dialog-title">Дата отключения</h3>
|
||
<p class="muted" id="dt-dialog-client"></p>
|
||
<label for="dt-dialog-input">Дата и время</label>
|
||
<input type="datetime-local" id="dt-dialog-input" step="60">
|
||
<div id="dt-dialog-extra" class="dt-dialog-extra hidden">
|
||
<p class="dt-dialog-hint" id="dt-dialog-hint"></p>
|
||
<label class="dt-checkbox-label">
|
||
<input type="checkbox" id="dt-dialog-schedule-tunnel">
|
||
Убрать из туннеля в указанное время (до этого клиент остаётся в туннеле)
|
||
</label>
|
||
</div>
|
||
<div class="dt-dialog-actions">
|
||
<button type="button" class="btn ghost" id="dt-dialog-cancel">Отмена</button>
|
||
<button type="button" class="btn primary" id="dt-dialog-ok">Подтвердить</button>
|
||
</div>
|
||
</div>
|
||
</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?v=1.2.28" type="module"></script>
|
||
</body>
|
||
</html>
|