#!/bin/bash CONFIG_FILE="/etc/psiphon/psiphon.config" SERVICE_FILE="/etc/systemd/system/psiphon.service" INSTALL_DIR="/etc/psiphon" show_help() { echo "Usage: psiphon [start|stop|restart|status|region|httpport|socksport|ip|checkip|uninstall]" echo echo "start - Start the Psiphon service." echo "stop - Stop the Psiphon service." echo "restart - Restart the Psiphon service." echo "status - Show the status of the Psiphon service." echo "region [code] - Set the EgressRegion to the specified region code." echo "httpport [port] - Set the LocalHttpProxyPort to the specified port." echo "socksport [port] - Set the LocalSocksProxyPort to the specified port." echo "ip - Show exit IP via Psiphon SOCKS proxy (dynamic port)." echo "uninstall - Uninstall Psiphon." echo echo "Valid region codes are: AT, BE, BG, CA, CH, CZ, DE, DK, EE, ES, FI, FR, GB, HU, IE, IN, IT, JP, LV, NL, NO, PL, RO, RS, SE, SG, SK, US" echo "Port numbers must be between 1024 and 65535." } # Read SOCKS port from config dynamically; fallback to 1081 get_socks_port() { local port="" if [ -f "$CONFIG_FILE" ]; then port="$(grep -oE '"LocalSocksProxyPort"[[:space:]]*:[[:space:]]*[0-9]+' "$CONFIG_FILE" \ | head -n1 | grep -oE '[0-9]+' || true)" fi if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then echo "$port" else echo "1081" fi } # Wait until a TCP port is actually open (listening) on host:port wait_for_port() { local host="$1" local port="$2" local tries="${3:-30}" local sleep_s="${4:-1}" for _ in $(seq 1 "$tries"); do (echo >"/dev/tcp/${host}/${port}") >/dev/null 2>&1 && return 0 sleep "$sleep_s" done return 1 } print_proxy_ip() { local socks_port socks_port="$(get_socks_port)" # 1) ensure service is active if ! systemctl is-active --quiet psiphon; then printf "[%s] Proxy IP (SOCKS:%s): (service not active)\n" "$(date '+%F %T')" "$socks_port" return 1 fi # 2) wait until SOCKS port is listening if ! wait_for_port "127.0.0.1" "$socks_port" 30 1; then if ! wait_for_port "localhost" "$socks_port" 10 1; then printf "[%s] Proxy IP (SOCKS:%s): (port not ready)\n" "$(date '+%F %T')" "$socks_port" return 1 fi fi # 3) fetch IP with retries local ip="" local err="" for _ in $(seq 1 10); do err="$(mktemp)" ip="$(curl -sS --fail \ --connect-timeout 3 --max-time 8 \ -x "socks5h://127.0.0.1:${socks_port}" \ https://ifconfig.me 2>"$err" || true)" ip="$(echo "$ip" | tr -d '[:space:]')" if [ -n "$ip" ]; then rm -f "$err" printf "[%s] Proxy IP (SOCKS:%s): %s\n" "$(date '+%F %T')" "$socks_port" "$ip" return 0 fi sleep 1 rm -f "$err" done # last error message err="$(mktemp)" ip="$(curl -sS --fail \ --connect-timeout 3 --max-time 8 \ -x "socks5h://127.0.0.1:${socks_port}" \ https://ifconfig.me 2>"$err" || true)" ip="$(echo "$ip" | tr -d '[:space:]')" if [ -n "$ip" ]; then rm -f "$err" printf "[%s] Proxy IP (SOCKS:%s): %s\n" "$(date '+%F %T')" "$socks_port" "$ip" return 0 fi printf "[%s] Proxy IP (SOCKS:%s): (curl failed) - %s\n" "$(date '+%F %T')" "$socks_port" "$(tr '\n' ' ' < "$err")" rm -f "$err" return 1 } restart_service() { sudo systemctl restart psiphon echo "Psiphon service has been restarted." print_proxy_ip || true } update_region() { local new_region=$1 local valid_regions=("AT" "BE" "BG" "CA" "CH" "CZ" "DE" "DK" "EE" "ES" "FI" "FR" "GB" "HU" "IE" "IN" "IT" "JP" "LV" "NL" "NO" "PL" "RO" "RS" "SE" "SG" "SK" "US") if [[ " ${valid_regions[*]} " =~ " ${new_region} " ]]; then if [ -f "$CONFIG_FILE" ]; then echo "Updating region to $new_region..." sudo sed -i "s/\"EgressRegion\":\"[^\"]*\"/\"EgressRegion\":\"$new_region\"/" "$CONFIG_FILE" echo "Region updated to $new_region." restart_service else echo "Error: Config file not found!" exit 1 fi else echo "Invalid region: $new_region" echo "Valid regions are: ${valid_regions[*]}" exit 1 fi } update_http_port() { local new_http_port=$1 if [[ "$new_http_port" =~ ^[0-9]+$ ]] && [ "$new_http_port" -ge 1024 ] && [ "$new_http_port" -le 65535 ]; then if [ -f "$CONFIG_FILE" ]; then echo "Updating HTTP proxy port to $new_http_port..." sudo sed -i "s/\"LocalHttpProxyPort\":[0-9]*/\"LocalHttpProxyPort\":$new_http_port/" "$CONFIG_FILE" echo "HTTP proxy port updated to $new_http_port." restart_service else echo "Error: Config file not found!" exit 1 fi else echo "Invalid HTTP port: $new_http_port" echo "Port must be a number between 1024 and 65535." exit 1 fi } update_socks_port() { local new_socks_port=$1 if [[ "$new_socks_port" =~ ^[0-9]+$ ]] && [ "$new_socks_port" -ge 1024 ] && [ "$new_socks_port" -le 65535 ]; then if [ -f "$CONFIG_FILE" ]; then echo "Updating SOCKS proxy port to $new_socks_port..." sudo sed -i "s/\"LocalSocksProxyPort\":[0-9]*/\"LocalSocksProxyPort\":$new_socks_port/" "$CONFIG_FILE" echo "SOCKS proxy port updated to $new_socks_port." restart_service else echo "Error: Config file not found!" exit 1 fi else echo "Invalid SOCKS port: $new_socks_port" echo "Port must be a number between 1024 and 65535." exit 1 fi } uninstall() { echo "Uninstalling Psiphon..." sudo systemctl stop psiphon 2>/dev/null || true sudo systemctl disable psiphon 2>/dev/null || true sudo rm -f "$SERVICE_FILE" sudo rm -f "$INSTALL_DIR/psiphon-tunnel-core-x86_64" sudo rm -f "$INSTALL_DIR/psiphon-tunnel-core-arm64" sudo rm -f "$INSTALL_DIR/psiphon.config" sudo rm -f /usr/bin/psiphon sudo rmdir "$INSTALL_DIR" 2>/dev/null || true sudo systemctl daemon-reload echo "Psiphon uninstalled successfully." } case "$1" in start) sudo systemctl start psiphon echo "Psiphon service started." ;; stop) sudo systemctl stop psiphon echo "Psiphon service stopped." ;; restart) restart_service ;; status) sudo systemctl status psiphon ;; region) [ -n "$2" ] || { echo "Please provide a region. Usage: psiphon region [region_code]"; exit 1; } update_region "$2" ;; httpport) [ -n "$2" ] || { echo "Please provide an HTTP port. Usage: psiphon httpport [port_number]"; exit 1; } update_http_port "$2" ;; socksport) [ -n "$2" ] || { echo "Please provide a SOCKS port. Usage: psiphon socksport [port_number]"; exit 1; } update_socks_port "$2" ;; ip) print_proxy_ip ;; uninstall) uninstall ;; ""|help|-h|--help) show_help ;; *) show_help exit 1 ;; esac