diff --git a/changelog b/changelog index 085e6c8..59e8e63 100644 --- a/changelog +++ b/changelog @@ -1,21 +1,10 @@ -## [1.9.0] - 2025-05-02 +## [1.9.1] - 2025-05-03 ### โœจ Changed -๐Ÿง  **refactor:** Migrate core Hysteria2 functionality from Bash to Python - - ๐Ÿ” Uninstallation - - โฌ†๏ธ Server update - - โž• User addition - - ๐Ÿ” User reset - - ๐Ÿ” User info retrieval - - โŒ User removal - - ๐Ÿ”„ Port update - - ๐Ÿ” Server restart - -### ๐Ÿ› Fixed -๐Ÿ› ๏ธ **fix:** Resolved file permission issues by explicitly calling Python in subprocess -๐Ÿ› **fix:** Removed improper usage of `asyncio.to_thread` in `remove_user.py` -๐ŸŒš **fix (UI):** Fixed JSONEditor background in dark mode for better readability - -### ๐ŸŽจ UI / UX -๐ŸŽจ **feat:** Improved light/dark mode design with enhanced CSS -๐Ÿงฉ **update:** Integrated custom CSS directly into `base.html` +๐Ÿง  **refactor:**: Implement server stats manager in Python +๐Ÿง  **refactor:**: Migrate IP address config management to Python +๐Ÿง  **refactor:**: Implement Hysteria backup functionality in Python +๐Ÿง  **refactor:**: Transition Telegram bot service management to Python +๐Ÿง  **refactor:**: Migrate WARP ACL configuration handling to Python +๐Ÿง  **refactor:**: Implement WARP status management in Python +๐Ÿง  **refactor:**: Rewrite WARP setup and uninstallation scripts in Python3 \ No newline at end of file diff --git a/core/cli.py b/core/cli.py index 3440a0d..ea964d1 100644 --- a/core/cli.py +++ b/core/cli.py @@ -365,16 +365,13 @@ def uninstall_warp(): @click.option('--popular-sites', '-p', is_flag=True, help='Use WARP for popular sites like Google, OpenAI, etc') @click.option('--domestic-sites', '-d', is_flag=True, help='Use WARP for Iran domestic sites') @click.option('--block-adult-sites', '-x', is_flag=True, help='Block adult content (porn)') -@click.option('--warp-option', '-w', type=click.Choice(['warp', 'warp plus'], case_sensitive=False), help='Specify whether to use WARP or WARP Plus') -@click.option('--warp-key', '-k', help="WARP Plus key (required if warp-option is 'warp plus')") -def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool, warp_option: str, warp_key: str): +def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool): try: - cli_api.configure_warp(all, popular_sites, domestic_sites, block_adult_sites, warp_option, warp_key) + cli_api.configure_warp(all, popular_sites, domestic_sites, block_adult_sites) click.echo('WARP configured successfully.') except Exception as e: click.echo(f'{e}', err=True) - @cli.command('warp-status') def warp_status(): try: diff --git a/core/cli_api.py b/core/cli_api.py index 6a9867f..9198aa4 100644 --- a/core/cli_api.py +++ b/core/cli_api.py @@ -29,24 +29,24 @@ class Command(Enum): REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.py') SHOW_USER_URI = os.path.join(SCRIPT_DIR, 'hysteria2', 'show_user_uri.py') WRAPPER_URI = os.path.join(SCRIPT_DIR, 'hysteria2', 'wrapper_uri.py') - IP_ADD = os.path.join(SCRIPT_DIR, 'hysteria2', 'ip.sh') + IP_ADD = os.path.join(SCRIPT_DIR, 'hysteria2', 'ip.py') MANAGE_OBFS = os.path.join(SCRIPT_DIR, 'hysteria2', 'manage_obfs.py') MASQUERADE_SCRIPT = os.path.join(SCRIPT_DIR, 'hysteria2', 'masquerade.sh') TRAFFIC_STATUS = 'traffic.py' # won't be called directly (it's a python module) UPDATE_GEO = os.path.join(SCRIPT_DIR, 'hysteria2', 'update_geo.py') LIST_USERS = os.path.join(SCRIPT_DIR, 'hysteria2', 'list_users.sh') - SERVER_INFO = os.path.join(SCRIPT_DIR, 'hysteria2', 'server_info.sh') - BACKUP_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'backup.sh') + SERVER_INFO = os.path.join(SCRIPT_DIR, 'hysteria2', 'server_info.py') + BACKUP_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'backup.py') RESTORE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restore.sh') - INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.sh') + INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.py') SHELL_SINGBOX = os.path.join(SCRIPT_DIR, 'singbox', 'singbox_shell.sh') SHELL_WEBPANEL = os.path.join(SCRIPT_DIR, 'webpanel', 'webpanel_shell.sh') INSTALL_NORMALSUB = os.path.join(SCRIPT_DIR, 'normalsub', 'normalsub.sh') INSTALL_TCP_BRUTAL = os.path.join(SCRIPT_DIR, 'tcp-brutal', 'install.sh') - INSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'install.sh') - UNINSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'uninstall.sh') - CONFIGURE_WARP = os.path.join(SCRIPT_DIR, 'warp', 'configure.sh') - STATUS_WARP = os.path.join(SCRIPT_DIR, 'warp', 'status.sh') + INSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'install.py') + UNINSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'uninstall.py') + CONFIGURE_WARP = os.path.join(SCRIPT_DIR, 'warp', 'configure.py') + STATUS_WARP = os.path.join(SCRIPT_DIR, 'warp', 'status.py') SERVICES_STATUS = os.path.join(SCRIPT_DIR, 'services_status.sh') VERSION = os.path.join(SCRIPT_DIR, 'hysteria2', 'version.py') LIMIT_SCRIPT = os.path.join(SCRIPT_DIR, 'hysteria2', 'limit.sh') @@ -182,7 +182,7 @@ def change_hysteria2_sni(sni: str): def backup_hysteria2(): '''Backups Hysteria configuration. Raises an exception on failure.''' try: - run_cmd(['bash', Command.BACKUP_HYSTERIA2.value]) + run_cmd(['python3', Command.BACKUP_HYSTERIA2.value]) except subprocess.CalledProcessError as e: raise Exception(f"Backup failed: {e}") except Exception as ex: @@ -368,10 +368,12 @@ def traffic_status(no_gui=False, display_output=True): return data +# Next Update: # TODO: it's better to return json +# TODO: After json todo need fix Telegram Bot and WebPanel def server_info() -> str | None: '''Retrieves server information.''' - return run_cmd(['bash', Command.SERVER_INFO.value]) + return run_cmd(['python3', Command.SERVER_INFO.value]) def get_ip_address() -> tuple[str | None, str | None]: @@ -387,7 +389,7 @@ def add_ip_address(): ''' Adds IP addresses from the environment to the .configs.env file. ''' - run_cmd(['bash', Command.IP_ADD.value, 'add']) + run_cmd(['python3', Command.IP_ADD.value, 'add']) def edit_ip_address(ipv4: str, ipv6: str): @@ -402,9 +404,9 @@ def edit_ip_address(ipv4: str, ipv6: str): if not ipv4 and not ipv6: raise InvalidInputError('Error: --edit requires at least one of --ipv4 or --ipv6.') if ipv4: - run_cmd(['bash', Command.IP_ADD.value, 'edit', '-4', ipv4]) + run_cmd(['python3', Command.IP_ADD.value, 'edit', '-4', ipv4]) if ipv6: - run_cmd(['bash', Command.IP_ADD.value, 'edit', '-6', ipv6]) + run_cmd(['python3', Command.IP_ADD.value, 'edit', '-6', ipv6]) def update_geo(country: str): @@ -434,56 +436,48 @@ def install_tcp_brutal(): def install_warp(): '''Installs WARP.''' - run_cmd(['bash', Command.INSTALL_WARP.value]) + run_cmd(['python3', Command.INSTALL_WARP.value]) def uninstall_warp(): '''Uninstalls WARP.''' - run_cmd(['bash', Command.UNINSTALL_WARP.value]) + run_cmd(['python3', Command.UNINSTALL_WARP.value]) -def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool, warp_option: str, warp_key: str): +def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool): ''' Configures WARP with various options. ''' - if warp_option == 'warp plus' and not warp_key: - raise InvalidInputError('Error: WARP Plus key is required when \'warp plus\' is selected.') - options = { - 'all': 'true' if all else 'false', - 'popular_sites': 'true' if popular_sites else 'false', - 'domestic_sites': 'true' if domestic_sites else 'false', - 'block_adult_sites': 'true' if block_adult_sites else 'false', - 'warp_option': warp_option or '', - 'warp_key': warp_key or '' - } cmd_args = [ - 'bash', Command.CONFIGURE_WARP.value, - options['all'], - options['popular_sites'], - options['domestic_sites'], - options['block_adult_sites'], - options['warp_option'] + 'python3', Command.CONFIGURE_WARP.value ] - if options['warp_key']: - cmd_args.append(options['warp_key']) + if all: + cmd_args.append('--all') + if popular_sites: + cmd_args.append('--popular-sites') + if domestic_sites: + cmd_args.append('--domestic-sites') + if block_adult_sites: + cmd_args.append('--block-adult') + run_cmd(cmd_args) def warp_status() -> str | None: '''Checks the status of WARP.''' - return run_cmd(['bash', Command.STATUS_WARP.value]) + return run_cmd(['python3', Command.STATUS_WARP.value]) def start_telegram_bot(token: str, adminid: str): '''Starts the Telegram bot.''' if not token or not adminid: raise InvalidInputError('Error: Both --token and --adminid are required for the start action.') - run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'start', token, adminid]) + run_cmd(['python3', Command.INSTALL_TELEGRAMBOT.value, 'start', token, adminid]) def stop_telegram_bot(): '''Stops the Telegram bot.''' - run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'stop']) + run_cmd(['python3', Command.INSTALL_TELEGRAMBOT.value, 'stop']) def start_singbox(domain: str, port: int): diff --git a/core/scripts/hysteria2/backup.py b/core/scripts/hysteria2/backup.py new file mode 100644 index 0000000..01dd60f --- /dev/null +++ b/core/scripts/hysteria2/backup.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +import zipfile +from pathlib import Path +from datetime import datetime + +backup_dir = Path("/opt/hysbackup") +backup_file = backup_dir / f"hysteria_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" + +files_to_backup = [ + Path("/etc/hysteria/ca.key"), + Path("/etc/hysteria/ca.crt"), + Path("/etc/hysteria/users.json"), + Path("/etc/hysteria/config.json"), + Path("/etc/hysteria/.configs.env"), +] + +backup_dir.mkdir(parents=True, exist_ok=True) + +try: + with zipfile.ZipFile(backup_file, 'w') as zipf: + for file_path in files_to_backup: + if file_path.exists(): + zipf.write(file_path, arcname=file_path.name) + print("Backup successfully created") +except Exception as e: + print("Backup failed!", str(e)) diff --git a/core/scripts/hysteria2/backup.sh b/core/scripts/hysteria2/backup.sh deleted file mode 100644 index fda800f..0000000 --- a/core/scripts/hysteria2/backup.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -BACKUP_DIR="/opt/hysbackup" -BACKUP_FILE="$BACKUP_DIR/hysteria_backup_$(date +%Y%m%d_%H%M%S).zip" - -if [ ! -d "$BACKUP_DIR" ]; then - mkdir -p "$BACKUP_DIR" -fi - -FILES_TO_BACKUP=( - "/etc/hysteria/ca.key" - "/etc/hysteria/ca.crt" - "/etc/hysteria/users.json" - "/etc/hysteria/config.json" - "/etc/hysteria/.configs.env" -) - -zip -j "$BACKUP_FILE" "${FILES_TO_BACKUP[@]}" >/dev/null - -if [ $? -eq 0 ]; then - echo "Backup successfully created" -else - echo "Backup failed!" -fi diff --git a/core/scripts/hysteria2/ip.py b/core/scripts/hysteria2/ip.py new file mode 100644 index 0000000..17c6a7e --- /dev/null +++ b/core/scripts/hysteria2/ip.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +import sys +import re +import subprocess +from pathlib import Path + +core_scripts_dir = Path(__file__).resolve().parents[1] +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import CONFIG_ENV + + +def ensure_env_file_exists(): + if not CONFIG_ENV.exists(): + print("CONFIG_ENV not found. Creating a new one...") + CONFIG_ENV.touch() + + +def update_config(key: str, value: str): + content = [] + if CONFIG_ENV.exists(): + with CONFIG_ENV.open("r") as f: + content = [line for line in f if not line.startswith(f"{key}=")] + content.append(f"{key}={value}\n") + with CONFIG_ENV.open("w") as f: + f.writelines(content) + + +def get_interface_addresses(): + ipv4_address = "" + ipv6_address = "" + + interfaces = subprocess.check_output(["ip", "-o", "link", "show"]).decode() + interfaces = [ + line.split(": ")[1] + for line in interfaces.strip().splitlines() + if not re.match(r"^(lo|wgcf|warp)$", line.split(": ")[1]) + ] + + for iface in interfaces: + try: + ipv4 = subprocess.check_output(["ip", "-o", "-4", "addr", "show", iface]).decode() + for line in ipv4.strip().splitlines(): + addr = line.split()[3].split("/")[0] + if not re.match(r"^(127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1]))", addr): + ipv4_address = addr + break + ipv6 = subprocess.check_output(["ip", "-o", "-6", "addr", "show", iface]).decode() + for line in ipv6.strip().splitlines(): + addr = line.split()[3].split("/")[0] + if not re.match(r"^(::1|fe80:)", addr): + ipv6_address = addr + break + except subprocess.CalledProcessError: + continue + + return ipv4_address, ipv6_address + + +def add_ips(): + ensure_env_file_exists() + ipv4, ipv6 = get_interface_addresses() + + update_config("IP4", ipv4 or "") + update_config("IP6", ipv6 or "") + + print(f"Updated IP4={ipv4 or 'Not Found'}") + print(f"Updated IP6={ipv6 or 'Not Found'}") + + +def edit_ip(option: str, new_ip: str): + ensure_env_file_exists() + if option == "-4": + update_config("IP4", new_ip) + print(f"IP4 has been updated to {new_ip}.") + elif option == "-6": + update_config("IP6", new_ip) + print(f"IP6 has been updated to {new_ip}.") + else: + print("Invalid option. Use -4 for IPv4 or -6 for IPv6.") + + +def main(): + if len(sys.argv) < 2: + print("Usage: {add|edit -4|-6 }") + sys.exit(1) + + action = sys.argv[1] + + if action == "add": + add_ips() + elif action == "edit" and len(sys.argv) == 4: + edit_ip(sys.argv[2], sys.argv[3]) + else: + print("Usage: {add|edit -4|-6 }") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/core/scripts/hysteria2/ip.sh b/core/scripts/hysteria2/ip.sh deleted file mode 100644 index 27b0da8..0000000 --- a/core/scripts/hysteria2/ip.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -source /etc/hysteria/core/scripts/path.sh - -if [ ! -f "$CONFIG_ENV" ]; then - echo "CONFIG_ENV not found. Creating a new one..." - touch "$CONFIG_ENV" -fi - -update_config() { - local key=$1 - local value=$2 - - sed -i "/^$key=/d" "$CONFIG_ENV" 2>/dev/null - - echo "$key=$value" >> "$CONFIG_ENV" -} - -add_ips() { - ipv4_address="" - ipv6_address="" - - interfaces=$(ip -o link show | awk -F': ' '{print $2}' | grep -vE '^(lo|wgcf|warp)$') - - for interface in $interfaces; do - if ip addr show "$interface" > /dev/null 2>&1; then - ipv4=$(ip -o -4 addr show "$interface" | awk '{print $4}' | grep -vE '^(127\\.|10\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3[0-1]))' | head -n 1 | cut -d/ -f1) - if [[ -z $ipv4_address && -n $ipv4 ]]; then - ipv4_address=$ipv4 - fi - - ipv6=$(ip -o -6 addr show "$interface" | awk '{print $4}' | grep -vE '^(::1|fe80:)' | head -n 1 | cut -d/ -f1) - if [[ -z $ipv6_address && -n $ipv6 ]]; then - ipv6_address=$ipv6 - fi - fi - done - - update_config "IP4" "${ipv4_address:-}" - update_config "IP6" "${ipv6_address:-}" - - echo "Updated IP4=${ipv4_address:-Not Found}" - echo "Updated IP6=${ipv6_address:-Not Found}" -} - -edit_ip() { - local type=$1 - local new_ip=$2 - - if [[ $type == "-4" ]]; then - update_config "IP4" "$new_ip" - echo "IP4 has been updated to $new_ip." - elif [[ $type == "-6" ]]; then - update_config "IP6" "$new_ip" - echo "IP6 has been updated to $new_ip." - else - echo "Invalid option. Use -4 for IPv4 or -6 for IPv6." - fi -} - -case "$1" in - add) - add_ips - ;; - edit) - edit_ip "$2" "$3" - ;; - *) - echo "Usage: $0 {add|edit -4|-6 }" - exit 1 - ;; -esac diff --git a/core/scripts/hysteria2/server_info.py b/core/scripts/hysteria2/server_info.py new file mode 100644 index 0000000..329dbc4 --- /dev/null +++ b/core/scripts/hysteria2/server_info.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import sys +import json +from hysteria2_api import Hysteria2Client +import time +from init_paths import * +from paths import * + + +def get_secret() -> str: + if not CONFIG_FILE.exists(): + print("Error: config.json file not found!", file=sys.stderr) + sys.exit(1) + + with CONFIG_FILE.open() as f: + data = json.load(f) + + secret = data.get("trafficStats", {}).get("secret") + if not secret: + print("Error: secret not found in config.json!", file=sys.stderr) + sys.exit(1) + + return secret + + +def convert_bytes(bytes_val: int) -> str: + units = [("TB", 1 << 40), ("GB", 1 << 30), ("MB", 1 << 20), ("KB", 1 << 10)] + for unit, factor in units: + if bytes_val >= factor: + return f"{bytes_val / factor:.2f} {unit}" + return f"{bytes_val} B" + + +def get_cpu_usage(interval: float = 0.1) -> float: + def read_cpu_times(): + with open("/proc/stat") as f: + line = f.readline() + fields = list(map(int, line.strip().split()[1:])) + idle, total = fields[3], sum(fields) + return idle, total + + idle1, total1 = read_cpu_times() + time.sleep(interval) + idle2, total2 = read_cpu_times() + + idle_delta = idle2 - idle1 + total_delta = total2 - total1 + cpu_usage = 100.0 * (1 - idle_delta / total_delta) if total_delta else 0.0 + return round(cpu_usage, 1) + + + +def get_memory_usage() -> tuple[int, int]: + with open("/proc/meminfo") as f: + lines = f.readlines() + + mem_total = int(next(line for line in lines if "MemTotal" in line).split()[1]) // 1024 + mem_available = int(next(line for line in lines if "MemAvailable" in line).split()[1]) // 1024 + mem_used = mem_total - mem_available + + return mem_total, mem_used + + + +def get_online_user_count(secret: str) -> int: + try: + client = Hysteria2Client( + base_url=API_BASE_URL, + secret=secret + ) + online_users = client.get_online_clients() + return sum(1 for user in online_users.values() if user.is_online) + except Exception as e: + print(f"Error getting online users: {e}", file=sys.stderr) + return 0 + + +def get_total_traffic() -> tuple[int, int]: + if not USERS_FILE.exists(): + return 0, 0 + + try: + with USERS_FILE.open() as f: + users = json.load(f) + + total_upload = 0 + total_download = 0 + + for user_data in users.values(): + total_upload += int(user_data.get("upload_bytes", 0) or 0) + total_download += int(user_data.get("download_bytes", 0) or 0) + + return total_upload, total_download + except Exception as e: + print(f"Error parsing traffic data: {e}", file=sys.stderr) + return 0, 0 + + + +def main(): + secret = get_secret() + + cpu_usage = get_cpu_usage() + mem_total, mem_used = get_memory_usage() + online_users = get_online_user_count(secret) + + print(f"๐Ÿ“ˆ CPU Usage: {cpu_usage}") + print(f"๐Ÿ“‹ Total RAM: {mem_total}MB") + print(f"๐Ÿ’ป Used RAM: {mem_used}MB") + print(f"๐Ÿ‘ฅ Online Users: {online_users}") + print() + + total_upload, total_download = get_total_traffic() + + print(f"๐Ÿ”ผ Uploaded Traffic: {convert_bytes(total_upload)}") + print(f"๐Ÿ”ฝ Downloaded Traffic: {convert_bytes(total_download)}") + print(f"๐Ÿ“Š Total Traffic: {convert_bytes(total_upload + total_download)}") + + +if __name__ == "__main__": + main() diff --git a/core/scripts/hysteria2/server_info.sh b/core/scripts/hysteria2/server_info.sh deleted file mode 100644 index fd4375e..0000000 --- a/core/scripts/hysteria2/server_info.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -source /etc/hysteria/core/scripts/path.sh - -get_secret() { - [ ! -f "$CONFIG_FILE" ] && { echo "Error: config.json file not found!" >&2; exit 1; } - - local secret=$(jq -r '.trafficStats.secret' "$CONFIG_FILE") - - [ "$secret" = "null" ] || [ -z "$secret" ] && { - echo "Error: secret not found in config.json!" >&2 - exit 1 - } - - echo "$secret" -} - -convert_bytes() { - local bytes=$1 - if (( bytes < 1048576 )); then - printf "%.2f KB" "$(echo "scale=2; $bytes / 1024" | bc)" - elif (( bytes < 1073741824 )); then - printf "%.2f MB" "$(echo "scale=2; $bytes / 1048576" | bc)" - elif (( bytes < 1099511627776 )); then - printf "%.2f GB" "$(echo "scale=2; $bytes / 1073741824" | bc)" - else - printf "%.2f TB" "$(echo "scale=2; $bytes / 1099511627776" | bc)" - fi -} - -cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}') - -mem_stats=$(free -m) -mem_total=$(echo "$mem_stats" | awk '/Mem:/ {print $2}') -mem_used=$(echo "$mem_stats" | awk '/Mem:/ {print $3}') - -secret=$(get_secret) -online_users=$(curl -s -H "Authorization: $secret" "$ONLINE_API_URL") -online_user_count=$(echo "$online_users" | jq 'add // 0') - -echo "๐Ÿ“ˆ CPU Usage: $cpu_usage" -echo "๐Ÿ“‹ Total RAM: ${mem_total}MB" -echo "๐Ÿ’ป Used RAM: ${mem_used}MB" -echo "๐Ÿ‘ฅ Online Users: $online_user_count" -echo - -if [ -f "$USERS_FILE" ]; then - read total_upload total_download <<< $(jq -r ' - reduce .[] as $user ( - {"up": 0, "down": 0}; - .up += (($user.upload_bytes | numbers) // 0) | - .down += (($user.download_bytes | numbers) // 0) - ) | "\(.up) \(.down)"' "$USERS_FILE" 2>/dev/null || echo "0 0") - - total_upload=${total_upload:-0} - total_download=${total_download:-0} - - echo "๐Ÿ”ผ Uploaded Traffic: $(convert_bytes "$total_upload")" - echo "๐Ÿ”ฝ Downloaded Traffic: $(convert_bytes "$total_download")" - - total_traffic=$((total_upload + total_download)) - echo "๐Ÿ“Š Total Traffic: $(convert_bytes "$total_traffic")" -fi \ No newline at end of file diff --git a/core/scripts/telegrambot/runbot.py b/core/scripts/telegrambot/runbot.py new file mode 100644 index 0000000..2279018 --- /dev/null +++ b/core/scripts/telegrambot/runbot.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import sys +import subprocess +from pathlib import Path + +core_scripts_dir = Path(__file__).resolve().parents[1] +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import TELEGRAM_ENV + + + + +def update_env_file(api_token, admin_user_ids): + TELEGRAM_ENV.write_text(f"""API_TOKEN={api_token} +ADMIN_USER_IDS=[{admin_user_ids}] +""") + +def create_service_file(): + Path("/etc/systemd/system/hysteria-telegram-bot.service").write_text("""[Unit] +Description=Hysteria Telegram Bot +After=network.target + +[Service] +ExecStart=/bin/bash -c 'source /etc/hysteria/hysteria2_venv/bin/activate && /etc/hysteria/hysteria2_venv/bin/python /etc/hysteria/core/scripts/telegrambot/tbot.py' +WorkingDirectory=/etc/hysteria/core/scripts/telegrambot +Restart=always + +[Install] +WantedBy=multi-user.target +""") + +def start_service(api_token, admin_user_ids): + if subprocess.run(["systemctl", "is-active", "--quiet", "hysteria-telegram-bot.service"]).returncode == 0: + print("The hysteria-telegram-bot.service is already running.") + return + + update_env_file(api_token, admin_user_ids) + create_service_file() + + subprocess.run(["systemctl", "daemon-reload"]) + subprocess.run(["systemctl", "enable", "hysteria-telegram-bot.service"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["systemctl", "start", "hysteria-telegram-bot.service"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + if subprocess.run(["systemctl", "is-active", "--quiet", "hysteria-telegram-bot.service"]).returncode == 0: + print("Hysteria bot setup completed. The service is now running.\n") + else: + print("Hysteria bot setup completed. The service failed to start.") + +def stop_service(): + subprocess.run(["systemctl", "stop", "hysteria-telegram-bot.service"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["systemctl", "disable", "hysteria-telegram-bot.service"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + TELEGRAM_ENV.unlink(missing_ok=True) + print("\nHysteria bot service stopped and disabled. .env file removed.") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python3 runbot.py {start|stop} ") + sys.exit(1) + + action = sys.argv[1] + + if action == "start": + if len(sys.argv) != 4: + print("Usage: python3 runbot.py start ") + sys.exit(1) + start_service(sys.argv[2], sys.argv[3]) + elif action == "stop": + stop_service() + else: + print("Usage: python3 runbot.py {start|stop} ") + sys.exit(1) diff --git a/core/scripts/telegrambot/runbot.sh b/core/scripts/telegrambot/runbot.sh deleted file mode 100644 index f68396b..0000000 --- a/core/scripts/telegrambot/runbot.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -source /etc/hysteria/core/scripts/utils.sh -define_colors - -update_env_file() { - local api_token=$1 - local admin_user_ids=$2 - - cat < /etc/hysteria/core/scripts/telegrambot/.env -API_TOKEN=$api_token -ADMIN_USER_IDS=[$admin_user_ids] -EOL -} - -create_service_file() { - cat < /etc/systemd/system/hysteria-telegram-bot.service -[Unit] -Description=Hysteria Telegram Bot -After=network.target - -[Service] -ExecStart=/bin/bash -c 'source /etc/hysteria/hysteria2_venv/bin/activate && /etc/hysteria/hysteria2_venv/bin/python /etc/hysteria/core/scripts/telegrambot/tbot.py' -WorkingDirectory=/etc/hysteria/core/scripts/telegrambot -Restart=always - -[Install] -WantedBy=multi-user.target -EOL -} - -start_service() { - local api_token=$1 - local admin_user_ids=$2 - - if systemctl is-active --quiet hysteria-telegram-bot.service; then - echo "The hysteria-telegram-bot.service is already running." - return - fi - - update_env_file "$api_token" "$admin_user_ids" - create_service_file - - systemctl daemon-reload - systemctl enable hysteria-telegram-bot.service > /dev/null 2>&1 - systemctl start hysteria-telegram-bot.service > /dev/null 2>&1 - - if systemctl is-active --quiet hysteria-telegram-bot.service; then - echo -e "${green}Hysteria bot setup completed. The service is now running. ${NC}" - echo -e "\n\n" - else - echo "Hysteria bot setup completed. The service failed to start." - fi -} - -stop_service() { - systemctl stop hysteria-telegram-bot.service > /dev/null 2>&1 - systemctl disable hysteria-telegram-bot.service > /dev/null 2>&1 - - rm -f /etc/hysteria/core/scripts/telegrambot/.env - echo -e "\n" - - echo "Hysteria bot service stopped and disabled. .env file removed." -} - -case "$1" in - start) - start_service "$2" "$3" - ;; - stop) - stop_service - ;; - *) - echo "Usage: $0 {start|stop} " - exit 1 - ;; -esac - -define_colors diff --git a/core/scripts/warp/configure.py b/core/scripts/warp/configure.py new file mode 100644 index 0000000..6f448aa --- /dev/null +++ b/core/scripts/warp/configure.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +import json +import sys +import subprocess +from pathlib import Path + +core_scripts_dir = Path(__file__).resolve().parents[1] +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import * + +def warp_configure_handler(all_traffic=False, popular_sites=False, domestic_sites=False, block_adult_sites=False): + """ + Configure WARP routing rules based on provided parameters + + Args: + all_traffic (bool): Toggle WARP for all traffic + popular_sites (bool): Toggle WARP for popular sites (Google, Netflix, etc.) + domestic_sites (bool): Toggle between WARP and Reject for domestic sites + block_adult_sites (bool): Toggle blocking of adult content + """ + with open(CONFIG_FILE, 'r') as f: + config = json.load(f) + + modified = False + + if all_traffic: + warp_all_active = any(rule == "warps(all)" for rule in config.get('acl', {}).get('inline', [])) + + if warp_all_active: + config['acl']['inline'] = [rule for rule in config['acl']['inline'] if rule != "warps(all)"] + print("Traffic configuration changed to Direct.") + modified = True + else: + if 'acl' not in config: + config['acl'] = {} + if 'inline' not in config['acl']: + config['acl']['inline'] = [] + config['acl']['inline'].append("warps(all)") + print("Traffic configuration changed to WARP.") + modified = True + + if popular_sites: + popular_rules = [ + "warps(geoip:google)", + "warps(geosite:google)", + "warps(geosite:netflix)", + "warps(geosite:spotify)", + "warps(geosite:openai)", + "warps(geoip:openai)" + ] + + rule_exists = any(rule in config.get('acl', {}).get('inline', []) for rule in popular_rules) + + if rule_exists: + config['acl']['inline'] = [rule for rule in config['acl']['inline'] + if rule not in popular_rules] + print("WARP configuration for Google, OpenAI, etc. removed.") + modified = True + else: + if 'acl' not in config: + config['acl'] = {} + if 'inline' not in config['acl']: + config['acl']['inline'] = [] + config['acl']['inline'].extend(popular_rules) + print("WARP configured for Google, OpenAI, etc.") + modified = True + + if domestic_sites: + ir_site_rule = "warps(geosite:ir)" + ir_ip_rule = "warps(geoip:ir)" + reject_site_rule = "reject(geosite:ir)" + reject_ip_rule = "reject(geoip:ir)" + + using_warp = (ir_site_rule in config.get('acl', {}).get('inline', []) and + ir_ip_rule in config.get('acl', {}).get('inline', [])) + using_reject = (reject_site_rule in config.get('acl', {}).get('inline', []) and + reject_ip_rule in config.get('acl', {}).get('inline', [])) + + if 'acl' not in config: + config['acl'] = {} + if 'inline' not in config['acl']: + config['acl']['inline'] = [] + + if using_warp: + config['acl']['inline'] = [reject_site_rule if rule == ir_site_rule else + reject_ip_rule if rule == ir_ip_rule else + rule for rule in config['acl']['inline']] + print("Configuration changed to Reject for geosite:ir and geoip:ir.") + modified = True + elif using_reject: + config['acl']['inline'] = [ir_site_rule if rule == reject_site_rule else + ir_ip_rule if rule == reject_ip_rule else + rule for rule in config['acl']['inline']] + print("Configuration changed to Use WARP for geosite:ir and geoip:ir.") + modified = True + else: + config['acl']['inline'].extend([reject_site_rule, reject_ip_rule]) + print("Added Reject configuration for geosite:ir and geoip:ir.") + modified = True + + if block_adult_sites: + nsfw_rule = "reject(geosite:nsfw)" + + blocked = nsfw_rule in config.get('acl', {}).get('inline', []) + + if blocked: + config['acl']['inline'] = [rule for rule in config['acl']['inline'] + if rule != nsfw_rule] + if 'resolver' not in config: + config['resolver'] = {} + if 'tls' not in config['resolver']: + config['resolver']['tls'] = {} + config['resolver']['tls']['addr'] = "1.1.1.1:853" + print("Adult content blocking removed and resolver updated.") + modified = True + else: + if 'acl' not in config: + config['acl'] = {} + if 'inline' not in config['acl']: + config['acl']['inline'] = [] + config['acl']['inline'].append(nsfw_rule) + if 'resolver' not in config: + config['resolver'] = {} + if 'tls' not in config['resolver']: + config['resolver']['tls'] = {} + config['resolver']['tls']['addr'] = "1.1.1.3:853" + print("Adult content blocked and resolver updated.") + modified = True + + if modified: + with open(CONFIG_FILE, 'w') as f: + json.dump(config, f, indent=2) + + try: + subprocess.run(["python3", CLI_PATH, "restart-hysteria2"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) + except subprocess.CalledProcessError: + print("Warning: Failed to restart hysteria2") + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Configure WARP settings") + parser.add_argument("--all", action="store_true", help="Toggle WARP for all traffic") + parser.add_argument("--popular-sites", action="store_true", help="Toggle WARP for popular sites") + parser.add_argument("--domestic-sites", action="store_true", help="Toggle between WARP and Reject for domestic sites") + parser.add_argument("--block-adult", action="store_true", help="Toggle blocking of adult content") + + args = parser.parse_args() + + warp_configure_handler( + all_traffic=args.all, + popular_sites=args.popular_sites, + domestic_sites=args.domestic_sites, + block_adult_sites=args.block_adult + ) \ No newline at end of file diff --git a/core/scripts/warp/configure.sh b/core/scripts/warp/configure.sh deleted file mode 100644 index 80f8299..0000000 --- a/core/scripts/warp/configure.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -source /etc/hysteria/core/scripts/path.sh - -warp_configure_handler() { - local all=$1 - local popular_sites=$2 - local domestic_sites=$3 - local block_adult_sites=$4 - local warp_option=$5 - local warp_key=$6 - - if [ "$all" == "true" ]; then - if [ "$(jq -r 'if .acl.inline | index("warps(all)") then "WARP active" else "Direct" end' "$CONFIG_FILE")" == "WARP active" ]; then - jq 'del(.acl.inline[] | select(. == "warps(all)"))' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Traffic configuration changed to Direct." - else - jq '.acl.inline += ["warps(all)"]' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Traffic configuration changed to WARP." - fi - fi - - if [ "$popular_sites" == "true" ]; then - if [ "$(jq -r 'if (.acl.inline | index("warps(geoip:google)")) or (.acl.inline | index("warps(geosite:google)")) or (.acl.inline | index("warps(geosite:netflix)")) or (.acl.inline | index("warps(geosite:spotify)")) or (.acl.inline | index("warps(geosite:openai)")) or (.acl.inline | index("warps(geoip:openai)")) then "WARP active" else "Direct" end' "$CONFIG_FILE")" == "WARP active" ]; then - jq 'del(.acl.inline[] | select(. == "warps(geoip:google)" or . == "warps(geosite:google)" or . == "warps(geosite:netflix)" or . == "warps(geosite:spotify)" or . == "warps(geosite:openai)" or . == "warps(geoip:openai)"))' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "WARP configuration for Google, OpenAI, etc. removed." - else - jq '.acl.inline += ["warps(geoip:google)", "warps(geosite:google)", "warps(geosite:netflix)", "warps(geosite:spotify)", "warps(geosite:openai)", "warps(geoip:openai)"]' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "WARP configured for Google, OpenAI, etc." - fi - fi - - if [ "$domestic_sites" == "true" ]; then - if [ "$(jq -r 'if (.acl.inline | index("warps(geosite:ir)")) and (.acl.inline | index("warps(geoip:ir)")) then "Use WARP" else "Reject" end' "$CONFIG_FILE")" == "Use WARP" ]; then - jq '(.acl.inline[] | select(. == "warps(geosite:ir)")) = "reject(geosite:ir)" | (.acl.inline[] | select(. == "warps(geoip:ir)")) = "reject(geoip:ir)"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Configuration changed to Reject for geosite:ir and geoip:ir." - else - jq '(.acl.inline[] | select(. == "reject(geosite:ir)")) = "warps(geosite:ir)" | (.acl.inline[] | select(. == "reject(geoip:ir)")) = "warps(geoip:ir)"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Configuration changed to Use WARP for geosite:ir and geoip:ir." - fi - fi - - if [ "$block_adult_sites" == "true" ]; then - if [ "$(jq -r 'if .acl.inline | index("reject(geosite:nsfw)") then "Blocked" else "Not blocked" end' "$CONFIG_FILE")" == "Blocked" ]; then - jq 'del(.acl.inline[] | select(. == "reject(geosite:nsfw)"))' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - jq '.resolver.tls.addr = "1.1.1.1:853"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Adult content blocking removed and resolver updated." - else - jq '.acl.inline += ["reject(geosite:nsfw)"]' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - jq '.resolver.tls.addr = "1.1.1.3:853"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" - echo "Adult content blocked and resolver updated." - fi - fi - - if [ "$warp_option" == "warp plus" ]; then - if [ -z "$warp_key" ]; then - echo "Error: WARP Plus key is required. Exiting." - exit 1 - fi - cd /etc/warp/ || { echo "Failed to change directory to /etc/warp/"; exit 1; } - - WGCF_LICENSE_KEY="$warp_key" wgcf update - - if [ $? -ne 0 ]; then - echo "Error: Failed to update WARP Plus configuration." - exit 1 - fi - - elif [ "$warp_option" == "warp" ]; then - cd /etc/warp/ || { echo "Failed to change directory to /etc/warp/"; exit 1; } - rm wgcf-account.toml && yes | wgcf register - echo "WARP configured with a new account." - fi - - python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1 -} - -warp_configure_handler "$1" "$2" "$3" "$4" "$5" "$6" diff --git a/core/scripts/warp/install.py b/core/scripts/warp/install.py new file mode 100644 index 0000000..1dfcd30 --- /dev/null +++ b/core/scripts/warp/install.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import json +from pathlib import Path +core_scripts_dir = Path(__file__).resolve().parents[1] + +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import * + +WARP_DEVICE = "wgcf" + +def is_service_active(service_name: str) -> bool: + return subprocess.run(["systemctl", "is-active", "--quiet", service_name]).returncode == 0 + + +def install_warp(): + print("Installing WARP...") + result = subprocess.run("bash <(curl -fsSL https://raw.githubusercontent.com/ReturnFI/Warp/main/warp.sh) wgx", + shell=True, executable="/bin/bash") + return result.returncode == 0 + + +def add_warp_outbound_to_config(): + if not CONFIG_FILE.exists(): + print(f"Error: Config file {CONFIG_FILE} not found.") + return + + with open(CONFIG_FILE, "r") as f: + config = json.load(f) + + outbounds = config.get("outbounds", []) + if any(outbound.get("name") == "warps" for outbound in outbounds): + print("WARP outbound already exists in the configuration.") + return + + outbounds.append({ + "name": "warps", + "type": "direct", + "direct": { + "mode": 4, + "bindDevice": WARP_DEVICE + } + }) + config["outbounds"] = outbounds + + with open(CONFIG_FILE, "w") as f: + json.dump(config, f, indent=2) + + print("WARP outbound added to config.json.") + + +def restart_hysteria(): + subprocess.run(["python3", str(CLI_PATH), "restart-hysteria2"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + print("Hysteria2 restarted with updated configuration.") + + +def main(): + warp_service = f"wg-quick@{WARP_DEVICE}.service" + + if is_service_active(warp_service): + print("WARP is already active. Checking configuration...") + add_warp_outbound_to_config() + restart_hysteria() + else: + if install_warp() and is_service_active(warp_service): + print("WARP installation successful.") + add_warp_outbound_to_config() + restart_hysteria() + else: + print("WARP installation failed.") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/core/scripts/warp/install.sh b/core/scripts/warp/install.sh deleted file mode 100644 index ecd29e2..0000000 --- a/core/scripts/warp/install.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -source /etc/hysteria/core/scripts/path.sh - -if systemctl is-active --quiet wg-quick@wgcf.service; then - echo "WARP is already active. Checking configuration..." - - if [ -f "$CONFIG_FILE" ] && jq -e '.outbounds[] | select(.name == "warps")' "$CONFIG_FILE" > /dev/null 2>&1; then - echo "WARP outbound already exists in the configuration. No changes needed." - else - if [ -f "$CONFIG_FILE" ]; then - jq '.outbounds += [{"name": "warps", "type": "direct", "direct": {"mode": 4, "bindDevice": "wgcf"}}]' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1 - echo "WARP outbound added to config.json." - else - echo "Error: Config file $CONFIG_FILE not found." - fi - fi -else - echo "Installing WARP..." - bash <(curl -fsSL https://raw.githubusercontent.com/ReturnFI/Warp/main/warp.sh) wgx - - if systemctl is-active --quiet wg-quick@wgcf.service; then - echo "WARP installation successful." - - if [ -f "$CONFIG_FILE" ]; then - if jq -e '.outbounds[] | select(.name == "warps")' "$CONFIG_FILE" > /dev/null 2>&1; then - echo "WARP outbound already exists in the configuration." - else - jq '.outbounds += [{"name": "warps", "type": "direct", "direct": {"mode": 4, "bindDevice": "wgcf"}}]' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - echo "WARP outbound added to config.json." - fi - python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1 - echo "Hysteria2 restarted with updated configuration." - else - echo "Error: Config file $CONFIG_FILE not found." - fi - else - echo "WARP installation failed." - fi -fi \ No newline at end of file diff --git a/core/scripts/warp/status.py b/core/scripts/warp/status.py new file mode 100644 index 0000000..0d49424 --- /dev/null +++ b/core/scripts/warp/status.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import json +from pathlib import Path +import sys + +core_scripts_dir = Path(__file__).resolve().parents[1] +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import * + +colors = { + "cyan": "\033[96m", + "green": "\033[92m", + "red": "\033[91m", + "purple": "\033[95m", + "end": "\033[0m" +} + +def echo_status(label, is_active): + status = f"{colors['green']}Active{colors['end']}" if is_active else f"{colors['red']}Inactive{colors['end']}" + print(f"{colors['cyan']}{label}:{colors['end']} {status}") + +def check_warp_configuration(): + if not Path(CONFIG_FILE).exists(): + print(f"{colors['red']}Error: Config file not found at {CONFIG_FILE}{colors['end']}") + return + + with open(CONFIG_FILE, "r") as f: + config = json.load(f) + + acl_inline = config.get("acl", {}).get("inline", []) + + def contains_warp(rule_prefixes): + return any(rule.startswith(prefix) for rule in acl_inline for prefix in rule_prefixes) + + print("--------------------------------") + print(f"{colors['purple']}Current WARP Configuration:{colors['end']}") + + echo_status( + "All traffic", + contains_warp(["warps(all)"]) + ) + + echo_status( + "Popular sites (Google, Netflix, etc.)", + contains_warp([ + "warps(geosite:google)", + "warps(geoip:google)", + "warps(geosite:netflix)", + "warps(geosite:spotify)", + "warps(geosite:openai)", + "warps(geoip:openai)" + ]) + ) + + echo_status( + "Domestic sites (geosite:ir, geoip:ir)", + contains_warp([ + "warps(geosite:ir)", + "warps(geoip:ir)" + ]) + ) + + echo_status( + "Block adult content", + "reject(geosite:nsfw)" in acl_inline + ) + + print("--------------------------------") + +if __name__ == "__main__": + check_warp_configuration() diff --git a/core/scripts/warp/status.sh b/core/scripts/warp/status.sh deleted file mode 100644 index c019d46..0000000 --- a/core/scripts/warp/status.sh +++ /dev/null @@ -1,34 +0,0 @@ -source /etc/hysteria/core/scripts/utils.sh -source /etc/hysteria/core/scripts/path.sh - -check_warp_configuration() { - echo "--------------------------------" - echo -e "${LPurple}Current WARP Configuration: ${NC}" - - if jq -e '.acl.inline[]? | select(test("warps\\(all\\)"))' "$CONFIG_FILE" > /dev/null; then - echo -e "${cyan}All traffic:${NC} ${green}Active${NC}" - else - echo -e "${cyan}All traffic:${NC} ${red}Inactive${NC}" - fi - - if jq -e '.acl.inline[]? | select(test("warps\\(geosite:google\\)")) or select(test("warps\\(geoip:google\\)")) or select(test("warps\\(geosite:netflix\\)")) or select(test("warps\\(geosite:spotify\\)")) or select(test("warps\\(geosite:openai\\)")) or select(test("warps\\(geoip:openai\\)"))' "$CONFIG_FILE" > /dev/null; then - echo -e "${cyan}Popular sites (Google, Netflix, etc.):${NC} ${green}Active${NC}" - else - echo -e "${cyan}Popular sites (Google, Netflix, etc.):${NC} ${red}Inactive${NC}" - fi - - if jq -e '.acl.inline[]? | select(test("warps\\(geosite:ir\\)")) or select(test("warps\\(geoip:ir\\)"))' "$CONFIG_FILE" > /dev/null; then - echo -e "${cyan}Domestic sites (geosite:ir, geoip:ir):${NC} ${green}Active${NC}" - else - echo -e "${cyan}Domestic sites (geosite:ir, geoip:ir):${NC} ${red}Inactive${NC}" - fi - - if jq -e '.acl.inline[]? | select(test("reject\\(geosite:nsfw\\)"))' "$CONFIG_FILE" > /dev/null; then - echo -e "${cyan}Block adult content:${NC} ${green}Active${NC}" - else - echo -e "${cyan}Block adult content:${NC} ${red}Inactive${NC}" - fi - echo "--------------------------------" -} -define_colors -check_warp_configuration diff --git a/core/scripts/warp/uninstall.py b/core/scripts/warp/uninstall.py new file mode 100644 index 0000000..f6b0d78 --- /dev/null +++ b/core/scripts/warp/uninstall.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +import subprocess +import json +import shutil +import sys +from pathlib import Path + +core_scripts_dir = Path(__file__).resolve().parents[1] + +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) + +from paths import CONFIG_FILE, CLI_PATH + +TEMP_CONFIG = Path("/etc/hysteria/config_temp.json") + + +def systemctl_active(service: str) -> bool: + return subprocess.run(["systemctl", "is-active", "--quiet", service]).returncode == 0 + + +def run_shell(command: str): + subprocess.run(command, shell=True, check=False) + + +def load_config(path: Path): + if path.exists(): + with path.open("r", encoding="utf-8") as f: + return json.load(f) + print(f"โŒ Config file not found: {path}") + return None + + +def save_config(config: dict, path: Path): + with path.open("w", encoding="utf-8") as f: + json.dump(config, f, indent=2) + shutil.move(str(path), str(CONFIG_FILE)) + + +def reset_acl_inline(config: dict): + default = [ + "reject(geosite:ir)", "reject(geoip:ir)", + "reject(geosite:category-ads-all)", "reject(geoip:private)", + "reject(geosite:google@ads)" + ] + updated = [] + for item in config.get("acl", {}).get("inline", []): + if item in [ + "warps(all)", "warps(geoip:google)", "warps(geosite:google)", + "warps(geosite:netflix)", "warps(geosite:spotify)", + "warps(geosite:openai)", "warps(geoip:openai)" + ]: + updated.append("direct") + elif item == "warps(geosite:ir)": + updated.append("reject(geosite:ir)") + elif item == "warps(geoip:ir)": + updated.append("reject(geoip:ir)") + else: + updated.append(item) + + final_inline = default + [i for i in updated if i not in default and i != "direct"] + config["acl"]["inline"] = final_inline + return config + + +def remove_warp_outbound(config: dict): + config["outbounds"] = [ + o for o in config.get("outbounds", []) + if not ( + o.get("name") == "warps" and + o.get("type") == "direct" and + o.get("direct", {}).get("mode") == 4 and + o.get("direct", {}).get("bindDevice") == "wgcf" + ) + ] + return config + + +def remove_porn_blocking(config: dict): + inline = config.get("acl", {}).get("inline", []) + if "reject(geosite:category-porn)" in inline: + config["acl"]["inline"] = [i for i in inline if i != "reject(geosite:category-porn)"] + print("๐Ÿ”’ Adult content blocking removed.") + return config + + +def set_dns(config: dict): + config.setdefault("resolver", {}).setdefault("tls", {})["addr"] = "1.1.1.1:853" + print("๐Ÿ”ง DNS resolver changed to 1.1.1.1:853.") + return config + + +def restart_hysteria(): + subprocess.run(["python3", str(CLI_PATH), "restart-hysteria2"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def main(): + if systemctl_active("wg-quick@wgcf.service"): + print("๐Ÿงน Uninstalling WARP...") + run_shell('bash -c "bash <(curl -fsSL https://raw.githubusercontent.com/ReturnFI/Warp/main/warp.sh) dwg"') + config = load_config(CONFIG_FILE) + if config: + config = reset_acl_inline(config) + config = remove_warp_outbound(config) + config = remove_porn_blocking(config) + config = set_dns(config) + save_config(config, TEMP_CONFIG) + restart_hysteria() + print("โœ… WARP uninstalled and configuration reset.") + else: + print("โ„น๏ธ WARP is not active. Skipping uninstallation.") + + +if __name__ == "__main__": + main() diff --git a/core/scripts/warp/uninstall.sh b/core/scripts/warp/uninstall.sh deleted file mode 100644 index 0e5baf0..0000000 --- a/core/scripts/warp/uninstall.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -source /etc/hysteria/core/scripts/path.sh - -if systemctl is-active --quiet wg-quick@wgcf.service; then - echo "Uninstalling WARP..." - bash <(curl -fsSL https://raw.githubusercontent.com/ReturnFI/Warp/main/warp.sh) dwg - - if [ -f "$CONFIG_FILE" ]; then - default_config='["reject(geosite:ir)", "reject(geoip:ir)", "reject(geosite:category-ads-all)", "reject(geoip:private)", "reject(geosite:google@ads)"]' - - jq --argjson default_config "$default_config" ' - .acl.inline |= map( - if . == "warps(all)" or . == "warps(geoip:google)" or . == "warps(geosite:google)" or . == "warps(geosite:netflix)" or . == "warps(geosite:spotify)" or . == "warps(geosite:openai)" or . == "warps(geoip:openai)" then - "direct" - elif . == "warps(geosite:ir)" then - "reject(geosite:ir)" - elif . == "warps(geoip:ir)" then - "reject(geoip:ir)" - else - . - end - ) | .acl.inline |= ($default_config + (. - $default_config | map(select(. != "direct")))) - ' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - - jq 'del(.outbounds[] | select(.name == "warps" and .type == "direct" and .direct.mode == 4 and .direct.bindDevice == "wgcf"))' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - - if [ "$(jq -r 'if .acl.inline | index("reject(geosite:category-porn)") then "Blocked" else "Not blocked" end' "$CONFIG_FILE")" == "Blocked" ]; then - jq 'del(.acl.inline[] | select(. == "reject(geosite:category-porn)"))' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - echo "Adult content blocking removed." - fi - - jq '.resolver.tls.addr = "1.1.1.1:853"' "$CONFIG_FILE" > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json "$CONFIG_FILE" - echo "DNS resolver address changed to 1.1.1.1:853." - - python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1 - echo "WARP uninstalled and configurations reset to default." - else - echo "Error: Config file $CONFIG_FILE not found." - fi -else - echo "WARP is not active. Skipping uninstallation." -fi diff --git a/core/scripts/webpanel/routers/api/v1/config/warp.py b/core/scripts/webpanel/routers/api/v1/config/warp.py index 87c5d0b..80f79a6 100644 --- a/core/scripts/webpanel/routers/api/v1/config/warp.py +++ b/core/scripts/webpanel/routers/api/v1/config/warp.py @@ -61,7 +61,7 @@ async def configure(body: ConfigureInputBody): """ try: cli_api.configure_warp(body.all, body.popular_sites, body.domestic_sites, - body.block_adult_sites, body.warp_option, body.warp_key) + body.block_adult_sites) return DetailResponse(detail='WARP configured successfully.') except Exception as e: raise HTTPException(status_code=400, detail=f'Error: {str(e)}') diff --git a/core/scripts/webpanel/routers/api/v1/schema/config/warp.py b/core/scripts/webpanel/routers/api/v1/schema/config/warp.py index cc69af3..09e23f0 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/config/warp.py +++ b/core/scripts/webpanel/routers/api/v1/schema/config/warp.py @@ -7,8 +7,6 @@ class ConfigureInputBody(BaseModel): popular_sites: bool = False domestic_sites: bool = False block_adult_sites: bool = False - warp_option: Literal['warp', 'warp plus', ''] = '' - warp_key: str = '' class StatusResponse(BaseModel): diff --git a/menu.sh b/menu.sh index dc51c65..d152e24 100644 --- a/menu.sh +++ b/menu.sh @@ -389,10 +389,8 @@ warp_configure_handler() { echo "2. Use WARP for popular sites" echo "3. Use WARP for domestic sites" echo "4. Block adult content" - echo "5. WARP (Plus) Profile" - echo "6. WARP (Normal) Profile" - echo "7. WARP Status Profile" - echo "8. Change IP address" + echo "5. WARP Status Profile" + echo "6. Change IP address" echo "0. Cancel" read -p "Select an option: " option @@ -402,23 +400,13 @@ warp_configure_handler() { 2) python3 $CLI_PATH configure-warp --popular-sites ;; 3) python3 $CLI_PATH configure-warp --domestic-sites ;; 4) python3 $CLI_PATH configure-warp --block-adult-sites ;; - 5) - echo "Please enter your WARP Plus key:" - read -r warp_key - if [ -z "$warp_key" ]; then - echo "Error: WARP Plus key cannot be empty. Exiting." - return - fi - python3 $CLI_PATH configure-warp --warp-option "warp plus" --warp-key "$warp_key" - ;; - 6) python3 $CLI_PATH configure-warp --warp-option "warp" ;; - 7) + 5) ip=$(curl -s --interface wgcf --connect-timeout 0.5 http://v4.ident.me) cd /etc/warp/ && wgcf status echo echo -e "${yellow}Warp IP :${NC} ${cyan}$ip ${NC}" ;; - 8) + 6) old_ip=$(curl -s --interface wgcf --connect-timeout 0.5 http://v4.ident.me) echo "Current IP address: $old_ip" echo "Restarting $service_name..."