#!/usr/bin/env bash set -uo pipefail IFS=$'\n\t' SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" source "$SCRIPT_DIR/lib-core.sh" check_root load_config() { if [[ -f "$SERVER_ENV" ]]; then source "$SERVER_ENV" fi OBFUSCATOR_PORT="${OBFUSCATOR_PORT:-$(grep 'source-lport' "$OBF_CONFIG" 2>/dev/null | awk '{print $3}' || echo 51821)}" OBFUSCATOR_KEY="${OBFUSCATOR_KEY:-$(grep 'key' "$OBF_CONFIG" 2>/dev/null | awk '{print $3}' || echo "KEY")}" SERVER_PUBLIC_IP_V4="${SERVER_PUBLIC_IP_V4:-0.0.0.0}" SERVER_PUBLIC_IP_V6="${SERVER_PUBLIC_IP_V6:-}" WG_LOCAL_ENDPOINT="${WG_LOCAL_ENDPOINT:-127.0.0.1:51820}" CLIENT_WG_PORT="${CLIENT_WG_PORT:-13255}" if [[ -f "$OBF_CONFIG" ]]; then CURRENT_IP=$(grep '^source-if' "$OBF_CONFIG" | cut -d'=' -f2- | tr -d ' ' || echo "0.0.0.0") CURRENT_LOG=$(grep '^verbose' "$OBF_CONFIG" | cut -d'=' -f2- | tr -d ' ' || echo "INFO") CURRENT_MASKING=$(grep '^masking' "$OBF_CONFIG" | cut -d'=' -f2- | tr -d ' ' || echo "AUTO") CURRENT_IDLE=$(grep '^idle-timeout' "$OBF_CONFIG" | cut -d'=' -f2- | tr -d ' ' || echo "300") CURRENT_DUMMY=$(grep '^max-dummy' "$OBF_CONFIG" | cut -d'=' -f2- | tr -d ' ' || echo "4") else CURRENT_IP="0.0.0.0" CURRENT_LOG="INFO" CURRENT_MASKING="AUTO" CURRENT_IDLE="300" CURRENT_DUMMY="4" fi } save_server_env() { if [[ -f "$SERVER_ENV" ]]; then grep -vE '^(OBFUSCATOR_PORT|OBFUSCATOR_KEY|OBFUSCATOR_DUMMY|OBFUSCATOR_IDLE|OBFUSCATOR_MASKING|SERVER_PUBLIC_IP_V4|SERVER_PUBLIC_IP_V6|WG_LOCAL_ENDPOINT|CLIENT_WG_PORT|SERVER_WG_IPV4_NETWORK|SERVER_WG_IPV6_NETWORK)=' "$SERVER_ENV" > "$SERVER_ENV.tmp" mv "$SERVER_ENV.tmp" "$SERVER_ENV" fi cat >> "$SERVER_ENV" < "$OBF_CONFIG" </dev/null 2>&1 || true "$client_script" package "$client_id" >/dev/null 2>&1 || true fi done } change_masking() { echo "" echo "Режим маскировки: STUN, AUTO, NONE" echo "Текущий: $CURRENT_MASKING" read -p "Новый режим: " input if [[ "$input" =~ ^(STUN|AUTO|NONE)$ ]]; then CURRENT_MASKING="$input" apply_obfuscator_config rebuild_all_clients log_success "Маскировка изменена" else log_error "Неверный режим" fi read -p "Нажмите Enter..." } change_log_level() { echo "" echo "Уровни: ERRORS, WARNINGS, INFO, DEBUG, TRACE" echo "Текущий: $CURRENT_LOG" read -p "Новый уровень: " input if [[ "$input" =~ ^(ERRORS|WARNINGS|INFO|DEBUG|TRACE)$ ]]; then CURRENT_LOG="$input" apply_obfuscator_config log_success "Уровень логов изменен" else log_error "Неверный уровень" fi read -p "Нажмите Enter..." } change_idle_timeout() { echo "" echo "Idle таймаут в секундах (по умолчанию: 300)" echo "Текущий: $CURRENT_IDLE" read -p "Новое значение: " input if [[ "$input" =~ ^[0-9]+$ ]]; then CURRENT_IDLE="$input" apply_obfuscator_config rebuild_all_clients log_success "Таймаут изменен" else log_error "Неверное значение" fi read -p "Нажмите Enter..." } change_dummy() { echo "" echo "Max dummy bytes (по умолчанию: 4)" echo "Текущий: $CURRENT_DUMMY" read -p "Новое значение: " input if [[ "$input" =~ ^[0-9]+$ ]]; then CURRENT_DUMMY="$input" apply_obfuscator_config rebuild_all_clients log_success "Max dummy изменен" else log_error "Неверное значение" fi read -p "Нажмите Enter..." } apply_template() { echo "" echo "==========================================" echo " Шаблоны уровня маскировки" echo "==========================================" echo "" echo " 1) Легкая (key: 3, dummy: 4)" echo " 2) Достаточная (key: 6, dummy: 10)" echo " 3) Средняя (key: 20, dummy: 20)" echo " 4) Выше среднего (key: 50, dummy: 50)" echo " 5) Кошмар! (key: 255, dummy: 100)" echo "" echo " 0) Отмена" echo "" while true; do read -p "Выбор [0-5]: " choice case "$choice" in 0) return ;; 1) local key_len=3; CURRENT_DUMMY=4; break ;; 2) local key_len=6; CURRENT_DUMMY=10; break ;; 3) local key_len=20; CURRENT_DUMMY=20; break ;; 4) local key_len=50; CURRENT_DUMMY=50; break ;; 5) local key_len=255; CURRENT_DUMMY=100; break ;; *) echo "Некорректный ввод. Введите число от 0 до 5." ;; esac done OBFUSCATOR_KEY=$(head -c $((key_len * 2)) /dev/urandom | base64 | tr -d '+/=\n' | head -c "$key_len") apply_obfuscator_config rebuild_all_clients log_success "Шаблон применен: ключ ${key_len} символов, dummy ${CURRENT_DUMMY} байт" read -p "Нажмите Enter..." } change_wg_pool() { echo "" echo "=== Смена внутреннего пула адресов WireGuard ===" echo "⚠ ВНИМАНИЕ: Это изменит IP сервера и ВСЕХ клиентов!" # Get current from wg0.conf local current_addr=$(grep "^Address" "$WG_CONFIG" | head -1 | cut -d'=' -f2- | tr -d ' ' || echo "N/A") echo "Текущие адреса сервера: $current_addr" read -p "Введите новую сеть IPv4 (CIDR, например 10.50.0.0/16): " new_net_v4 if [[ ! "$new_net_v4" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then log_error "Неверный формат IPv4" return fi read -p "Введите новую сеть IPv6 (CIDR, например fd00:50:0::/48) или пустую строку: " new_net_v6 # Save to env SERVER_WG_IPV4_NETWORK="$new_net_v4" SERVER_WG_IPV6_NETWORK="$new_net_v6" save_server_env # Calculate Server IP (usually .1 in the network) local ip_base_v4=$(echo "$new_net_v4" | cut -d'/' -f1 | awk -F. '{print $1"."$2"."$3"."}') local last_octet=$(echo "$new_net_v4" | cut -d'/' -f1 | awk -F. '{print $4}') local server_ip_v4 if [[ "$last_octet" == "0" ]]; then local prefix=$(echo "$new_net_v4" | cut -d'/' -f1 | awk -F. '{print $1"."$2"."$3}') server_ip_v4="${prefix}.1" else server_ip_v4=$(echo "$new_net_v4" | cut -d'/' -f1) fi local mask_v4=$(echo "$new_net_v4" | cut -d'/' -f2) local server_addr_str="${server_ip_v4}/${mask_v4}" local server_addr_full="$server_addr_str" if [[ -n "$new_net_v6" ]]; then local mask_v6=$(echo "$new_net_v6" | cut -d'/' -f2) local prefix_v6=$(echo "$new_net_v6" | cut -d'/' -f1 | sed 's/::.*//') local server_ip_v6="${prefix_v6}::1" server_addr_full="$server_addr_str, ${server_ip_v6}/${mask_v6}" fi echo "Новый адрес сервера: $server_addr_full" # Update wg0.conf sed -i "s|^Address = .*|Address = $server_addr_full|" "$WG_CONFIG" # Iterate clients echo "Обновление клиентов..." for client_dir in "$PHOBOS_DIR/clients"/*; do if [[ -d "$client_dir" ]]; then local client_id=$(basename "$client_dir") local meta="$client_dir/metadata.json" local conf="$client_dir/${client_id}.conf" local old_ipv4=$(jq -r '.tunnel_ip_v4 // empty' "$meta" 2>/dev/null) if [[ -z "$old_ipv4" ]]; then continue; fi local old_last=$(echo "$old_ipv4" | awk -F. '{print $4}') local old_third=$(echo "$old_ipv4" | awk -F. '{print $3}') # New IPv4 local new_prefix_v4=$(echo "$server_ip_v4" | awk -F. '{print $1"."$2}') local new_client_ip_v4="${new_prefix_v4}.${old_third}.${old_last}" # New IPv6 local new_client_ip_v6="" if [[ -n "$new_net_v6" ]]; then local hex_part=$(printf "%x:%x" "$old_third" "$old_last") local prefix_v6_clean=$(echo "$new_net_v6" | cut -d'/' -f1 | sed 's/::.*//') new_client_ip_v6="${prefix_v6_clean}::${hex_part}" fi log_info "Client $client_id: $old_ipv4 -> $new_client_ip_v4" # Update metadata if [[ -n "$new_client_ip_v6" ]]; then jq --arg ipv4 "$new_client_ip_v4" --arg ipv6 "$new_client_ip_v6" \ '.tunnel_ip_v4 = $ipv4 | .tunnel_ip_v6 = $ipv6' "$meta" > "$meta.tmp" && mv "$meta.tmp" "$meta" else jq --arg ipv4 "$new_client_ip_v4" \ '.tunnel_ip_v4 = $ipv4 | .tunnel_ip_v6 = null' "$meta" > "$meta.tmp" && mv "$meta.tmp" "$meta" fi # Update conf file (Address =) local new_conf_addr="$new_client_ip_v4/32" if [[ -n "$new_client_ip_v6" ]]; then new_conf_addr="$new_conf_addr, $new_client_ip_v6/128" fi sed -i "s|^Address = .*|Address = $new_conf_addr|" "$conf" fi done # Rebuild Peer section of wg0.conf log_info "Пересборка списка пиров в wg0.conf..." sed -n '/^\['Interface'\]/,/^$/p' "$WG_CONFIG" > "$WG_CONFIG.new" for client_dir in "$PHOBOS_DIR/clients"/*; do if [[ -d "$client_dir" ]] && [[ -f "$client_dir/metadata.json" ]]; then local pub=$(jq -r '.public_key' "$client_dir/metadata.json") local ipv4=$(jq -r '.tunnel_ip_v4' "$client_dir/metadata.json") local ipv6=$(jq -r '.tunnel_ip_v6 // empty' "$client_dir/metadata.json") echo "" >> "$WG_CONFIG.new" echo "[Peer]" >> "$WG_CONFIG.new" echo "PublicKey = $pub" >> "$WG_CONFIG.new" if [[ -n "$ipv6" ]]; then echo "AllowedIPs = $ipv4/32, $ipv6/128" >> "$WG_CONFIG.new" else echo "AllowedIPs = $ipv4/32" >> "$WG_CONFIG.new" fi fi done mv "$WG_CONFIG.new" "$WG_CONFIG" chmod 600 "$WG_CONFIG" log_info "Пересборка пакетов клиентов..." local client_script="$SCRIPT_DIR/phobos-client.sh" for client_dir in "$PHOBOS_DIR/clients"/*; do if [[ -d "$client_dir" ]]; then local client_id=$(basename "$client_dir") "$client_script" package "$client_id" >/dev/null 2>&1 || true fi done log_info "Перезапуск WireGuard..." systemctl restart wg-quick@wg0 systemctl restart wg-obfuscator log_success "Адреса обновлены." } change_wg_listen_port() { echo "" echo "=== Смена порта WireGuard ===" local current_port=$(grep "^ListenPort" "$WG_CONFIG" | cut -d'=' -f2 | tr -d ' ') echo "Текущий порт WireGuard: $current_port" echo "" read -p "Введите новый порт (1024-65535) или 'r' для случайного: " input local new_port if [[ "$input" == "r" ]]; then new_port=$(find_free_port) || { log_error "Нет свободных портов"; read -p "Нажмите Enter..."; return; } elif [[ "$input" =~ ^[0-9]+$ ]] && [ "$input" -ge 1024 ] && [ "$input" -le 65535 ]; then if ss -ulnp | grep -q ":$input "; then log_error "Порт $input уже занят" read -p "Нажмите Enter..." return fi new_port="$input" else log_error "Неверный порт" read -p "Нажмите Enter..." return fi log_info "Смена порта WireGuard: $current_port -> $new_port" sed -i "s|^ListenPort = .*|ListenPort = $new_port|" "$WG_CONFIG" WG_LOCAL_ENDPOINT="127.0.0.1:$new_port" save_server_env apply_obfuscator_config skip_restart log_info "Пересборка пакетов клиентов..." local client_script="$SCRIPT_DIR/phobos-client.sh" for client_dir in "$PHOBOS_DIR/clients"/*; do if [[ -d "$client_dir" ]]; then local client_id=$(basename "$client_dir") "$client_script" package "$client_id" >/dev/null 2>&1 || true fi done log_info "Перезапуск служб..." systemctl restart wg-quick@wg0 systemctl restart wg-obfuscator log_success "Порт WireGuard изменен на $new_port" read -p "Нажмите Enter..." } change_server_ip() { echo "" echo "Текущий публичный IP сервера: $SERVER_PUBLIC_IP_V4" echo "1) Определить из сетевого интерфейса" echo "2) Ввести вручную" read -p "Выбор: " choice local ip="" if [[ "$choice" == "1" ]]; then ip=$(get_public_ipv4) || true if [[ -z "$ip" ]]; then log_error "Не удалось определить IP из сетевого интерфейса" read -p "Нажмите Enter..." return fi elif [[ "$choice" == "2" ]]; then read -p "Введите IPv4: " input if [[ ! "$input" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then log_error "Неверный формат IP" read -p "Нажмите Enter..." return fi ip="$input" else return fi SERVER_PUBLIC_IP_V4="$ip" save_server_env rebuild_all_clients log_success "Публичный IP сервера обновлен: $SERVER_PUBLIC_IP_V4" read -p "Нажмите Enter..." } # Menu show_menu() { load_config clear echo "==========================================" echo " PHOBOS - Настройка WG-Obfuscator" echo "==========================================" echo "" echo " 1) Шаблоны маскировки [key: ${#OBFUSCATOR_KEY}, dummy: $CURRENT_DUMMY]" echo "" echo " 2) Порт сервера (UDP) [$OBFUSCATOR_PORT]" echo " 3) Порт клиента (Local) [$CLIENT_WG_PORT]" echo " 4) IP интерфейса [$CURRENT_IP]" echo " 5) Ключ обфускации [${OBFUSCATOR_KEY:0:8}...]" echo " 6) Маскировка [$CURRENT_MASKING]" echo " 7) Уровень логов [$CURRENT_LOG]" echo " 8) Idle таймаут (сек) [$CURRENT_IDLE]" echo " 9) Max dummy (байт) [$CURRENT_DUMMY]" echo "" local wg_port=$(grep "^ListenPort" "$WG_CONFIG" 2>/dev/null | cut -d'=' -f2 | tr -d ' ') echo " 10) Смена пула адресов WG (IPv4/IPv6)" echo " 11) Порт WireGuard [${wg_port:-51820}]" echo " 12) Публичный IP сервера [$SERVER_PUBLIC_IP_V4]" echo "" echo " 0) Назад" echo "" read -p "Выберите действие: " choice case $choice in 1) apply_template ;; 2) change_port ;; 3) change_client_port ;; 4) change_interface_ip ;; 5) change_key ;; 6) change_masking ;; 7) change_log_level ;; 8) change_idle_timeout ;; 9) change_dummy ;; 10) change_wg_pool ;; 11) change_wg_listen_port ;; 12) change_server_ip ;; 0) exit 0 ;; *) echo "Неверный выбор" ;; esac } if [[ "${1:-}" == "apply_defaults_silent" ]]; then load_config apply_obfuscator_config skip_restart exit 0 fi while true; do show_menu done