mirror of
https://github.com/andrey271192/PCA_Phobos.git
synced 2026-09-20 11:55:32 +00:00
Phone clients: panel generates phobos:// link + QR (Android) and plain WG + QR (iOS); installer qrcode dep + ALLOW_PLAIN_WG option
This commit is contained in:
@@ -148,3 +148,12 @@ PCA Phobos — это веб-панель и turnkey-инсталлятор по
|
||||
- [**wg-obfuscator**](https://github.com/ClusterM/wg-obfuscator) (ClusterM) — обфускация WireGuard-трафика. Поддержать автора: [Boosty](https://boosty.to/cluster) ❤️
|
||||
|
||||
Спасибо авторам за отличные инструменты.
|
||||
|
||||
## Клиенты для телефона
|
||||
|
||||
В таблице клиентов у каждого клиента есть кнопки:
|
||||
|
||||
- 📱 **Android** — `phobos://`-ссылка + QR для приложения **PhobosWG** (с обфускацией). Скан QR → импорт.
|
||||
- 🍎 **iPhone / iOS** — обычный WireGuard-конфиг + QR для официального WireGuard (БЕЗ обфускации). Требует открытого порта 51820 на сервере (поставь `ALLOW_PLAIN_WG=1` при установке, либо удали правило `iptables ... 51820 ... DROP`).
|
||||
|
||||
> Для каждого устройства создавай **отдельного клиента** (свой ключ и IP). Один конфиг на двух устройствах = конфликт ключей.
|
||||
|
||||
75
app.py
75
app.py
@@ -403,6 +403,49 @@ def generate_failover_conf_for_client(client_id):
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_phone_config(cid, mode="android"):
|
||||
"""Assemble a phone client config from the client's files.
|
||||
android = WireGuard + [instance] obfuscator -> phobos:// link (PhobosWG app).
|
||||
ios = plain WireGuard (no obfuscation), Endpoint -> server:51820."""
|
||||
import base64, urllib.parse
|
||||
cdir = os.path.join(CLIENTS_DIR, cid)
|
||||
wgpath = os.path.join(cdir, cid + ".conf")
|
||||
instpath = os.path.join(cdir, "wg-obfuscator.conf")
|
||||
if not os.path.exists(wgpath):
|
||||
return None, None
|
||||
wg = open(wgpath).read().rstrip()
|
||||
if mode == "ios":
|
||||
server_ip = SERVER_IP
|
||||
try:
|
||||
for ln in open(instpath):
|
||||
if ln.strip().startswith("target"):
|
||||
server_ip = ln.split("=", 1)[1].strip().split(":")[0]
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
out = []
|
||||
for ln in wg.split("\n"):
|
||||
out.append("Endpoint = %s:51820" % server_ip if ln.strip().startswith("Endpoint") else ln)
|
||||
return "\n".join(out) + "\n", None
|
||||
inst = ""
|
||||
if os.path.exists(instpath):
|
||||
inst = open(instpath).read().strip()
|
||||
conf = wg + "\n\n" + inst + "\n"
|
||||
b64 = base64.urlsafe_b64encode(conf.encode()).decode().rstrip("=")
|
||||
link = "phobos://" + b64 + "#" + urllib.parse.quote(cid)
|
||||
return conf, link
|
||||
|
||||
|
||||
def qr_datauri(data):
|
||||
try:
|
||||
import qrcode, io, base64
|
||||
buf = io.BytesIO()
|
||||
qrcode.make(data).save(buf, format="PNG")
|
||||
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def fanout_router_config(client_id):
|
||||
"""Push this client's failover.conf to every secondary server's agent so
|
||||
routers can PULL it through the tunnel (10.25.0.1:8444) — survives a public
|
||||
@@ -960,6 +1003,8 @@ def clients_page():
|
||||
<input type="hidden" name="client_id" value="{cid}">
|
||||
<button class="btn btn-danger btn-sm" type="submit">{tr("Удалить", "Del")}</button>
|
||||
</form>
|
||||
<a href="/client/{cid}/phone/android" class="btn btn-sm" style="background:#16a34a" title="Android / PhobosWG">📱</a>
|
||||
<a href="/client/{cid}/phone/ios" class="btn btn-sm" style="background:#475569" title="iPhone / iOS WireGuard">🍎</a>
|
||||
{h_del}
|
||||
</td>
|
||||
</tr>"""
|
||||
@@ -1210,6 +1255,36 @@ def sync_peer_to_all_servers(public_key, allowed_ips, action="add"):
|
||||
sync_peer_to_server(srv, public_key, allowed_ips, action)
|
||||
|
||||
|
||||
@app.route("/client/<cid>/phone/<mode>")
|
||||
@auth_required
|
||||
def client_phone(cid, mode):
|
||||
if mode not in ("android", "ios"):
|
||||
mode = "android"
|
||||
conf, link = build_phone_config(cid, mode)
|
||||
if conf is None:
|
||||
return ("client not found", 404)
|
||||
qr = qr_datauri(link if (mode == "android" and link) else conf)
|
||||
if mode == "android":
|
||||
title = tr("Android — PhobosWG (с обфускацией)", "Android — PhobosWG (obfuscated)")
|
||||
extra = (f'<p>{tr("phobos:// ссылка — импорт в PhobosWG:", "phobos:// link — import into PhobosWG:")}</p>'
|
||||
f'<textarea readonly onclick="this.select()" style="width:100%;height:90px;font-size:.75em">{link}</textarea>')
|
||||
else:
|
||||
title = tr("iPhone / iOS WireGuard (без обфускации)", "iPhone / iOS WireGuard (plain, no obfuscation)")
|
||||
extra = (f'<p style="color:#fcd34d">{tr("⚠ Обычный WireGuard без маскировки (Endpoint → :51820). На сервере должен быть открыт порт 51820.", "⚠ Plain WireGuard, no obfuscation (Endpoint → :51820). Port 51820 must be open on the server.")}</p>')
|
||||
html = f"""
|
||||
<div class="container">
|
||||
{nav('clients')}
|
||||
<div class="card" style="text-align:center">
|
||||
<h2>{cid} — {title}</h2>
|
||||
<img src="{qr}" alt="QR" style="width:300px;height:300px;background:#fff;padding:10px;border-radius:10px"><br>
|
||||
<p style="margin-top:10px">{tr("Отсканируй QR в приложении или импортируй конфиг:", "Scan the QR in the app or import the config:")}</p>
|
||||
{extra}
|
||||
<textarea readonly onclick="this.select()" style="width:100%;height:250px;font-family:monospace;font-size:.78em">{conf}</textarea>
|
||||
<p style="margin-top:10px"><a href="/clients" class="btn btn-sm">{tr("← назад", "← back")}</a></p>
|
||||
</div></div>"""
|
||||
return render(html)
|
||||
|
||||
|
||||
@app.route("/api/router-config/<client_id>")
|
||||
def router_config(client_id):
|
||||
"""NAT-friendly config pull: router fetches its own failover.conf.
|
||||
|
||||
11
install.sh
11
install.sh
@@ -54,7 +54,7 @@ echo "[1/9] dependencies..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq wireguard wireguard-tools iptables jq curl git \
|
||||
python3 python3-flask gunicorn nginx cron >/dev/null
|
||||
python3 python3-flask gunicorn nginx cron python3-qrcode >/dev/null
|
||||
systemctl enable cron -q 2>/dev/null || true; systemctl start cron 2>/dev/null || true
|
||||
|
||||
# ── 2. wg-obfuscator binary (Ground-Zerro) ──
|
||||
@@ -107,8 +107,13 @@ systemctl restart wg-quick@wg0
|
||||
# ── 5. obfuscator services (multi-port) ──
|
||||
echo "[5/9] obfuscator services..."
|
||||
OBF_KEY=$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 32)
|
||||
iptables -C INPUT -p udp --dport 51820 ! -s 127.0.0.1 -j DROP 2>/dev/null \
|
||||
|| iptables -A INPUT -p udp --dport 51820 ! -s 127.0.0.1 -j DROP
|
||||
# Block direct WG (force obfuscation) unless ALLOW_PLAIN_WG=1 (e.g. for iOS WireGuard)
|
||||
if [ -z "${ALLOW_PLAIN_WG:-}" ]; then
|
||||
iptables -C INPUT -p udp --dport 51820 ! -s 127.0.0.1 -j DROP 2>/dev/null \
|
||||
|| iptables -A INPUT -p udp --dport 51820 ! -s 127.0.0.1 -j DROP
|
||||
else
|
||||
iptables -D INPUT -p udp --dport 51820 ! -s 127.0.0.1 -j DROP 2>/dev/null || true
|
||||
fi
|
||||
IFS=',' read -ra PORTS <<< "$OBF_PORTS"
|
||||
for PORT in "${PORTS[@]}"; do
|
||||
cat > "$PHOBOS_DIR/server/wg-obfuscator-${PORT}.conf" <<EOF
|
||||
|
||||
Reference in New Issue
Block a user