release: 1.4.0 — distribution via git.bobyrev.com

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Бобырев Андрей
2026-10-23 02:31:23 +03:00
parent 7eac63343d
commit 5a01a6eed2
7 changed files with 67 additions and 219 deletions

View File

@@ -73,19 +73,24 @@
--- ---
> **Источник с 23.10.2026:** `git.bobyrev.com/PCA/PCA_Phobos` (GitHub-аккаунт временно недоступен). Серверы на 1.2.x обновляются одной командой:
> ```bash
> bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/update.sh)
> ```
## Быстрый старт (с чистого VPS, одной командой) ## Быстрый старт (с чистого VPS, одной командой)
Ставит **всё** со всеми зависимостями: wg-obfuscator, WireGuard, обфускатор-сервисы, веб-панель, nginx, скрипты онбординга роутеров, сторож авто-восстановления. Предустановленный Phobos **не требуется**. Ставит **всё** со всеми зависимостями: wg-obfuscator, WireGuard, обфускатор-сервисы, веб-панель, nginx, скрипты онбординга роутеров, сторож авто-восстановления. Предустановленный Phobos **не требуется**.
```bash ```bash
bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh) bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh)
``` ```
### С параметрами ### С параметрами
```bash ```bash
PANEL_PASS=МойПароль TG_TOKEN=123:abc TG_CHAT=123456789 \ PANEL_PASS=МойПароль TG_TOKEN=123:abc TG_CHAT=123456789 \
bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh) bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh)
``` ```
| Переменная | По умолчанию | Описание | | Переменная | По умолчанию | Описание |
@@ -106,7 +111,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
```bash ```bash
MAIN_PROTO=http MAIN_SERVER=<ip_основного> MAIN_PORT=<порт_панели> MAIN_API_KEY=<API key из основного> \ MAIN_PROTO=http MAIN_SERVER=<ip_основного> MAIN_PORT=<порт_панели> MAIN_API_KEY=<API key из основного> \
bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/secondary-setup.sh) bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/secondary-setup.sh)
``` ```
Сервер сам зарегистрируется в панели, клиенты досылаются на него сразу. Если сервер за NAT — добавь `SERVER_IP=<внешний_ip>`. Сервер сам зарегистрируется в панели, клиенты досылаются на него сразу. Если сервер за NAT — добавь `SERVER_IP=<внешний_ip>`.
@@ -116,7 +121,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
На входном VPS (чистый Debian/Ubuntu, Phobos не нужен): На входном VPS (чистый Debian/Ubuntu, Phobos не нужен):
```bash ```bash
bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/phobos-cascade.sh) enable <IP_выходного_сервера> 2083,5443,993 bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/phobos-cascade.sh) enable <IP_выходного_сервера> 2083,5443,993
``` ```
Затем в панели выходного сервера → **Настройки → Адрес для клиентов и каскад** → впиши IP/домен входа. Роутеры переключатся сами (~15 с), телефонам — заново открыть ссылку/QR. Отключить: `phobos-cascade disable`. Затем в панели выходного сервера → **Настройки → Адрес для клиентов и каскад** → впиши IP/домен входа. Роутеры переключатся сами (~15 с), телефонам — заново открыть ссылку/QR. Отключить: `phobos-cascade disable`.
@@ -131,8 +136,8 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
## Приложения для Android ## Приложения для Android
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), журнал, проверка скорости. [Скачать APK](https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PCA-Phobos-VPN.apk) · исходники `android-client/`. - **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), журнал, проверка скорости. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.0/PCA-Phobos-VPN.apk) · исходники `android-client/`.
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PCA-Phobos.apk) · исходники `android/`. - **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://git.bobyrev.com/PCA/PCA_Phobos/releases/download/v1.4.0/PCA-Phobos.apk) · исходники `android/`.
## Клиенты для телефона ## Клиенты для телефона

79
app.py
View File

@@ -34,6 +34,10 @@ PANEL_DIR = "/opt/phobos-panel"
SETTINGS_FILE = f"{PANEL_DIR}/settings.json" SETTINGS_FILE = f"{PANEL_DIR}/settings.json"
SECRET_FILE = f"{PANEL_DIR}/.secret_key" SECRET_FILE = f"{PANEL_DIR}/.secret_key"
WG_CONFIG = "/etc/wireguard/wg0.conf" 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.0"
WARP_DIR = f"{PHOBOS_DIR}/server/warp" WARP_DIR = f"{PHOBOS_DIR}/server/warp"
WARP_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf" WARP_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf"
WARP_LICENSE_FILE = f"{WARP_DIR}/license.key" WARP_LICENSE_FILE = f"{WARP_DIR}/license.key"
@@ -1058,26 +1062,17 @@ def _find_update_runner():
def _restore_update_script(ref): def _restore_update_script(ref):
repo, branch = _update_repo_for_ref(ref) _repo, branch = _update_repo_for_ref(ref)
token = "" kind = "tag" if branch.startswith("v") else "branch"
try: r = None
token = Path(GH_TOKEN_FILE).read_text().strip() for url in (f"{PCA_GIT}/raw/{kind}/{branch}/update.sh", f"{PCA_GH_RAW}/{branch}/update.sh"):
except Exception: r = subprocess.run(["curl", "-fsSL", "-m", "25", f"{url}?cb={int(time.time())}"],
pass capture_output=True, text=True, timeout=35)
cmd = ["curl", "-fsSL", "-m", "25"] if r.returncode == 0 and (r.stdout or "").startswith("#!"):
if repo.endswith("-dev") and token: break
cmd += ["-H", f"Authorization: token {token}"] if r is None or r.returncode != 0 or not (r.stdout or "").startswith("#!"):
cmd += ["-H", "Accept: application/vnd.github.raw"] return False, f"update helper restore failed for {branch}: {(r.stderr if r else '') or 'download failed'}"
url = f"https://api.github.com/repos/{repo}/contents/update.sh?ref={branch}" repo = PCA_GIT
else:
if token:
cmd += ["-H", f"Authorization: token {token}"]
url = f"https://raw.githubusercontent.com/{repo}/{branch}/update.sh?cb={int(time.time())}"
cmd.append(url)
r = subprocess.run(cmd, capture_output=True, text=True, timeout=35)
if r.returncode != 0 or not (r.stdout or "").startswith("#!"):
hint = "subscriber token missing or invalid" if repo.endswith("-dev") and not token else (r.stderr or "download failed")
return False, f"update helper restore failed from {repo}/{branch}: {hint}"
os.makedirs(os.path.dirname(UPDATE_SCRIPT), exist_ok=True) os.makedirs(os.path.dirname(UPDATE_SCRIPT), exist_ok=True)
tmp = UPDATE_SCRIPT + ".tmp" tmp = UPDATE_SCRIPT + ".tmp"
Path(tmp).write_text(r.stdout) Path(tmp).write_text(r.stdout)
@@ -1812,7 +1807,7 @@ textarea::placeholder{color:var(--muted)}
</div> </div>
CONTENT CONTENT
<div class="footer"> <div class="footer">
<b style="color:var(--accent)">PCA Phobos Panel</b> — панель управления &copy; <a href="https://github.com/andrey271192/PCA_Phobos" target="_blank" rel="noopener">andrey271192</a><br> <b style="color:var(--accent)">PCA Phobos Panel</b> — панель управления &copy; <a href="https://git.bobyrev.com/PCA/PCA_Phobos" target="_blank" rel="noopener">andrey271192</a><br>
Поддержать проект: <a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener">💖 Boosty</a> · <a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener">💳 Ozon Bank</a> · <a href="https://t.me/PCAdministration" target="_blank" rel="noopener">✉️ Telegram</a> Поддержать проект: <a href="https://boosty.to/andrey27/donate" target="_blank" rel="noopener">💖 Boosty</a> · <a href="https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798" target="_blank" rel="noopener">💳 Ozon Bank</a> · <a href="https://t.me/PCAdministration" target="_blank" rel="noopener">✉️ Telegram</a>
<div style="margin-top:8px;font-size:.88em;color:var(--muted);line-height:1.7;text-align:left;max-width:660px;margin:8px auto 0"> <div style="margin-top:8px;font-size:.88em;color:var(--muted);line-height:1.7;text-align:left;max-width:660px;margin:8px auto 0">
<b>Создано на основе открытых проектов:</b><br> <b>Создано на основе открытых проектов:</b><br>
@@ -2785,7 +2780,7 @@ def settings_page():
f'<div class="form-row"><label>{_html_attr(sv["ip"])}{" ★" if sv.get("is_primary") else ""}</label>' f'<div class="form-row"><label>{_html_attr(sv["ip"])}{" ★" if sv.get("is_primary") else ""}</label>'
f'<input type="text" name="ep_{_html_attr(sv["ip"])}" value="{_html_attr(eps.get(sv["ip"], ""))}" placeholder="{_html_attr(sv["ip"])}" style="width:260px"></div>' f'<input type="text" name="ep_{_html_attr(sv["ip"])}" value="{_html_attr(eps.get(sv["ip"], ""))}" placeholder="{_html_attr(sv["ip"])}" style="width:260px"></div>'
for sv in get_all_servers_ordered()) for sv in get_all_servers_ordered())
cascade_cmd = f"bash &lt;(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/phobos-cascade.sh) enable {SERVER_IP} {_load_server_env().get('OBFUSCATOR_PORTS', '2083,5443,993')}" cascade_cmd = f"bash &lt;(curl -fsSL {PCA_GIT}/raw/branch/main/server/phobos-cascade.sh) enable {SERVER_IP} {_load_server_env().get('OBFUSCATOR_PORTS', '2083,5443,993')}"
extras_html = f""" extras_html = f"""
<div class="card"> <div class="card">
<h2>{tr("Домен панели","Panel domain")} {hlp("Панель по адресу https://ваш-домен/ с бесплатным сертификатом Let's Encrypt (nginx на 443). Домен (A-запись) должен указывать на IP этого сервера, порт 80 открыт. Старый адрес http://IP:порт продолжает работать — роутеры и второй сервер ничего не заметят.", "Panel at https://your-domain/ with a free Let's Encrypt certificate (nginx on 443). The domain A record must point to this server, port 80 open. The old http://IP:port keeps working — routers and secondaries notice nothing.")}</h2> <h2>{tr("Домен панели","Panel domain")} {hlp("Панель по адресу https://ваш-домен/ с бесплатным сертификатом Let's Encrypt (nginx на 443). Домен (A-запись) должен указывать на IP этого сервера, порт 80 открыт. Старый адрес http://IP:порт продолжает работать — роутеры и второй сервер ничего не заметят.", "Panel at https://your-domain/ with a free Let's Encrypt certificate (nginx on 443). The domain A record must point to this server, port 80 open. The old http://IP:port keeps working — routers and secondaries notice nothing.")}</h2>
@@ -2847,7 +2842,7 @@ def settings_page():
<b style="color:#22c55e">stable</b> — {tr("стабильная","stable")} · <b style="color:#22c55e">stable</b> — {tr("стабильная","stable")} ·
<b style="color:#eab308">beta</b> — {tr("тест","testing")} · <b style="color:#eab308">beta</b> — {tr("тест","testing")} ·
<b style="color:#f97316">dev</b> — {tr("разработка","development")}. <b style="color:#f97316">dev</b> — {tr("разработка","development")}.
{tr("stable — без токена. Для beta/dev вставь токен подписчика (по подписке Boosty), сохрани — и жми канал.","stable needs no token. For beta/dev paste your subscriber token (Boosty), save it, then click the channel.")}</p> {tr("С 1.4.0 все каналы открыты и берутся с git.bobyrev.com (GitHub — запасной). Токен больше не нужен.","Since 1.4.0 all channels are open and served from git.bobyrev.com (GitHub as fallback). No token needed.")}</p>
<form method="post" class="form-row" style="margin-bottom:8px"> <form method="post" class="form-row" style="margin-bottom:8px">
<input type="hidden" name="action" value="save_token"> <input type="hidden" name="action" value="save_token">
<input type="password" name="gh_token" placeholder="{tr('токен подписчика (для beta/dev)','subscriber token (for beta/dev)')}" style="width:240px" autocomplete="off"> <input type="password" name="gh_token" placeholder="{tr('токен подписчика (для beta/dev)','subscriber token (for beta/dev)')}" style="width:240px" autocomplete="off">
@@ -3298,45 +3293,13 @@ def sync_missing_peers_to_secondaries(server=None):
@app.route("/download/apk") @app.route("/download/apk")
@auth_required @auth_required
def download_apk(): def download_apk():
"""Proxy PhobosWG APK from GitHub release using saved subscriber token.""" """PhobosWG APK: local copy if the server has one, else the release on Gitea."""
local = "/opt/Phobos/www/app/PhobosWG.apk" local = "/opt/Phobos/www/app/PhobosWG.apk"
if os.path.exists(local): if os.path.exists(local):
from flask import send_file from flask import send_file
return send_file(local, as_attachment=True, download_name="PhobosWG.apk", return send_file(local, as_attachment=True, download_name="PhobosWG.apk",
mimetype="application/vnd.android.package-archive") mimetype="application/vnd.android.package-archive")
try: return redirect(f"{PCA_GIT}/releases/download/{APK_TAG}/PhobosWG.apk")
gh_token = open("/opt/phobos-panel/.gh_token").read().strip()
except Exception:
gh_token = ""
if not gh_token:
# stable / no subscriber token: public release asset
return redirect("https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PhobosWG.apk")
import urllib.request as _ur, ssl as _ssl, json as _json
_ctx = _ssl.create_default_context(); _ctx.check_hostname=False; _ctx.verify_mode=_ssl.CERT_NONE
try:
# /releases (all, incl. prereleases) — /releases/latest excludes prereleases
# and the beta APK release is a prerelease -> 404. Scan all releases for .apk.
_api = _ur.Request(
"https://api.github.com/repos/andrey271192/PCA_Phobos-dev/releases?per_page=20",
headers={"Authorization": f"token {gh_token}", "Accept": "application/vnd.github+json"})
_rels = _json.loads(_ur.urlopen(_api, context=_ctx, timeout=10).read())
if isinstance(_rels, dict):
_rels = [_rels]
_url = None
for _rel in _rels:
_url = next((a["url"] for a in _rel.get("assets", []) if a["name"].endswith(".apk")), None)
if _url:
break
if not _url:
return "APK asset not found in any release", 404
_req = _ur.Request(_url, headers={"Authorization": f"token {gh_token}",
"Accept": "application/octet-stream"})
_resp = _ur.urlopen(_req, context=_ctx, timeout=120)
from flask import Response
return Response(_resp.read(), mimetype="application/vnd.android.package-archive",
headers={"Content-Disposition": "attachment; filename=PhobosWG.apk"})
except Exception as e:
return f"APK download failed: {e}", 500
@app.route("/client/<cid>/conf/<mode>") @app.route("/client/<cid>/conf/<mode>")
@@ -3385,7 +3348,7 @@ def client_phone(cid, mode):
else: else:
apk_btn = (f'<p style="margin-top:6px"><a href="/download/apk" class="btn btn-primary" ' apk_btn = (f'<p style="margin-top:6px"><a href="/download/apk" class="btn btn-primary" '
f'style="background:#16a34a">📥 {tr("Скачать PhobosWG (APK)","Download PhobosWG (APK)")}</a></p>') f'style="background:#16a34a">📥 {tr("Скачать PhobosWG (APK)","Download PhobosWG (APK)")}</a></p>')
client_btn = (f'<p style="margin-top:6px"><a href="https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PCA-Phobos-VPN.apk" ' client_btn = (f'<p style="margin-top:6px"><a href="{PCA_GIT}/releases/download/{APK_TAG}/PCA-Phobos-VPN.apk" '
f'class="btn btn-primary" target="_blank" rel="noopener">🛡 {tr("PCA Phobos VPN — наше приложение (раздельный трафик)","PCA Phobos VPN — our app (split tunnelling)")}</a></p>' f'class="btn btn-primary" target="_blank" rel="noopener">🛡 {tr("PCA Phobos VPN — наше приложение (раздельный трафик)","PCA Phobos VPN — our app (split tunnelling)")}</a></p>'
f'<p style="font-size:.8em;color:var(--muted)">{tr("Поставь любое из двух приложений и открой ссылку ниже на телефоне — профиль импортируется сам.","Install either app and open the link below on the phone — the profile imports itself.")}</p>') f'<p style="font-size:.8em;color:var(--muted)">{tr("Поставь любое из двух приложений и открой ссылку ниже на телефоне — профиль импортируется сам.","Install either app and open the link below on the phone — the profile imports itself.")}</p>')
extra = (client_btn + apk_btn + extra = (client_btn + apk_btn +

View File

@@ -71,7 +71,7 @@ Native HTTPS is supported by the primary installer:
```bash ```bash
PANEL_TLS_CERT=/etc/letsencrypt/live/example.com/fullchain.pem \ PANEL_TLS_CERT=/etc/letsencrypt/live/example.com/fullchain.pem \
PANEL_TLS_KEY=/etc/letsencrypt/live/example.com/privkey.pem \ PANEL_TLS_KEY=/etc/letsencrypt/live/example.com/privkey.pem \
bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh) bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh)
``` ```
Then the panel runs as: Then the panel runs as:

View File

@@ -3,7 +3,7 @@
# PCA Phobos — TURNKEY installer (primary / panel node) # PCA Phobos — TURNKEY installer (primary / panel node)
# #
# One command, all dependencies, from a clean VPS: # One command, all dependencies, from a clean VPS:
# bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh) # bash <(curl -fsSL https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/install.sh)
# #
# Installs, in order: # Installs, in order:
# deps -> wg-obfuscator (Ground-Zerro) -> WireGuard wg0 -> # deps -> wg-obfuscator (Ground-Zerro) -> WireGuard wg0 ->
@@ -30,33 +30,19 @@ TG_TOKEN="${TG_TOKEN:-}"
TG_CHAT="${TG_CHAT:-}" TG_CHAT="${TG_CHAT:-}"
OBF_PORTS="${OBF_PORTS:-2083,5443,993}" OBF_PORTS="${OBF_PORTS:-2083,5443,993}"
SERVER_WG_IPV4_NETWORK="${SERVER_WG_IPV4_NETWORK:-10.25.0.0/16}" SERVER_WG_IPV4_NETWORK="${SERVER_WG_IPV4_NETWORK:-10.25.0.0/16}"
# Каналы: stable (открытый, публичный repo) · beta (тест) · dev (разработка). # Источник: Gitea git.bobyrev.com (основной), GitHub — запасной.
# beta и dev живут в ПРИВАТНОМ repo PCA_Phobos-dev — доступ по токену подписчика # Каналы stable/beta/dev — ветки main/beta/dev одного публичного репозитория.
# (GH_TOKEN, read-only, выдаётся по подписке Boosty). stable — без токена. PCA_GIT="${PCA_GIT:-https://git.bobyrev.com/PCA/PCA_Phobos}"
GH_TOKEN="${GH_TOKEN:-}" GH_RAW="${GH_RAW:-https://raw.githubusercontent.com/andrey271192/PCA_Phobos}"
PUB_REPO="andrey271192/PCA_Phobos"
DEV_REPO="andrey271192/PCA_Phobos-dev"
PHOBOSWG_APK_TAG="v1.3.0-beta"
PHOBOSWG_APK_SHA256="5399de7c576d56e491a01ab9b52d53c08d30a34d1156c1cb0f4520bf34821e02"
CHANNEL="${CHANNEL:-stable}" CHANNEL="${CHANNEL:-stable}"
case "$CHANNEL" in case "$CHANNEL" in
beta) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-beta}";; stable|main|"") PCA_BRANCH="${PCA_BRANCH:-main}";;
dev) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-dev}";; *) PCA_BRANCH="${PCA_BRANCH:-$CHANNEL}";;
stable|main|"") REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-main}";;
*) REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-$CHANNEL}";;
esac esac
if [ "$REPO" = "$DEV_REPO" ] && [ -z "$GH_TOKEN" ]; then case "$PCA_BRANCH" in v[0-9]*) _kind=tag;; *) _kind=branch;; esac
echo "Канал '$CHANNEL' закрыт — это $([ "$CHANNEL" = dev ] && echo 'разработка (нестабильно)' || echo 'бета (тест)'), приватный репозиторий."
echo "Нужен read-only токен подписчика:"
echo " GH_TOKEN=ваш_токен CHANNEL=$CHANNEL bash <(curl -fsSL https://raw.githubusercontent.com/$PUB_REPO/main/install.sh)"
echo "Токен выдаётся по подписке: https://boosty.to/andrey27"
echo "Стабильная версия ставится без токена (CHANNEL=stable, по умолчанию)."
exit 1
fi
[ "$REPO" = "$DEV_REPO" ] && echo "Токен принят — закрытый канал '$CHANNEL' (приватный repo)."
PHOBOS_DIR="/opt/Phobos" PHOBOS_DIR="/opt/Phobos"
PANEL_DIR="/opt/phobos-panel" PANEL_DIR="/opt/phobos-panel"
RAW="https://raw.githubusercontent.com/${REPO}/${PCA_BRANCH}" RAW="$PCA_GIT/raw/$_kind/$PCA_BRANCH"
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; } [ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; }
@@ -235,12 +221,8 @@ fetch() {
mkdir -p "$(dirname "$2")" mkdir -p "$(dirname "$2")"
# ponytail: файл из архива рядом с install.sh — не ходим в сеть. # ponytail: файл из архива рядом с install.sh — не ходим в сеть.
if [ -f "$SRC/$1" ]; then cp -f "$SRC/$1" "$2" && return 0; fi if [ -f "$SRC/$1" ]; then cp -f "$SRC/$1" "$2" && return 0; fi
_url="$RAW/$1?cb=$(date +%s)" curl -fsSL -m20 "$RAW/$1?cb=$(date +%s)" -o "$2" && return 0
if [ -n "$GH_TOKEN" ]; then curl -fsSL -m20 "$GH_RAW/$PCA_BRANCH/$1?cb=$(date +%s)" -o "$2" && return 0
curl -fsSL -m20 -H "Authorization: token $GH_TOKEN" "$_url" -o "$2" && return 0
else
curl -fsSL -m20 "$_url" -o "$2" && return 0
fi
echo " WARN: fetch $1 failed"; return 1 echo " WARN: fetch $1 failed"; return 1
} }
fetch overlay/lib-core.sh "$PHOBOS_DIR/repo/server/scripts/lib-core.sh" && chmod +x "$PHOBOS_DIR/repo/server/scripts/lib-core.sh" fetch overlay/lib-core.sh "$PHOBOS_DIR/repo/server/scripts/lib-core.sh" && chmod +x "$PHOBOS_DIR/repo/server/scripts/lib-core.sh"
@@ -345,39 +327,9 @@ fi
sleep 2 sleep 2
# ── PhobosWG APK (beta/dev only: fetch from private release asset) ── # persist channel so phobos-update follows it
if [ "$REPO" = "$DEV_REPO" ] && [ -n "$GH_TOKEN" ]; then
mkdir -p "$PHOBOS_DIR/www/app"
_apk="$PHOBOS_DIR/www/app/PhobosWG.apk"
_cur_sha="$(sha256sum "$_apk" 2>/dev/null | awk '{print $1}')"
if [ "$_cur_sha" != "$PHOBOSWG_APK_SHA256" ]; then
echo "[APK] Refreshing PhobosWG.apk from release $PHOBOSWG_APK_TAG..."
_apk_api_url=$(curl -fsSL -m15 -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/$DEV_REPO/releases/tags/$PHOBOSWG_APK_TAG" 2>/dev/null \
| grep -B8 '"name": "PhobosWG.apk"' | grep '"url"' | head -1 | cut -d'"' -f4)
if [ -n "$_apk_api_url" ] && curl -fsSL -m120 -H "Authorization: token $GH_TOKEN" \
-H "Accept: application/octet-stream" "$_apk_api_url" -o "$_apk"; then
_new_sha="$(sha256sum "$_apk" 2>/dev/null | awk '{print $1}')"
if [ "$_new_sha" = "$PHOBOSWG_APK_SHA256" ]; then
echo "[APK] Downloaded PhobosWG.apk: $(ls -lh "$_apk" | awk '{print $5}')"
else
rm -f "$_apk"
echo "[APK] SHA mismatch after download (non-fatal)"
fi
else
echo "[APK] Download failed (non-fatal)"
fi
else
echo "[APK] PhobosWG.apk current"
fi
fi
# persist channel + subscriber token so phobos-update can reach private channels
mkdir -p "$PANEL_DIR" mkdir -p "$PANEL_DIR"
echo "$CHANNEL" > "$PANEL_DIR/.channel" 2>/dev/null || true echo "$CHANNEL" > "$PANEL_DIR/.channel" 2>/dev/null || true
if [ "$REPO" = "$DEV_REPO" ] && [ -n "$GH_TOKEN" ]; then
( umask 077; printf %s "$GH_TOKEN" > "$PANEL_DIR/.gh_token" )
fi
echo "" echo ""
echo "============================================" echo "============================================"

View File

@@ -8,7 +8,7 @@
set -uo pipefail set -uo pipefail
STATE=/etc/phobos-cascade.env STATE=/etc/phobos-cascade.env
BIN=/usr/local/sbin/phobos-cascade BIN=/usr/local/sbin/phobos-cascade
SELF_URL="${SELF_URL:-https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/server/phobos-cascade.sh}" SELF_URL="${SELF_URL:-https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/server/phobos-cascade.sh}"
TAG="phobos-cascade" TAG="phobos-cascade"
clear_rules() { clear_rules() {

View File

@@ -289,16 +289,11 @@ EOF
echo "[6/7] Setting up mini-API..." echo "[6/7] Setting up mini-API..."
# Mini-API (fetched from the main panel first, then PCA GitHub fallback) # Mini-API (fetched from the main panel first, then PCA GitHub fallback)
PCA_BRANCH="${PCA_BRANCH:-main}" PCA_BRANCH="${PCA_BRANCH:-main}"
PCA_REPO="${PCA_REPO:-andrey271192/PCA_Phobos}" RAW="${PCA_RAW_BASE:-https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/${PCA_BRANCH}}"
RAW="${PCA_RAW_BASE:-https://raw.githubusercontent.com/${PCA_REPO}/${PCA_BRANCH}}"
PANEL_BOOTSTRAP="${MAIN_PROTO}://${MAIN_SERVER}:${MAIN_PORT}/server/api.py" PANEL_BOOTSTRAP="${MAIN_PROTO}://${MAIN_SERVER}:${MAIN_PORT}/server/api.py"
if ! curl -kfsSL "$PANEL_BOOTSTRAP" -o "$PHOBOS_DIR/server/api.py"; then curl -kfsSL "$PANEL_BOOTSTRAP" -o "$PHOBOS_DIR/server/api.py" \
if [ -n "${GH_TOKEN:-}" ]; then || curl -fsSL "$RAW/server/api.py" -o "$PHOBOS_DIR/server/api.py" \
curl -fsSL -H "Authorization: token $GH_TOKEN" "$RAW/server/api.py" -o "$PHOBOS_DIR/server/api.py" || curl -fsSL "https://raw.githubusercontent.com/andrey271192/PCA_Phobos/${PCA_BRANCH}/server/api.py" -o "$PHOBOS_DIR/server/api.py"
else
curl -fsSL "$RAW/server/api.py" -o "$PHOBOS_DIR/server/api.py"
fi
fi
[ -s "$PHOBOS_DIR/server/api.py" ] || { echo "ERROR: api.py fetch failed"; exit 1; } [ -s "$PHOBOS_DIR/server/api.py" ] || { echo "ERROR: api.py fetch failed"; exit 1; }
cat > /etc/systemd/system/phobos-api.service << EOF cat > /etc/systemd/system/phobos-api.service << EOF

101
update.sh
View File

@@ -3,19 +3,14 @@
# PCA Phobos — updater / rollback # PCA Phobos — updater / rollback
# #
# phobos-update # обновить до стабильной (канал stable / main) # phobos-update # обновить до стабильной (канал stable / main)
# phobos-update stable # стабильный канал (публичный, без токена) # phobos-update stable|beta|dev # канал (ветка main/beta/dev)
# GH_TOKEN=... phobos-update beta # бета-канал (приватный, по токену подписчика)
# GH_TOKEN=... phobos-update dev # канал разработки (приватный, по токену)
# phobos-update v1.2.4 # конкретная версия (git tag) # phobos-update v1.2.4 # конкретная версия (git tag)
# phobos-update --rollback # откатить на предыдущую версию (из бэкапа) # phobos-update --rollback # откатить на предыдущую версию (из бэкапа)
# phobos-update --check # установленная + версии всех каналов # phobos-update --check # установленная + версии всех каналов
# phobos-update --list # список бэкапов для отката # phobos-update --list # список бэкапов для отката
# #
# Каналы: # Источник: Gitea git.bobyrev.com/PCA/PCA_Phobos (основной), GitHub — запасной.
# stable -> публичный репозиторий andrey271192/PCA_Phobos (открыт) # Свой адрес: PCA_GIT=https://… phobos-update
# beta / dev -> приватный andrey271192/PCA_Phobos-dev (только подписчики)
# Для beta/dev нужен read-only токен подписчика: env GH_TOKEN или файл
# $PANEL_DIR/.gh_token (сохраняется при установке закрытого канала).
# #
# Обновляет ТОЛЬКО PCA-слой (панель + скрипты). НЕ трогает ключи WireGuard, # Обновляет ТОЛЬКО PCA-слой (панель + скрипты). НЕ трогает ключи WireGuard,
# server.env, клиентов. Перед каждым обновлением — бэкап; откат восстанавливает. # server.env, клиентов. Перед каждым обновлением — бэкап; откат восстанавливает.
@@ -23,10 +18,8 @@
set -e set -e
PHOBOS_DIR=/opt/Phobos PHOBOS_DIR=/opt/Phobos
PANEL_DIR=/opt/phobos-panel PANEL_DIR=/opt/phobos-panel
PUB_REPO=andrey271192/PCA_Phobos PCA_GIT="${PCA_GIT:-https://git.bobyrev.com/PCA/PCA_Phobos}"
DEV_REPO=andrey271192/PCA_Phobos-dev GH_RAW="${GH_RAW:-https://raw.githubusercontent.com/andrey271192/PCA_Phobos}"
PHOBOSWG_APK_TAG="v1.3.0-beta"
PHOBOSWG_APK_SHA256="5399de7c576d56e491a01ab9b52d53c08d30a34d1156c1cb0f4520bf34821e02"
BACKUPS="$PHOBOS_DIR/updates" BACKUPS="$PHOBOS_DIR/updates"
VERFILE="$PANEL_DIR/.version" VERFILE="$PANEL_DIR/.version"
CUR=$(cat "$VERFILE" 2>/dev/null || echo "unknown") CUR=$(cat "$VERFILE" 2>/dev/null || echo "unknown")
@@ -46,30 +39,14 @@ restart_panel_service() {
systemctl restart "$svc" 2>/dev/null || true systemctl restart "$svc" 2>/dev/null || true
} }
# repo for a ref/channel: beta/dev/*-beta/*-dev -> private, остальное -> public # since 1.4.0 all channels are branches of one public repo (no subscriber token)
repo_for() { case "$1" in beta|dev|*-beta|*-dev) echo "$DEV_REPO";; *) echo "$PUB_REPO";; esac; } is_private() { return 1; }
is_private() { case "$1" in beta|dev|*-beta|*-dev) return 0;; *) return 1;; esac; }
# curl with token header only when ref is a private channel
gh_curl() { # $1=ref, rest=curl args
local ref="$1"; shift
if is_private "$ref" && [ -n "$GH_TOKEN" ]; then
curl -H "Authorization: token $GH_TOKEN" "$@"
else
curl "$@"
fi
}
fetch_repo_file() { # $1=ref $2=repo_path $3=output fetch_repo_file() { # $1=ref $2=repo_path $3=output ; Gitea first, GitHub fallback
local ref="$1" rp="$2" out="$3" repo local ref="$1" rp="$2" out="$3" kind=branch
repo=$(repo_for "$ref") case "$ref" in v[0-9]*) kind=tag;; esac
if is_private "$ref" && [ -n "$GH_TOKEN" ]; then curl -fsSL -m25 "$PCA_GIT/raw/$kind/$ref/$rp?cb=$(date +%s)" -o "$out" 2>/dev/null && return 0
curl -fsSL -m25 -H "Authorization: token $GH_TOKEN" \ curl -fsSL -m25 "$GH_RAW/$ref/$rp?cb=$(date +%s)" -o "$out"
-H "Accept: application/vnd.github.raw" \
"https://api.github.com/repos/$repo/contents/$rp?ref=$ref" -o "$out"
else
gh_curl "$ref" -fsSL -m25 \
"https://raw.githubusercontent.com/$repo/$ref/$rp?cb=$(date +%s)" -o "$out"
fi
} }
fetch_repo_text() { # $1=ref $2=repo_path fetch_repo_text() { # $1=ref $2=repo_path
@@ -81,38 +58,6 @@ fetch_repo_text() { # $1=ref $2=repo_path
return "$rc" return "$rc"
} }
refresh_phoboswg_apk() { # $1=ref/channel
local ref="${1:-}"
is_private "$ref" || return 0
[ -n "$GH_TOKEN" ] || return 0
local apk="$PHOBOS_DIR/www/app/PhobosWG.apk"
mkdir -p "$PHOBOS_DIR/www/app"
local cur_sha
cur_sha="$(sha256sum "$apk" 2>/dev/null | awk '{print $1}')"
if [ "$cur_sha" = "$PHOBOSWG_APK_SHA256" ]; then
echo "[APK] PhobosWG.apk current"
return 0
fi
echo "[APK] Refreshing PhobosWG.apk from release $PHOBOSWG_APK_TAG..."
local apk_api_url
apk_api_url="$(curl -fsSL -m15 -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/$DEV_REPO/releases/tags/$PHOBOSWG_APK_TAG" 2>/dev/null \
| grep -B8 '"name": "PhobosWG.apk"' | grep '"url"' | head -1 | cut -d'"' -f4)"
if [ -n "$apk_api_url" ] && curl -fsSL -m120 -H "Authorization: token $GH_TOKEN" \
-H "Accept: application/octet-stream" "$apk_api_url" -o "$apk"; then
local new_sha
new_sha="$(sha256sum "$apk" 2>/dev/null | awk '{print $1}')"
if [ "$new_sha" = "$PHOBOSWG_APK_SHA256" ]; then
echo "[APK] Downloaded PhobosWG.apk: $(ls -lh "$apk" | awk '{print $5}')"
else
rm -f "$apk"
echo "[APK] SHA mismatch after download (non-fatal)"
fi
else
echo "[APK] Download failed (non-fatal)"
fi
}
managed_files() { managed_files() {
cat <<LIST cat <<LIST
$PANEL_DIR/app.py|app.py $PANEL_DIR/app.py|app.py
@@ -180,7 +125,7 @@ chan_version() { # $1 = ref(channel)
if is_private "$1" && [ -z "$GH_TOKEN" ]; then echo ""; return; fi if is_private "$1" && [ -z "$GH_TOKEN" ]; then echo ""; return; fi
fetch_repo_text "$1" VERSION 2>/dev/null | tr -d ' \r\n' fetch_repo_text "$1" VERSION 2>/dev/null | tr -d ' \r\n'
} }
latest_version() { curl -fsSL -m10 "https://raw.githubusercontent.com/$PUB_REPO/main/VERSION" 2>/dev/null | tr -d ' \r\n'; } latest_version() { fetch_repo_text main VERSION 2>/dev/null | tr -d ' \r\n'; }
do_backup() { do_backup() {
local dir="$BACKUPS/${CUR}-$(date +%Y%m%d-%H%M%S)" local dir="$BACKUPS/${CUR}-$(date +%Y%m%d-%H%M%S)"
@@ -244,24 +189,13 @@ case "${1:-}" in
--version|-v|--check) --version|-v|--check)
echo "Установлено: $CUR" echo "Установлено: $CUR"
echo "Стабильная: $(latest_version) [открытый]" echo "Стабильная: $(latest_version) [открытый]"
b=$(chan_version beta); d=$(chan_version dev) echo "Бета (beta): $(chan_version beta)"
if [ -n "$GH_TOKEN" ]; then echo "Разработка(dev):$(chan_version dev)"
[ -n "$b" ] && echo "Бета (beta): $b [приватный, токен ок]"
[ -n "$d" ] && echo "Разработка(dev):$d [приватный, токен ок]"
else
echo "Бета (beta): — [приватный: нужен GH_TOKEN подписчика]"
echo "Разработка(dev):— [приватный: нужен GH_TOKEN подписчика]"
fi
;; ;;
--versions|--tags) --versions|--tags)
echo "Стабильные версии (публичные теги):" echo "Версии (теги):"
curl -fsSL -m10 "https://api.github.com/repos/$PUB_REPO/tags?per_page=100" 2>/dev/null \ curl -fsSL -m10 "${PCA_GIT%/*/*}/api/v1/repos/${PCA_GIT#*://*/}/tags?limit=100" 2>/dev/null \
| grep -oE '"name": *"[^"]+"' | sed 's/.*"name": *"\(.*\)"/ \1/' || echo " (нет данных)" | grep -oE '"name": *"[^"]+"' | sed 's/.*"name": *"\(.*\)"/ \1/' || echo " (нет данных)"
if [ -n "$GH_TOKEN" ]; then
echo "Закрытые версии (приватные теги, beta/dev):"
curl -fsSL -m10 -H "Authorization: token $GH_TOKEN" "https://api.github.com/repos/$DEV_REPO/tags?per_page=100" 2>/dev/null \
| grep -oE '"name": *"[^"]+"' | sed 's/.*"name": *"\(.*\)"/ \1/' || echo " (нет данных)"
fi
;; ;;
--list) --list)
echo "Бэкапы (для отката):" echo "Бэкапы (для отката):"
@@ -316,7 +250,6 @@ case "${1:-}" in
b=$(do_backup); echo "Бэкап: $b" b=$(do_backup); echo "Бэкап: $b"
apply_ref "$ref" apply_ref "$ref"
ensure_runtime_deps ensure_runtime_deps
refresh_phoboswg_apk "$ref"
run_self_check run_self_check
refresh_router_packages refresh_router_packages
echo "$target" > "$VERFILE" echo "$target" > "$VERFILE"