Merge pull request #135 from ReturnFI/beta

Migration Continues!
This commit is contained in:
Whispering Wind
2025-05-03 00:28:10 +03:30
committed by GitHub
22 changed files with 798 additions and 512 deletions

View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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))

View File

@ -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

View File

@ -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 <new_ip>}")
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 <new_ip>}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -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 <new_ip>}"
exit 1
;;
esac

View File

@ -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()

View File

@ -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

View File

@ -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} <API_TOKEN> <ADMIN_USER_IDS>")
sys.exit(1)
action = sys.argv[1]
if action == "start":
if len(sys.argv) != 4:
print("Usage: python3 runbot.py start <API_TOKEN> <ADMIN_USER_IDS>")
sys.exit(1)
start_service(sys.argv[2], sys.argv[3])
elif action == "stop":
stop_service()
else:
print("Usage: python3 runbot.py {start|stop} <API_TOKEN> <ADMIN_USER_IDS>")
sys.exit(1)

View File

@ -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 <<EOL > /etc/hysteria/core/scripts/telegrambot/.env
API_TOKEN=$api_token
ADMIN_USER_IDS=[$admin_user_ids]
EOL
}
create_service_file() {
cat <<EOL > /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} <API_TOKEN> <ADMIN_USER_IDS>"
exit 1
;;
esac
define_colors

View File

@ -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
)

View File

@ -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"

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)}')

View File

@ -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):

20
menu.sh
View File

@ -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..."