release: 1.4.2 — server obfuscator settings, Android 1.4.3 fix for configs with unknown obfuscator keys

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Бобырев Андрей
2026-10-23 11:57:13 +03:00
parent c139debd8d
commit d795bc6f70
7 changed files with 81 additions and 8 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,3 @@
__pycache__/
*.pyc
macos/
windows/
windows/build/

View File

@@ -19,6 +19,11 @@ phobos-update beta # или: phobos-update dev
---
## 1.4.2 — настройка обфускатора сервера
- **feat:** в панели «Настройки» → «Обфускатор сервера»: masking (AUTO/STUN/NONE), max-dummy, idle — записываются во все порты сервера, обфускатор перезапускается (клиенты переподключаются за секунды).
- **fix (Android PCA Phobos VPN 1.4.3):** конфиги других сборок Phobos с лишними ключами (`obfuscate-bytes` и т. п.) больше не роняют обфускатор («обфускатор завершился, код 1»): передаются только ключи, известные wg-obfuscator v1.6.
## 1.4.1 — резервный TCP-канал, «РФ напрямую»
- **feat:** резервный канал TCP: если провайдер режет UDP, клиент сам переходит на TCP (порт 443, иначе 8443/2096/4433). Сервер: служба `phobos-tcp` (`udptcp`), ставится и включается автоматически при установке и обновлении. Проверено реальной блокировкой UDP: переключение за ~15 с, без блокировки остаётся UDP.

View File

@@ -137,8 +137,8 @@ bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/
## Приложения для Android
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.1/PCA-Phobos-VPN.apk) · исходники `android-client/`.
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.1/PCA-Phobos.apk) · исходники `android/`.
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), **российские сайты напрямую** (по умолчанию), **канал Авто/UDP/TCP** (при блокировке UDP — сам на TCP), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.2/PCA-Phobos-VPN.apk) · исходники `android-client/`.
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.2/PCA-Phobos.apk) · исходники `android/`.
## Клиенты для телефона

View File

@@ -1 +1 @@
1.4.1
1.4.2

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pca.phobos.vpn"
android:versionCode="142"
android:versionName="1.4.2">
android:versionCode="143"
android:versionName="1.4.3">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="35" />
<uses-permission android:name="android.permission.INTERNET" />

View File

@@ -217,11 +217,16 @@ public class MainActivity extends Activity {
private static String getPackageNameSafe() { return "com.pca.phobos.vpn"; }
private static final java.util.Set<String> OBF_KEYS = new java.util.HashSet<>(java.util.Arrays.asList(
"target", "key", "masking", "idle-timeout", "max-dummy", "static-bindings", "max-clients", "target-if", "allow-clean", "in-timeout", "resolve-interval"));
private String obfPart(String conf, boolean viaTcp) {
StringBuilder sb = new StringBuilder("[instance]\nsource-if = 127.0.0.1\nsource-lport = " + LPORT + "\n");
for (String ln : section(conf, "[instance]")) {
String k = key(ln);
if (k.equals("source-if") || k.equals("source-lport") || k.equals("verbose")) continue;
// configs from other Phobos builds carry keys our wg-obfuscator v1.6 rejects (obfuscate-bytes…)
if (!OBF_KEYS.contains(k)) continue;
if (viaTcp && k.equals("target")) ln = "target = 127.0.0.1:" + TCP_LPORT;
sb.append(ln).append('\n');
}

66
app.py
View File

@@ -37,7 +37,7 @@ WG_CONFIG = "/etc/wireguard/wg0.conf"
# Distribution: Gitea first (GitHub account is unavailable since 23.10.2026), GitHub fallback
PCA_GIT = "https://git.bobyrev.com/PCA/PCA_Phobos"
PCA_GH_RAW = "https://raw.githubusercontent.com/andrey271192/PCA_Phobos"
APK_TAG = "v1.4.1"
APK_TAG = "v1.4.2"
WARP_DIR = f"{PHOBOS_DIR}/server/warp"
WARP_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf"
WARP_LICENSE_FILE = f"{WARP_DIR}/license.key"
@@ -102,6 +102,44 @@ def save_settings(s):
json.dump(s, f, indent=2, ensure_ascii=False)
SRV_OBF_KEYS = ("masking", "max-dummy", "idle-timeout")
def _srv_obf_confs():
import glob
return sorted(glob.glob(f"{PHOBOS_DIR}/server/wg-obfuscator-*.conf"))
def read_server_obf():
"""Current server wg-obfuscator params (from the first instance config)."""
vals = {"masking": "AUTO", "max-dummy": "50", "idle-timeout": "300"}
confs = _srv_obf_confs()
if confs:
for ln in Path(confs[0]).read_text().splitlines():
k, _, v = ln.partition("=")
if k.strip() in SRV_OBF_KEYS:
vals[k.strip()] = v.strip()
return vals
def write_server_obf(vals):
"""Rewrite masking/max-dummy/idle-timeout in every server instance and restart them."""
done = []
for f in _srv_obf_confs():
lines, seen = [], set()
for ln in Path(f).read_text().splitlines():
k = ln.partition("=")[0].strip()
if k in vals:
ln = f"{k} = {vals[k]}"; seen.add(k)
lines.append(ln)
lines += [f"{k} = {v}" for k, v in vals.items() if k not in seen]
Path(f).write_text("\n".join(lines) + "\n")
port = re.search(r"wg-obfuscator-(\d+)\.conf$", f).group(1)
subprocess.run(["systemctl", "restart", f"wg-obfuscator-{port}"], timeout=30)
done.append(port)
return done
def _load_server_env():
"""Load server.env as dict for subprocess env."""
env = {}
@@ -2741,6 +2779,17 @@ def settings_page():
except Exception:
pass
msg = f'<div class="alert alert-ok">{tr("Параметры обфускатора сохранены и отправлены на роутеры", "Obfuscator params saved and pushed to routers")} ({pushed})</div>'
elif request.method == "POST" and request.form.get("action") == "save_srv_obf":
masking = request.form.get("srv_masking", "AUTO").strip().upper()
if masking not in ("AUTO", "STUN", "NONE"):
masking = "AUTO"
try:
dummy = str(min(255, max(0, int(request.form.get("srv_max_dummy", "50")))))
idle = str(min(3600, max(10, int(request.form.get("srv_idle", "300")))))
except ValueError:
dummy, idle = "50", "300"
ports = write_server_obf({"masking": masking, "max-dummy": dummy, "idle-timeout": idle})
msg = f'<div class="alert alert-ok">{tr("Серверный обфускатор перезапущен с новыми параметрами, порты", "Server obfuscator restarted with new params, ports")}: {", ".join(ports) or "—"}</div>'
elif request.method == "POST":
new_pass = request.form.get("admin_pass", "").strip()
if new_pass:
@@ -2758,6 +2807,7 @@ def settings_page():
custom_warp_profile = read_custom_warp_profile()
warp_license_key = read_warp_license_key()
warp = warp_status()
srv_obf = read_server_obf()
warp_service = warp.get("service", "unknown")
warp_state = tr("включён", "enabled") if warp.get("enabled") else tr("выключен", "disabled")
warp_subnet = _html_attr(warp.get("subnet", server_wg_network()))
@@ -2898,6 +2948,20 @@ def settings_page():
</form>
</div>
<div class="card">
<h2>{tr("Обфускатор сервера","Server obfuscator")} {hlp("Параметры wg-obfuscator на ЭТОМ сервере (все порты). masking: AUTO принимает любой режим клиента — менять, только если знаете зачем; STUN/NONE — клиенты должны работать в том же режиме. max-dummy — мусорные байты в ответах, idle — таймаут простоя (сек). Сохранение перезапускает обфускатор: клиенты переподключатся за пару секунд.", "wg-obfuscator params on THIS server (all ports). masking: AUTO accepts any client mode — change only if you know why; with STUN/NONE clients must use the same mode. max-dummy — junk bytes in replies, idle — idle timeout (s). Saving restarts the obfuscator: clients reconnect within seconds.")}</h2>
<form method="post" class="form-row" style="flex-wrap:wrap;gap:10px;align-items:center" onsubmit="return confirm('{tr("Перезапустить обфускатор сервера? Клиенты переподключатся за пару секунд.","Restart server obfuscator? Clients will reconnect within seconds.")}')">
<input type="hidden" name="action" value="save_srv_obf">
<label>masking
<select name="srv_masking" style="background:var(--bg);border:1px solid var(--border);color:var(--text);padding:5px;border-radius:6px">
{"".join(f'<option value="{m}"{" selected" if srv_obf["masking"].upper()==m else ""}>{m}</option>' for m in ("AUTO","STUN","NONE"))}
</select></label>
<label>max-dummy <input type="number" name="srv_max_dummy" value="{_html_attr(srv_obf['max-dummy'])}" min="0" max="255" style="width:70px;padding:5px"></label>
<label>idle (s) <input type="number" name="srv_idle" value="{_html_attr(srv_obf['idle-timeout'])}" min="10" max="3600" style="width:80px;padding:5px"></label>
<button class="btn btn-primary btn-sm" type="submit">{tr("Сохранить и перезапустить","Save & restart")}</button>
</form>
</div>
<div class="card">
<h2>{tr("Информация о сервере","Server info")}</h2>
<table>