release 1.5.0: one panel in all channels; Apple apps (iOS, new macOS tab) only in beta/dev

- stable gets the beta panel and server features (new design, presets, firewall,
  monthly traffic, router failover over all ports/addresses + TCP, altip, HR tab,
  Windows tab, update banner); ios/ macos/ windows/ sources stay out of stable
- menu item "Update to N" under Settings when a new version is out
- mobile: top menu no longer stretches to 200px
- urllib User-Agent: Cloudflare in front of git.bobyrev.com returns 403 to Python-urllib
- docs: GitHub adleibzon-rgb as source, password/uninstall/devices sections,
  new screenshots, PORTS.md TCP fallback and second IP

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrei Leibzon
2026-11-11 18:12:13 +03:00
parent 22537b3aad
commit 1f3eced228
104 changed files with 2499 additions and 8256 deletions

View File

@@ -23,6 +23,20 @@ resolve_install_names() {
fi
if [ -f "$binary_path" ] && [ -f "$PHOBOS_DIR/${CLIENT_NAME}.conf" ]; then
# 02.11.2026: переустановка — берём те же имена, что у этой учётки (по *.link),
# иначе доп. учётка затёрла бы обфускатор главной
local l n sc
for l in "$PHOBOS_DIR"/wg-obfuscator?*.link; do
[ "$(cat "$l" 2>/dev/null)" = "${CLIENT_NAME}" ] || continue
n=$(basename "$l" .link); n=${n#wg-obfuscator}
sc=$(grep -l "/wg-obfuscator${n}.conf\"\$" /opt/etc/init.d/S*wg-obfuscator 2>/dev/null | head -1)
OBF_BINARY_NAME="wg-obfuscator${n}"
OBF_CONF_NAME="wg-obfuscator${n}.conf"
OBF_INIT_NAME=$(basename "${sc:-S50wg-obfuscator}")
OBF_SERVICE_NAME="phobos-obfuscator${n}"
OBF_WG_IFACE="phobos${n}"
return
done
return
fi

View File

@@ -417,6 +417,16 @@ show_final_info() {
}
# Убирает из crontab строки health/pull только этой учётки ($1 = "PHOBOS_ACC=<dir> "
# для доп. учётки, "PHOBOS_ACC=" для главной — у неё строки без PHOBOS_ACC).
drop_own_cron() {
if [ "$1" = "PHOBOS_ACC=" ]; then
awk '!/phobos-(health|pull)/ || /PHOBOS_ACC=/'
else
grep -v -F "$1"
fi
}
deploy_health_monitor() {
log "Развертывание монитора здоровья Phobos..."
@@ -428,12 +438,19 @@ deploy_health_monitor() {
cp "phobos-health.sh" "$PHOBOS_DIR/phobos-health.sh"
chmod +x "$PHOBOS_DIR/phobos-health.sh"
if [ -f "failover.conf" ]; then
cp "failover.conf" "$PHOBOS_DIR/failover.conf"
chmod 600 "$PHOBOS_DIR/failover.conf"
# 02.11.2026: вторая и следующие учётки на одном роутере — свой каталог
# (failover.conf, state, client_id, panel_*), свои строки cron с PHOBOS_ACC.
local A="$PHOBOS_DIR" envp="" mine="PHOBOS_ACC="
local main_cid=$(cat "$A/client_id" 2>/dev/null)
if [ -n "$main_cid" ] && [ "$main_cid" != "{{CLIENT_NAME}}" ]; then
A="$PHOBOS_DIR/acc/{{CLIENT_NAME}}"; envp="PHOBOS_ACC=$A "; mine="PHOBOS_ACC=$A "
log " Дополнительная учётка: $A (главная — $main_cid)"
fi
mkdir -p "$A/state"
if [ -f "failover.conf" ]; then
cp "failover.conf" "$A/failover.conf"
chmod 600 "$A/failover.conf"
fi
mkdir -p "$PHOBOS_DIR/state"
# --- Phobos pull agent: panel -> router config sync (NAT-friendly) ---
# Routers behind NAT cannot be reached by the panel over SSH, so they PULL
@@ -441,18 +458,18 @@ deploy_health_monitor() {
if [ -f "phobos-pull.sh" ]; then
cp "phobos-pull.sh" "$PHOBOS_DIR/phobos-pull.sh"
chmod +x "$PHOBOS_DIR/phobos-pull.sh"
echo "{{CLIENT_NAME}}" > "$PHOBOS_DIR/client_id"
[ -f "pull_token" ] && cp "pull_token" "$PHOBOS_DIR/pull_token"
[ -f "panel_tunnel_ip" ] && cp "panel_tunnel_ip" "$PHOBOS_DIR/panel_tunnel_ip"
[ -f "panel_port" ] && cp "panel_port" "$PHOBOS_DIR/panel_port"
[ -f "panel_scheme" ] && cp "panel_scheme" "$PHOBOS_DIR/panel_scheme"
[ -f "panel_url" ] && cp "panel_url" "$PHOBOS_DIR/panel_url"
[ -f "panel_urls" ] && cp "panel_urls" "$PHOBOS_DIR/panel_urls"
echo "{{CLIENT_NAME}}" > "$A/client_id"
[ -f "pull_token" ] && cp "pull_token" "$A/pull_token"
[ -f "panel_tunnel_ip" ] && cp "panel_tunnel_ip" "$A/panel_tunnel_ip"
[ -f "panel_port" ] && cp "panel_port" "$A/panel_port"
[ -f "panel_scheme" ] && cp "panel_scheme" "$A/panel_scheme"
[ -f "panel_url" ] && cp "panel_url" "$A/panel_url"
[ -f "panel_urls" ] && cp "panel_urls" "$A/panel_urls"
log " Pull-агент установлен (client_id={{CLIENT_NAME}})"
fi
local cron_line="*/1 * * * * $PHOBOS_DIR/phobos-health.sh"
local pull_line="*/1 * * * * $PHOBOS_DIR/phobos-pull.sh"
local cron_line="*/1 * * * * ${envp}$PHOBOS_DIR/phobos-health.sh"
local pull_line="*/1 * * * * ${envp}$PHOBOS_DIR/phobos-pull.sh"
[ -f "$PHOBOS_DIR/phobos-pull.sh" ] || pull_line=""
if [ "$ROUTER_PLATFORM" = "keenetic" ]; then
@@ -468,21 +485,21 @@ deploy_health_monitor() {
mkdir -p /opt/var/spool/cron/crontabs
# 01.11.2026: демоны cron на Keenetic (busybox crond, Entware cron) читают crontab
# пользователя root, а не /opt/etc/crontab — туда писать бесполезно
sed -i "/phobos-health\|phobos-pull/d" /opt/etc/crontab 2>/dev/null || true
(crontab -l 2>/dev/null | grep -v "phobos-health" | grep -v "phobos-pull"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab -
[ -z "$envp" ] && sed -i "/phobos-health\|phobos-pull/d" /opt/etc/crontab 2>/dev/null || true
(crontab -l 2>/dev/null | drop_own_cron "$mine"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab -
log " Cron добавлен через crontab (health + pull)"
/opt/etc/init.d/S10cron restart >/dev/null 2>&1 || true
elif [ "$ROUTER_PLATFORM" = "openwrt" ]; then
(crontab -l 2>/dev/null | grep -v "phobos-health" | grep -v "phobos-pull"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab -
(crontab -l 2>/dev/null | drop_own_cron "$mine"; echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line") | crontab -
log " Cron добавлен через crontab (health + pull)"
elif [ "$ROUTER_PLATFORM" = "linux" ]; then
{ echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line"; } > /etc/cron.d/phobos-health
chmod 644 /etc/cron.d/phobos-health
{ echo "$cron_line"; [ -n "$pull_line" ] && echo "$pull_line"; } > /etc/cron.d/phobos-health${envp:+-{{CLIENT_NAME}}}
chmod 644 /etc/cron.d/phobos-health*
log " Cron добавлен в /etc/cron.d/phobos-health (health + pull)"
fi
log " Монитор здоровья: $PHOBOS_DIR/phobos-health.sh"
log " Failover конфиг: $PHOBOS_DIR/failover.conf"
log " Failover конфиг: $A/failover.conf"
log " Проверка каждую минуту через cron"
}

View File

@@ -1,4 +1,5 @@
#!/bin/sh
export PATH=/opt/bin:/opt/sbin:/opt/usr/bin:$PATH # cron без /opt/bin -> busybox wget висел вечно (09.29)
# ============================================================
# Phobos Config Pull — NAT-friendly management channel.
# Runs ON the router via cron (every 2 min). OUTBOUND ONLY:
@@ -21,9 +22,11 @@
# PANEL env or default below -> panel base URL
# ============================================================
PHOBOS_DIR="/opt/etc/Phobos"
CONF="$PHOBOS_DIR/failover.conf"
# 02.11.2026: доп. учётки — PHOBOS_ACC=$PHOBOS_DIR/acc/<клиент> (см. phobos-health.sh)
ACC_DIR="${PHOBOS_ACC:-$PHOBOS_DIR}"
CONF="$ACC_DIR/failover.conf"
HEALTH="$PHOBOS_DIR/phobos-health.sh"
LOG="$PHOBOS_DIR/health.log"
LOG="$ACC_DIR/health.log"
# Config sources, tried in order. TUNNEL FIRST: panel_tunnel_ip is the wg0 of
# whatever server the tunnel currently terminates on, so management rides the
# same obfuscated channel as data — survives a public-IP ban and follows
@@ -31,15 +34,18 @@ LOG="$PHOBOS_DIR/health.log"
# - panel_tunnel_ip:8444 -> secondary server agent (phobos-api)
# - panel_tunnel_ip:<panel_port> -> primary server panel
# - public panel IP -> bootstrap / if tunnel is down
PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$PHOBOS_DIR/panel_tunnel_ip" 2>/dev/null)}"
PANEL_TUNNEL_IP="${PANEL_TUNNEL_IP:-$(cat "$ACC_DIR/panel_tunnel_ip" 2>/dev/null)}"
[ -n "$PANEL_TUNNEL_IP" ] || PANEL_TUNNEL_IP="10.25.0.1"
PANEL="${PANEL:-$(cat "$PHOBOS_DIR/panel_url" 2>/dev/null)}"
PANEL_URLS="${PANEL_URLS:-$(cat "$PHOBOS_DIR/panel_urls" 2>/dev/null)}"
PANEL="${PANEL:-$(cat "$ACC_DIR/panel_url" 2>/dev/null)}"
PANEL_URLS="${PANEL_URLS:-$(cat "$ACC_DIR/panel_urls" 2>/dev/null)}"
[ -n "$PANEL_URLS" ] || PANEL_URLS="http://${PANEL_TUNNEL_IP}:8444 $PANEL"
CLIENT_ID=$(cat "$PHOBOS_DIR/client_id" 2>/dev/null || echo "home")
TOKEN=$(cat "$PHOBOS_DIR/pull_token" 2>/dev/null)
TMP="/tmp/failover.conf.pull"
LOCK="/tmp/phobos-pull.lock"
# + public URLs the panel announced in failover.conf (second IP etc., 09.33)
_extra=$(grep "^PANEL_URLS_EXTRA=" "$CONF" 2>/dev/null | cut -d= -f2-)
for _u in $_extra; do case " $PANEL_URLS " in *" $_u "*) ;; *) PANEL_URLS="$PANEL_URLS $_u" ;; esac; done
CLIENT_ID=$(cat "$ACC_DIR/client_id" 2>/dev/null || echo "home")
TOKEN=$(cat "$ACC_DIR/pull_token" 2>/dev/null)
TMP="/tmp/failover.conf.pull${PHOBOS_ACC:+-$CLIENT_ID}"
LOCK="/tmp/phobos-pull${PHOBOS_ACC:+-$CLIENT_ID}.lock"
log() { echo "$(date '+%H:%M:%S') $1" >> "$LOG"; }
@@ -74,8 +80,21 @@ fetch_config() {
wget -T 8 --no-check-certificate -q -O "$TMP" "$url" 2>/dev/null || wget -T 8 -q -O "$TMP" "$url" 2>/dev/null
}
# TCP channel for routers (1.5.0): the guard needs /opt/bin/udptcp when failover.conf has TCP_N.
ensure_udptcp() {
[ -x /opt/bin/udptcp ] && return 0
grep -q "^TCP_[0-9]*=" "$CONF" 2>/dev/null || return 0
case "$(uname -m)" in aarch64) a=aarch64 ;; mips*) a=mipsel ;; *) return 0 ;; esac
command -v curl >/dev/null || return 0
{ curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://raw.githubusercontent.com/adleibzon-rgb/PCA_Phobos/main/udptcp/bin/udptcp-$a" 2>/dev/null \
|| curl -fsSL -m 30 -o /opt/bin/udptcp.tmp "https://git.bobyrev.com/PCA/PCA_Phobos/raw/branch/main/udptcp/bin/udptcp-$a" 2>/dev/null; } \
&& [ -s /opt/bin/udptcp.tmp ] && chmod 755 /opt/bin/udptcp.tmp && mv /opt/bin/udptcp.tmp /opt/bin/udptcp \
&& log "PULL: udptcp installed ($a)" || rm -f /opt/bin/udptcp.tmp
}
# One fetch + compare + apply pass. Returns 0 always (best-effort).
do_pull() {
ensure_udptcp
# Try each source (tunnel first); accept first that returns a valid conf.
got=0
for base in $PANEL_URLS; do
@@ -113,7 +132,13 @@ apply_obf_params() {
D=$(grep "^OBF_MAX_DUMMY=" "$CONF" 2>/dev/null | cut -d= -f2-)
I=$(grep "^OBF_IDLE=" "$CONF" 2>/dev/null | cut -d= -f2-)
changed=0
for OBF in "$PHOBOS_DIR"/wg-obfuscator*.conf; do
# только свой обфускатор: у других учёток свои failover.conf
own=""
for l in "$PHOBOS_DIR"/wg-obfuscator*.link; do
[ "$(cat "$l" 2>/dev/null)" = "$CLIENT_ID" ] && own="${l%.link}.conf"
done
[ -z "$own" ] && [ -z "$PHOBOS_ACC" ] && own="$PHOBOS_DIR/wg-obfuscator*.conf"
for OBF in $own; do
[ -f "$OBF" ] || continue
cur=$(grep "^masking = " "$OBF" 2>/dev/null | sed 's/masking = //')
[ "$cur" != "$M" ] && { sed -i "s/^masking = .*/masking = $M/" "$OBF"; changed=1; }
@@ -129,7 +154,9 @@ apply_obf_params() {
[ "$changed" = 0 ] && return 0
log "PULL: obf params applied (masking=$M dummy=${D:-} idle=${I:-}), restart obfuscator"
if ls /opt/etc/init.d/S*obfuscator* >/dev/null 2>&1; then
for sc in /opt/etc/init.d/S*obfuscator*; do [ -x "$sc" ] && "$sc" restart >/dev/null 2>&1; done
for sc in /opt/etc/init.d/S*obfuscator*; do
for OBF in $own; do grep -q "/$(basename "$OBF")\"\$" "$sc" && [ -x "$sc" ] && "$sc" restart >/dev/null 2>&1; done
done
elif command -v systemctl >/dev/null 2>&1; then
systemctl restart 'wg-obfuscator*' >/dev/null 2>&1 || true
else

View File

@@ -278,11 +278,21 @@ EOF
# Настраиваем статический маршрут в созданный интерфейс отдельным вызовом RCI
local route_target_net=$(echo "$client_ip_addr" | cut -d'.' -f1-2).0.0
log "Настройка маршрута для подсети туннеля (${route_target_net}/16) -> ${interface_name}..."
local route_mask="255.255.0.0"
# 02.11.2026: доп. учётка (на роутере уже стоит другая): та же /16 через второй
# интерфейс конфликтует с главной, а /32 до панели увёл бы pull главной в чужой
# туннель — маршрут не ставим, pull доп. учётки ходит по panel_urls.
local main_cid=$(cat /opt/etc/Phobos/client_id 2>/dev/null)
if [ -n "$main_cid" ] && [ "$main_cid" != "${CLIENT_NAME}" ]; then
log "Доп. учётка (главная — ${main_cid}): маршрут подсети туннеля не нужен"
log "Интерфейс ${interface_name} создан ✓"
return 0
fi
log "Настройка маршрута для туннеля (${route_target_net}/${route_mask}) -> ${interface_name}..."
local route_json=$(cat <<EOF
{
"network": "${route_target_net}",
"mask": "255.255.0.0",
"mask": "${route_mask}",
"interface": "${interface_name}",
"auto": true
}