feat: private dev/beta channels via subscriber token (repo split)

Closed channels (beta, dev) now live in a separate PRIVATE repository
andrey271192/PCA_Phobos-dev. Access is gated by a read-only subscriber
GH_TOKEN (distributed via Boosty) instead of a shared hash key — the
token both authenticates the private raw fetch AND acts as the gate, so
there is a single secret and the code is genuinely hidden (public repo
holds stable/main only).

install.sh: CHANNEL=beta|dev -> private repo, requires GH_TOKEN, persists
it to $PANEL_DIR/.gh_token + writes .channel. stable stays public/no-token.
update.sh: per-ref repo routing (repo_for), gh_curl injects the token for
private channels, token read from env or saved .gh_token, --check/--versions
reflect public vs private, stable path unchanged and tokenless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Андрей Бобырев
2026-05-31 01:12:32 +03:00
parent 7b941dc532
commit d072c8227f
2 changed files with 105 additions and 54 deletions

View File

@@ -25,31 +25,31 @@ PANEL_PASS="${PANEL_PASS:-OcAdmin2026!}"
TG_TOKEN="${TG_TOKEN:-}"
TG_CHAT="${TG_CHAT:-}"
OBF_PORTS="${OBF_PORTS:-2083,5443,993}"
# Каналы: stable (открытый, ветка main) · beta (тест) · dev (разработка).
# beta и dev ЗАКРЫТЫ ключом подписчика PHOBOS_KEY (по подписке Boosty). stable — без ключа.
# Каналы: 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"
CHANNEL="${CHANNEL:-stable}"
if [ -z "${PCA_BRANCH:-}" ]; then
case "$CHANNEL" in beta) PCA_BRANCH="beta";; dev) PCA_BRANCH="dev";; stable|main|"") PCA_BRANCH="main";; *) PCA_BRANCH="$CHANNEL";; esac
fi
PHOBOS_KEY_HASH="cb3ce498b8d2c67ec7b57a9b08ed2fb410c881bcb9bc9cf50d09be1d9727333a"
case "$PCA_BRANCH" in
beta|dev)
_got=$(printf %s "${PHOBOS_KEY:-}" | { sha256sum 2>/dev/null || shasum -a 256; } | cut -d' ' -f1)
if [ "$_got" != "$PHOBOS_KEY_HASH" ]; then
echo "Канал '$CHANNEL' закрыт — это $([ "$CHANNEL" = dev ] && echo 'разработка (нестабильно)' || echo 'бета (тест)')."
echo "Нужен ключ подписчика:"
echo " PHOBOS_KEY=ваш_ключ CHANNEL=$CHANNEL bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh)"
echo "Ключ выдаётся по подписке: https://boosty.to/andrey27"
echo "Стабильная версия ставится без ключа (CHANNEL=stable, по умолчанию)."
exit 1
fi
echo "Ключ принят — канал '$CHANNEL'."
;;
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}";;
esac
GH_TOKEN="${GH_TOKEN:-}" # set for private-repo installs; empty for public
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)."
PHOBOS_DIR="/opt/Phobos"
PANEL_DIR="/opt/phobos-panel"
RAW="https://raw.githubusercontent.com/andrey271192/PCA_Phobos/${PCA_BRANCH}"
RAW="https://raw.githubusercontent.com/${REPO}/${PCA_BRANCH}"
[ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; }
@@ -260,6 +260,13 @@ if [ -f "$PHOBOS_DIR/server/phobos-router-watchdog.py" ]; then
fi
sleep 2
# persist channel + subscriber token so phobos-update can reach private channels
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 "============================================"
echo " Installation complete"

110
update.sh
View File

@@ -2,24 +2,47 @@
# ============================================================
# PCA Phobos — updater / rollback
#
# phobos-update # обновить до последней версии (main)
# phobos-update v1.1.0 # обновить до конкретной версии (git tag)
# phobos-update # обновить до стабильной (канал stable / main)
# phobos-update stable # стабильный канал (публичный, без токена)
# GH_TOKEN=... phobos-update beta # бета-канал (приватный, по токену подписчика)
# GH_TOKEN=... phobos-update dev # канал разработки (приватный, по токену)
# phobos-update v1.2.4 # конкретная версия (git tag)
# phobos-update --rollback # откатить на предыдущую версию (из бэкапа)
# phobos-update --version # показать установленную и последнюю версию
# phobos-update --check # установленная + версии всех каналов
# phobos-update --list # список бэкапов для отката
#
# Обновляет ТОЛЬКО PCA-слой (панель + скрипты онбординга/служебные).
# НЕ трогает ключи WireGuard, server.env, клиентов — они сохраняются.
# Перед каждым обновлением делает бэкап; откат восстанавливает его.
# Каналы:
# stable -> публичный репозиторий andrey271192/PCA_Phobos (открыт)
# beta / dev -> приватный andrey271192/PCA_Phobos-dev (только подписчики)
# Для beta/dev нужен read-only токен подписчика: env GH_TOKEN или файл
# $PANEL_DIR/.gh_token (сохраняется при установке закрытого канала).
#
# Обновляет ТОЛЬКО PCA-слой (панель + скрипты). НЕ трогает ключи WireGuard,
# server.env, клиентов. Перед каждым обновлением — бэкап; откат восстанавливает.
# ============================================================
set -e
PHOBOS_DIR=/opt/Phobos
PANEL_DIR=/opt/phobos-panel
REPO=andrey271192/PCA_Phobos
RAW="https://raw.githubusercontent.com/$REPO"
PUB_REPO=andrey271192/PCA_Phobos
DEV_REPO=andrey271192/PCA_Phobos-dev
BACKUPS="$PHOBOS_DIR/updates"
VERFILE="$PANEL_DIR/.version"
CUR=$(cat "$VERFILE" 2>/dev/null || echo "unknown")
# Токен подписчика для приватных каналов: env > сохранённый файл
GH_TOKEN="${GH_TOKEN:-$(cat "$PANEL_DIR/.gh_token" 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
}
managed_files() {
cat <<LIST
@@ -35,7 +58,13 @@ $PHOBOS_DIR/server/update.sh|update.sh
LIST
}
latest_version() { curl -fsSL -m10 "$RAW/main/VERSION" 2>/dev/null | tr -d ' \r\n'; }
# fetch VERSION of a channel (public repos / or private if token present)
chan_version() { # $1 = ref(channel)
local repo; repo=$(repo_for "$1"); local repo_ref="$1"
if is_private "$1" && [ -z "$GH_TOKEN" ]; then echo ""; return; fi
gh_curl "$1" -fsSL -m8 "https://raw.githubusercontent.com/$repo/$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'; }
do_backup() {
local dir="$BACKUPS/${CUR}-$(date +%Y%m%d-%H%M%S)"
@@ -48,10 +77,12 @@ do_backup() {
}
apply_ref() {
local ref="$1"
local ref="$1"; local repo; repo=$(repo_for "$ref")
local repo_ref="$ref"
local RAW="https://raw.githubusercontent.com/$repo/$ref"
managed_files | while IFS='|' read -r lp rp; do
local tmp; tmp=$(mktemp)
if curl -fsSL -m25 "$RAW/$ref/$rp" -o "$tmp" && [ -s "$tmp" ]; then
if gh_curl "$ref" -fsSL -m25 "$RAW/$rp" -o "$tmp" && [ -s "$tmp" ]; then
mkdir -p "$(dirname "$lp")"; mv "$tmp" "$lp"
case "$lp" in *.sh|*.py) chmod +x "$lp" 2>/dev/null || true;; esac
else
@@ -60,21 +91,33 @@ apply_ref() {
done
}
ref_exists() { curl -fsSL -m12 "$RAW/$1/app.py" -o /dev/null 2>/dev/null; }
ref_exists() {
local repo; repo=$(repo_for "$1"); local repo_ref="$1"
gh_curl "$1" -fsSL -m12 "https://raw.githubusercontent.com/$repo/$1/app.py" -o /dev/null 2>/dev/null
}
case "${1:-}" in
--version|-v|--check)
echo "Установлено: $CUR"
echo "Стабильная: $(latest_version)"
beta=$(curl -fsSL -m8 "$RAW/beta/VERSION" 2>/dev/null | tr -d ' \r\n')
[ -n "$beta" ] && echo "Бета (beta): $beta [закрыт ключом]"
dev=$(curl -fsSL -m8 "$RAW/dev/VERSION" 2>/dev/null | tr -d ' \r\n')
[ -n "$dev" ] && echo "Разработка(dev):$dev [закрыт ключом]"
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
;;
--versions|--tags)
echo "Доступные версии (git tags):"
curl -fsSL -m10 "https://api.github.com/repos/$REPO/tags?per_page=100" 2>/dev/null \
echo "Стабильные версии (публичные теги):"
curl -fsSL -m10 "https://api.github.com/repos/$PUB_REPO/tags?per_page=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 "Бэкапы (для отката):"
@@ -95,24 +138,25 @@ case "${1:-}" in
*)
ref="${1:-main}"
case "$ref" in latest|stable) ref="main";; esac
# Каналы beta/dev закрыты ключом подписчика PHOBOS_KEY (Boosty)
case "$ref" in
beta|dev|*-beta|*-dev)
_got=$(printf %s "${PHOBOS_KEY:-}" | { sha256sum 2>/dev/null || shasum -a 256; } | cut -d' ' -f1)
if [ "$_got" != "cb3ce498b8d2c67ec7b57a9b08ed2fb410c881bcb9bc9cf50d09be1d9727333a" ]; then
echo "Канал '$ref' закрыт (бета/разработка). Нужен ключ подписчика:"
echo " PHOBOS_KEY=ваш_ключ phobos-update $ref"
echo "Ключ — по подписке: https://boosty.to/andrey27 . Стабильная: phobos-update stable"
exit 1
fi
;;
esac
# Каналы beta/dev — приватный репозиторий, нужен токен подписчика
if is_private "$ref" && [ -z "$GH_TOKEN" ]; then
echo "Канал '$ref' закрыт (бета/разработка) — приватный репозиторий."
echo "Нужен read-only токен подписчика:"
echo " GH_TOKEN=ваш_токен phobos-update $ref"
echo "Токен — по подписке: https://boosty.to/andrey27 . Стабильная (без токена): phobos-update stable"
exit 1
fi
if ! ref_exists "$ref"; then
echo "Версия/ветка '$ref' не найдена на GitHub. Обновление отменено (текущая $CUR не тронута)."
echo "Версия/ветка '$ref' не найдена (или токен неверный). Обновление отменено (текущая $CUR не тронута)."
echo "Доступные: phobos-update --versions · стабильная: $(latest_version)"
exit 1
fi
target=$(curl -fsSL -m10 "$RAW/$ref/VERSION" 2>/dev/null | tr -d ' \r\n'); [ -z "$target" ] && target="$ref"
# сохранить токен для будущих обновлений приватного канала
if is_private "$ref" && [ -n "$GH_TOKEN" ]; then
( umask 077; printf %s "$GH_TOKEN" > "$PANEL_DIR/.gh_token" )
fi
repo_ref="$ref"
target=$(gh_curl "$ref" -fsSL -m10 "https://raw.githubusercontent.com/$(repo_for "$ref")/$ref/VERSION" 2>/dev/null | tr -d ' \r\n'); [ -z "$target" ] && target="$ref"
echo "Текущая: $CUR -> целевая: $target ($ref)"
b=$(do_backup); echo "Бэкап: $b"
apply_ref "$ref"