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, одной командой)
Ставит **всё** со всеми зависимостями: wg-obfuscator, WireGuard, обфускатор-сервисы, веб-панель, nginx, скрипты онбординга роутеров, сторож авто-восстановления. Предустановленный Phobos **не требуется**.
```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
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
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>`.
@@ -116,7 +121,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
На входном VPS (чистый Debian/Ubuntu, Phobos не нужен):
```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`.
@@ -131,8 +136,8 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
## Приложения для Android
- **PCA Phobos VPN** — клиент: включает VPN одной кнопкой, импорт `phobos://` из панели, встроенный обфускатор, **раздельный трафик** (весь / только выбранные приложения / все кроме выбранных), журнал, проверка скорости. [Скачать APK](https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PCA-Phobos-VPN.apk) · исходники `android-client/`.
- **PCA Phobos** (админ) — список ваших панелей, открываются в приложении. [Скачать APK](https://github.com/andrey271192/PCA_Phobos/releases/latest/download/PCA-Phobos.apk) · исходники `android/`.
- **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://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"
SECRET_FILE = f"{PANEL_DIR}/.secret_key"
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_CUSTOM_PROFILE_FILE = f"{WARP_DIR}/custom-profile.conf"
WARP_LICENSE_FILE = f"{WARP_DIR}/license.key"
@@ -1058,26 +1062,17 @@ def _find_update_runner():
def _restore_update_script(ref):
repo, branch = _update_repo_for_ref(ref)
token = ""
try:
token = Path(GH_TOKEN_FILE).read_text().strip()
except Exception:
pass
cmd = ["curl", "-fsSL", "-m", "25"]
if repo.endswith("-dev") and token:
cmd += ["-H", f"Authorization: token {token}"]
cmd += ["-H", "Accept: application/vnd.github.raw"]
url = f"https://api.github.com/repos/{repo}/contents/update.sh?ref={branch}"
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}"
_repo, branch = _update_repo_for_ref(ref)
kind = "tag" if branch.startswith("v") else "branch"
r = None
for url in (f"{PCA_GIT}/raw/{kind}/{branch}/update.sh", f"{PCA_GH_RAW}/{branch}/update.sh"):
r = subprocess.run(["curl", "-fsSL", "-m", "25", f"{url}?cb={int(time.time())}"],
capture_output=True, text=True, timeout=35)
if r.returncode == 0 and (r.stdout or "").startswith("#!"):
break
if r is None or r.returncode != 0 or not (r.stdout or "").startswith("#!"):
return False, f"update helper restore failed for {branch}: {(r.stderr if r else '') or 'download failed'}"
repo = PCA_GIT
os.makedirs(os.path.dirname(UPDATE_SCRIPT), exist_ok=True)
tmp = UPDATE_SCRIPT + ".tmp"
Path(tmp).write_text(r.stdout)
@@ -1812,7 +1807,7 @@ textarea::placeholder{color:var(--muted)}
</div>
CONTENT
<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>
<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>
@@ -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'<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())
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"""
<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>
@@ -2847,7 +2842,7 @@ def settings_page():
<b style="color:#22c55e">stable</b> — {tr("стабильная","stable")} ·
<b style="color:#eab308">beta</b> — {tr("тест","testing")} ·
<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">
<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">
@@ -3298,45 +3293,13 @@ def sync_missing_peers_to_secondaries(server=None):
@app.route("/download/apk")
@auth_required
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"
if os.path.exists(local):
from flask import send_file
return send_file(local, as_attachment=True, download_name="PhobosWG.apk",
mimetype="application/vnd.android.package-archive")
try:
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
return redirect(f"{PCA_GIT}/releases/download/{APK_TAG}/PhobosWG.apk")
@app.route("/client/<cid>/conf/<mode>")
@@ -3385,7 +3348,7 @@ def client_phone(cid, mode):
else:
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>')
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'<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 +

View File

@@ -71,7 +71,7 @@ Native HTTPS is supported by the primary installer:
```bash
PANEL_TLS_CERT=/etc/letsencrypt/live/example.com/fullchain.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:

View File

@@ -3,7 +3,7 @@
# PCA Phobos — TURNKEY installer (primary / panel node)
#
# 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:
# deps -> wg-obfuscator (Ground-Zerro) -> WireGuard wg0 ->
@@ -30,33 +30,19 @@ TG_TOKEN="${TG_TOKEN:-}"
TG_CHAT="${TG_CHAT:-}"
OBF_PORTS="${OBF_PORTS:-2083,5443,993}"
SERVER_WG_IPV4_NETWORK="${SERVER_WG_IPV4_NETWORK:-10.25.0.0/16}"
# Каналы: stable (открытый, публичный repo) · beta (тест) · dev (разработка).
# beta и dev живут в ПРИВАТНОМ repo PCA_Phobos-dev — доступ по токену подписчика
# (GH_TOKEN, read-only, выдаётся по подписке Boosty). stable — без токена.
GH_TOKEN="${GH_TOKEN:-}"
PUB_REPO="andrey271192/PCA_Phobos"
DEV_REPO="andrey271192/PCA_Phobos-dev"
PHOBOSWG_APK_TAG="v1.3.0-beta"
PHOBOSWG_APK_SHA256="5399de7c576d56e491a01ab9b52d53c08d30a34d1156c1cb0f4520bf34821e02"
# Источник: Gitea git.bobyrev.com (основной), GitHub — запасной.
# Каналы stable/beta/dev — ветки main/beta/dev одного публичного репозитория.
PCA_GIT="${PCA_GIT:-https://git.bobyrev.com/PCA/PCA_Phobos}"
GH_RAW="${GH_RAW:-https://raw.githubusercontent.com/andrey271192/PCA_Phobos}"
CHANNEL="${CHANNEL:-stable}"
case "$CHANNEL" in
beta) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-beta}";;
dev) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-dev}";;
stable|main|"") REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-main}";;
*) REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-$CHANNEL}";;
stable|main|"") PCA_BRANCH="${PCA_BRANCH:-main}";;
*) PCA_BRANCH="${PCA_BRANCH:-$CHANNEL}";;
esac
if [ "$REPO" = "$DEV_REPO" ] && [ -z "$GH_TOKEN" ]; then
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)."
case "$PCA_BRANCH" in v[0-9]*) _kind=tag;; *) _kind=branch;; esac
PHOBOS_DIR="/opt/Phobos"
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)"
[ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; }
@@ -235,12 +221,8 @@ fetch() {
mkdir -p "$(dirname "$2")"
# ponytail: файл из архива рядом с install.sh — не ходим в сеть.
if [ -f "$SRC/$1" ]; then cp -f "$SRC/$1" "$2" && return 0; fi
_url="$RAW/$1?cb=$(date +%s)"
if [ -n "$GH_TOKEN" ]; then
curl -fsSL -m20 -H "Authorization: token $GH_TOKEN" "$_url" -o "$2" && return 0
else
curl -fsSL -m20 "$_url" -o "$2" && return 0
fi
curl -fsSL -m20 "$RAW/$1?cb=$(date +%s)" -o "$2" && return 0
curl -fsSL -m20 "$GH_RAW/$PCA_BRANCH/$1?cb=$(date +%s)" -o "$2" && return 0
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"
@@ -345,39 +327,9 @@ fi
sleep 2
# ── PhobosWG APK (beta/dev only: fetch from private release asset) ──
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
# persist channel so phobos-update follows it
mkdir -p "$PANEL_DIR"
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 "============================================"

View File

@@ -8,7 +8,7 @@
set -uo pipefail
STATE=/etc/phobos-cascade.env
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"
clear_rules() {

View File

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

101
update.sh
View File

@@ -3,19 +3,14 @@
# PCA Phobos — updater / rollback
#
# phobos-update # обновить до стабильной (канал stable / main)
# phobos-update stable # стабильный канал (публичный, без токена)
# GH_TOKEN=... phobos-update beta # бета-канал (приватный, по токену подписчика)
# GH_TOKEN=... phobos-update dev # канал разработки (приватный, по токену)
# phobos-update stable|beta|dev # канал (ветка main/beta/dev)
# phobos-update v1.2.4 # конкретная версия (git tag)
# phobos-update --rollback # откатить на предыдущую версию (из бэкапа)
# phobos-update --check # установленная + версии всех каналов
# phobos-update --list # список бэкапов для отката
#
# Каналы:
# stable -> публичный репозиторий andrey271192/PCA_Phobos (открыт)
# beta / dev -> приватный andrey271192/PCA_Phobos-dev (только подписчики)
# Для beta/dev нужен read-only токен подписчика: env GH_TOKEN или файл
# $PANEL_DIR/.gh_token (сохраняется при установке закрытого канала).
# Источник: Gitea git.bobyrev.com/PCA/PCA_Phobos (основной), GitHub — запасной.
# Свой адрес: PCA_GIT=https://… phobos-update
#
# Обновляет ТОЛЬКО PCA-слой (панель + скрипты). НЕ трогает ключи WireGuard,
# server.env, клиентов. Перед каждым обновлением — бэкап; откат восстанавливает.
@@ -23,10 +18,8 @@
set -e
PHOBOS_DIR=/opt/Phobos
PANEL_DIR=/opt/phobos-panel
PUB_REPO=andrey271192/PCA_Phobos
DEV_REPO=andrey271192/PCA_Phobos-dev
PHOBOSWG_APK_TAG="v1.3.0-beta"
PHOBOSWG_APK_SHA256="5399de7c576d56e491a01ab9b52d53c08d30a34d1156c1cb0f4520bf34821e02"
PCA_GIT="${PCA_GIT:-https://git.bobyrev.com/PCA/PCA_Phobos}"
GH_RAW="${GH_RAW:-https://raw.githubusercontent.com/andrey271192/PCA_Phobos}"
BACKUPS="$PHOBOS_DIR/updates"
VERFILE="$PANEL_DIR/.version"
CUR=$(cat "$VERFILE" 2>/dev/null || echo "unknown")
@@ -46,30 +39,14 @@ restart_panel_service() {
systemctl restart "$svc" 2>/dev/null || true
}
# repo for a ref/channel: beta/dev/*-beta/*-dev -> private, остальное -> public
repo_for() { case "$1" in beta|dev|*-beta|*-dev) echo "$DEV_REPO";; *) echo "$PUB_REPO";; esac; }
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
}
# since 1.4.0 all channels are branches of one public repo (no subscriber token)
is_private() { return 1; }
fetch_repo_file() { # $1=ref $2=repo_path $3=output
local ref="$1" rp="$2" out="$3" repo
repo=$(repo_for "$ref")
if is_private "$ref" && [ -n "$GH_TOKEN" ]; then
curl -fsSL -m25 -H "Authorization: token $GH_TOKEN" \
-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_file() { # $1=ref $2=repo_path $3=output ; Gitea first, GitHub fallback
local ref="$1" rp="$2" out="$3" kind=branch
case "$ref" in v[0-9]*) kind=tag;; esac
curl -fsSL -m25 "$PCA_GIT/raw/$kind/$ref/$rp?cb=$(date +%s)" -o "$out" 2>/dev/null && return 0
curl -fsSL -m25 "$GH_RAW/$ref/$rp?cb=$(date +%s)" -o "$out"
}
fetch_repo_text() { # $1=ref $2=repo_path
@@ -81,38 +58,6 @@ fetch_repo_text() { # $1=ref $2=repo_path
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() {
cat <<LIST
$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
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() {
local dir="$BACKUPS/${CUR}-$(date +%Y%m%d-%H%M%S)"
@@ -244,24 +189,13 @@ case "${1:-}" in
--version|-v|--check)
echo "Установлено: $CUR"
echo "Стабильная: $(latest_version) [открытый]"
b=$(chan_version beta); d=$(chan_version dev)
if [ -n "$GH_TOKEN" ]; then
[ -n "$b" ] && echo "Бета (beta): $b [приватный, токен ок]"
[ -n "$d" ] && echo "Разработка(dev):$d [приватный, токен ок]"
else
echo "Бета (beta): — [приватный: нужен GH_TOKEN подписчика]"
echo "Разработка(dev):— [приватный: нужен GH_TOKEN подписчика]"
fi
echo "Бета (beta): $(chan_version beta)"
echo "Разработка(dev):$(chan_version dev)"
;;
--versions|--tags)
echo "Стабильные версии (публичные теги):"
curl -fsSL -m10 "https://api.github.com/repos/$PUB_REPO/tags?per_page=100" 2>/dev/null \
echo "Версии (теги):"
curl -fsSL -m10 "${PCA_GIT%/*/*}/api/v1/repos/${PCA_GIT#*://*/}/tags?limit=100" 2>/dev/null \
| 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)
echo "Бэкапы (для отката):"
@@ -316,7 +250,6 @@ case "${1:-}" in
b=$(do_backup); echo "Бэкап: $b"
apply_ref "$ref"
ensure_runtime_deps
refresh_phoboswg_apk "$ref"
run_self_check
refresh_router_packages
echo "$target" > "$VERFILE"