Files
PCA_Phobos/server/phobos-cascade.sh
Бобырев Андрей 7eac63343d release: 1.4.0 — one version for stable/beta/dev
Light/dark theme and compact mobile layout, per-client speed limit, real
speed tests, panel domain (Let's Encrypt), client address + cascade relay,
RKN scanner guard, WARP, backup/restore, router web terminal, Android VPN
client with split tunnelling and Android admin app. Updating from 1.2.x
keeps keys, server.env and clients; tested 1.2.8 -> 1.4.0 on live VMs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:22:46 +03:00

94 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# PCA Phobos — cascade relay. Run on an ENTRY VPS (e.g. in Russia, "white" IP):
# clients connect to this server, UDP obfuscator ports are forwarded to the EXIT
# Phobos server abroad. The entry box needs no Phobos, no keys, no panel.
# bash <(curl -fsSL .../server/phobos-cascade.sh) enable <exit_host> [ports]
# phobos-cascade.sh disable | status | apply
# Then in the exit server panel: Settings -> "Адрес для клиентов" -> entry IP/domain.
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}"
TAG="phobos-cascade"
clear_rules() {
local t
for t in nat filter; do
iptables -t $t -S 2>/dev/null | grep -- "--comment $TAG" | sed 's/^-A /-D /' | \
while read -r rule; do eval iptables -t $t $rule 2>/dev/null; done
done
}
apply() {
[ -f "$STATE" ] || { echo "cascade: not configured"; return 0; }
. "$STATE"
local ip p
ip=$(getent ahostsv4 "$EXIT_HOST" | awk 'NR==1{print $1}')
[ -n "$ip" ] || { echo "cascade: cannot resolve $EXIT_HOST" >&2; return 1; }
sysctl -qw net.ipv4.ip_forward=1
clear_rules
for p in ${PORTS//,/ }; do
iptables -t nat -A PREROUTING -p udp --dport "$p" -m comment --comment $TAG -j DNAT --to-destination "$ip:$p"
iptables -t nat -A POSTROUTING -p udp -d "$ip" --dport "$p" -m comment --comment $TAG -j MASQUERADE
iptables -I FORWARD -p udp -d "$ip" --dport "$p" -m comment --comment $TAG -j ACCEPT
iptables -I FORWARD -p udp -s "$ip" --sport "$p" -m comment --comment $TAG -j ACCEPT
done
echo "cascade: udp ${PORTS} -> ${EXIT_HOST} (${ip})"
}
enable() {
local host="${1:-}" ports="${2:-2083,5443,993}"
[ -n "$host" ] || { echo "usage: $0 enable <exit_host> [ports]" >&2; exit 2; }
[[ "$ports" =~ ^[0-9]+(,[0-9]+)*$ ]] || { echo "bad ports: $ports" >&2; exit 2; }
command -v iptables >/dev/null || DEBIAN_FRONTEND=noninteractive apt-get install -y -qq iptables >/dev/null
printf 'EXIT_HOST=%s\nPORTS=%s\n' "$host" "$ports" > "$STATE"
# bash <(curl ...) gives a pipe as $0 -> fetch a real copy for the systemd unit
if [ "$(readlink -f "$0")" != "$BIN" ]; then
if [ -f "$0" ]; then cp "$0" "$BIN"; else curl -fsSL -m30 "$SELF_URL" -o "$BIN" || { echo "cannot save $BIN" >&2; exit 1; }; fi
chmod +x "$BIN"
fi
cat > /etc/systemd/system/phobos-cascade.service <<EOF
[Unit]
Description=PCA Phobos cascade relay
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=$BIN apply
ExecStop=$BIN stop
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable phobos-cascade.service >/dev/null 2>&1
systemctl restart phobos-cascade.service
apply >/dev/null
status
}
disable() {
systemctl disable --now phobos-cascade.service >/dev/null 2>&1 || true
clear_rules
rm -f /etc/systemd/system/phobos-cascade.service "$STATE"
systemctl daemon-reload
echo "cascade: disabled"
}
status() {
local host="" ports="" rules
[ -f "$STATE" ] && . "$STATE" && host="$EXIT_HOST" && ports="$PORTS"
rules=$(iptables -t nat -S PREROUTING 2>/dev/null | grep -c -- "--comment $TAG")
printf '{"enabled": %s, "exit_host": "%s", "ports": "%s", "rules": %s}\n' \
"$([ "$rules" -gt 0 ] && echo true || echo false)" "$host" "$ports" "$rules"
}
case "${1:-status}" in
enable) enable "${2:-}" "${3:-}" ;;
apply) apply ;;
stop) clear_rules ;;
disable) disable ;;
status) status ;;
*) echo "usage: $0 enable <exit_host> [ports]|disable|status"; exit 2 ;;
esac