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_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}"
# Каналы: stable (открытый, ветка main) · beta (тест) · dev (разработка). # Каналы: stable (открытый, публичный repo) · beta (тест) · dev (разработка).
# beta и dev ЗАКРЫТЫ ключом подписчика PHOBOS_KEY (по подписке Boosty). stable — без ключа. # 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}" CHANNEL="${CHANNEL:-stable}"
if [ -z "${PCA_BRANCH:-}" ]; then case "$CHANNEL" in
case "$CHANNEL" in beta) PCA_BRANCH="beta";; dev) PCA_BRANCH="dev";; stable|main|"") PCA_BRANCH="main";; *) PCA_BRANCH="$CHANNEL";; esac beta) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-beta}";;
fi dev) REPO="$DEV_REPO"; PCA_BRANCH="${PCA_BRANCH:-dev}";;
PHOBOS_KEY_HASH="cb3ce498b8d2c67ec7b57a9b08ed2fb410c881bcb9bc9cf50d09be1d9727333a" stable|main|"") REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-main}";;
case "$PCA_BRANCH" in *) REPO="$PUB_REPO"; PCA_BRANCH="${PCA_BRANCH:-$CHANNEL}";;
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'."
;;
esac 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" PHOBOS_DIR="/opt/Phobos"
PANEL_DIR="/opt/phobos-panel" 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; } [ "$EUID" -eq 0 ] || { echo "Run as root"; exit 1; }
@@ -260,6 +260,13 @@ if [ -f "$PHOBOS_DIR/server/phobos-router-watchdog.py" ]; then
fi fi
sleep 2 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 "============================================" echo "============================================"
echo " Installation complete" echo " Installation complete"

110
update.sh
View File

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