Merge pull request #134 from ReturnFI/beta
feat(core): Comprehensive refactoring and enhancements
This commit is contained in:
@ -17,20 +17,20 @@ WEBPANEL_ENV_FILE = '/etc/hysteria/core/scripts/webpanel/.env'
|
|||||||
class Command(Enum):
|
class Command(Enum):
|
||||||
'''Contains path to command's script'''
|
'''Contains path to command's script'''
|
||||||
INSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'install.sh')
|
INSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'install.sh')
|
||||||
UNINSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'uninstall.sh')
|
UNINSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'uninstall.py')
|
||||||
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.sh')
|
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.py')
|
||||||
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.sh')
|
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.py')
|
||||||
CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.sh')
|
CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.py')
|
||||||
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.sh')
|
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.sh')
|
||||||
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.sh')
|
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py')
|
||||||
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.sh')
|
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
|
||||||
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
|
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
|
||||||
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.sh')
|
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.py')
|
||||||
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.sh')
|
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.py')
|
||||||
SHOW_USER_URI = os.path.join(SCRIPT_DIR, 'hysteria2', 'show_user_uri.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')
|
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.sh')
|
||||||
MANAGE_OBFS = os.path.join(SCRIPT_DIR, 'hysteria2', 'manage_obfs.sh')
|
MANAGE_OBFS = os.path.join(SCRIPT_DIR, 'hysteria2', 'manage_obfs.py')
|
||||||
MASQUERADE_SCRIPT = os.path.join(SCRIPT_DIR, 'hysteria2', 'masquerade.sh')
|
MASQUERADE_SCRIPT = os.path.join(SCRIPT_DIR, 'hysteria2', 'masquerade.sh')
|
||||||
TRAFFIC_STATUS = 'traffic.py' # won't be called directly (it's a python module)
|
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')
|
UPDATE_GEO = os.path.join(SCRIPT_DIR, 'hysteria2', 'update_geo.py')
|
||||||
@ -110,8 +110,11 @@ def generate_password() -> str:
|
|||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
return subprocess.check_output(['pwgen', '-s', '32', '1'], shell=False).decode().strip()
|
return subprocess.check_output(['pwgen', '-s', '32', '1'], shell=False).decode().strip()
|
||||||
except subprocess.CalledProcessError as e:
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
raise PasswordGenerationError(f'Failed to generate password: {e}')
|
try:
|
||||||
|
return subprocess.check_output(['cat', '/proc/sys/kernel/random/uuid'], shell=False).decode().strip()
|
||||||
|
except Exception as e:
|
||||||
|
raise PasswordGenerationError(f"Failed to generate password: {e}")
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
@ -129,17 +132,17 @@ def install_hysteria2(port: int, sni: str):
|
|||||||
|
|
||||||
def uninstall_hysteria2():
|
def uninstall_hysteria2():
|
||||||
'''Uninstalls Hysteria2.'''
|
'''Uninstalls Hysteria2.'''
|
||||||
run_cmd(['bash', Command.UNINSTALL_HYSTERIA2.value])
|
run_cmd(['python3', Command.UNINSTALL_HYSTERIA2.value])
|
||||||
|
|
||||||
|
|
||||||
def update_hysteria2():
|
def update_hysteria2():
|
||||||
'''Updates Hysteria2.'''
|
'''Updates Hysteria2.'''
|
||||||
run_cmd(['bash', Command.UPDATE_HYSTERIA2.value])
|
run_cmd(['python3', Command.UPDATE_HYSTERIA2.value])
|
||||||
|
|
||||||
|
|
||||||
def restart_hysteria2():
|
def restart_hysteria2():
|
||||||
'''Restarts Hysteria2.'''
|
'''Restarts Hysteria2.'''
|
||||||
run_cmd(['bash', Command.RESTART_HYSTERIA2.value])
|
run_cmd(['python3', Command.RESTART_HYSTERIA2.value])
|
||||||
|
|
||||||
|
|
||||||
def get_hysteria2_port() -> int | None:
|
def get_hysteria2_port() -> int | None:
|
||||||
@ -158,7 +161,7 @@ def change_hysteria2_port(port: int):
|
|||||||
'''
|
'''
|
||||||
Changes the port for Hysteria2.
|
Changes the port for Hysteria2.
|
||||||
'''
|
'''
|
||||||
run_cmd(['bash', Command.CHANGE_PORT_HYSTERIA2.value, str(port)])
|
run_cmd(['python3', Command.CHANGE_PORT_HYSTERIA2.value, str(port)])
|
||||||
|
|
||||||
|
|
||||||
def get_hysteria2_sni() -> str | None:
|
def get_hysteria2_sni() -> str | None:
|
||||||
@ -198,12 +201,12 @@ def restore_hysteria2(backup_file_path: str):
|
|||||||
|
|
||||||
def enable_hysteria2_obfs():
|
def enable_hysteria2_obfs():
|
||||||
'''Generates 'obfs' in Hysteria2 configuration.'''
|
'''Generates 'obfs' in Hysteria2 configuration.'''
|
||||||
run_cmd(['bash', Command.MANAGE_OBFS.value, '--generate'])
|
run_cmd(['python3', Command.MANAGE_OBFS.value, '--generate'])
|
||||||
|
|
||||||
|
|
||||||
def disable_hysteria2_obfs():
|
def disable_hysteria2_obfs():
|
||||||
'''Removes 'obfs' from Hysteria2 configuration.'''
|
'''Removes 'obfs' from Hysteria2 configuration.'''
|
||||||
run_cmd(['bash', Command.MANAGE_OBFS.value, '--remove'])
|
run_cmd(['python3', Command.MANAGE_OBFS.value, '--remove'])
|
||||||
|
|
||||||
|
|
||||||
def enable_hysteria2_masquerade(domain: str):
|
def enable_hysteria2_masquerade(domain: str):
|
||||||
@ -243,7 +246,7 @@ def get_user(username: str) -> dict[str, Any] | None:
|
|||||||
'''
|
'''
|
||||||
Retrieves information about a specific user.
|
Retrieves information about a specific user.
|
||||||
'''
|
'''
|
||||||
if res := run_cmd(['bash', Command.GET_USER.value, '-u', str(username)]):
|
if res := run_cmd(['python3', Command.GET_USER.value, '-u', str(username)]):
|
||||||
return json.loads(res)
|
return json.loads(res)
|
||||||
|
|
||||||
|
|
||||||
@ -255,7 +258,7 @@ def add_user(username: str, traffic_limit: int, expiration_days: int, password:
|
|||||||
password = generate_password()
|
password = generate_password()
|
||||||
if not creation_date:
|
if not creation_date:
|
||||||
creation_date = datetime.now().strftime('%Y-%m-%d')
|
creation_date = datetime.now().strftime('%Y-%m-%d')
|
||||||
run_cmd(['bash', Command.ADD_USER.value, username, str(traffic_limit), str(expiration_days), password, creation_date])
|
run_cmd(['python3', Command.ADD_USER.value, username, str(traffic_limit), str(expiration_days), password, creation_date])
|
||||||
|
|
||||||
|
|
||||||
def edit_user(username: str, new_username: str | None, new_traffic_limit: int | None, new_expiration_days: int | None, renew_password: bool, renew_creation_date: bool, blocked: bool):
|
def edit_user(username: str, new_username: str | None, new_traffic_limit: int | None, new_expiration_days: int | None, renew_password: bool, renew_creation_date: bool, blocked: bool):
|
||||||
@ -296,14 +299,14 @@ def reset_user(username: str):
|
|||||||
'''
|
'''
|
||||||
Resets a user's configuration.
|
Resets a user's configuration.
|
||||||
'''
|
'''
|
||||||
run_cmd(['bash', Command.RESET_USER.value, username])
|
run_cmd(['python3', Command.RESET_USER.value, username])
|
||||||
|
|
||||||
|
|
||||||
def remove_user(username: str):
|
def remove_user(username: str):
|
||||||
'''
|
'''
|
||||||
Removes a user by username.
|
Removes a user by username.
|
||||||
'''
|
'''
|
||||||
run_cmd(['bash', Command.REMOVE_USER.value, username])
|
run_cmd(['python3', Command.REMOVE_USER.value, username])
|
||||||
|
|
||||||
def kick_user_by_name(username: str):
|
def kick_user_by_name(username: str):
|
||||||
'''Kicks a specific user by username.'''
|
'''Kicks a specific user by username.'''
|
||||||
|
|||||||
117
core/scripts/hysteria2/add_user.py
Normal file
117
core/scripts/hysteria2/add_user.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def add_user(username, traffic_gb, expiration_days, password=None, creation_date=None):
|
||||||
|
"""
|
||||||
|
Adds a new user to the USERS_FILE.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username (str): The username to add.
|
||||||
|
traffic_gb (str): The traffic limit in GB.
|
||||||
|
expiration_days (str): The number of days until the account expires.
|
||||||
|
password (str, optional): The user's password. If None, a random one is generated.
|
||||||
|
creation_date (str, optional): The account creation date in YYYY-MM-DD format. If None, the current date is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 0 on success, 1 on failure.
|
||||||
|
"""
|
||||||
|
if not username or not traffic_gb or not expiration_days:
|
||||||
|
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date]")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
traffic_bytes = int(float(traffic_gb) * 1073741824)
|
||||||
|
expiration_days = int(expiration_days)
|
||||||
|
except ValueError:
|
||||||
|
print("Error: Traffic limit and expiration days must be numeric.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
username_lower = username.lower()
|
||||||
|
|
||||||
|
if not password:
|
||||||
|
try:
|
||||||
|
password_process = subprocess.run(['pwgen', '-s', '32', '1'], capture_output=True, text=True, check=True)
|
||||||
|
password = password_process.stdout.strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
try:
|
||||||
|
password = subprocess.check_output(['cat', '/proc/sys/kernel/random/uuid'], text=True).strip()
|
||||||
|
except Exception:
|
||||||
|
print("Error: Failed to generate password. Please install 'pwgen' or ensure /proc access.")
|
||||||
|
|
||||||
|
if not creation_date:
|
||||||
|
creation_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
else:
|
||||||
|
if not re.match(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$", creation_date):
|
||||||
|
print("Invalid date format. Expected YYYY-MM-DD.")
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
datetime.strptime(creation_date, "%Y-%m-%d")
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid date. Please provide a valid date in YYYY-MM-DD format.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not re.match(r"^[a-zA-Z0-9]+$", username):
|
||||||
|
print("Error: Username can only contain letters and numbers.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
except IOError:
|
||||||
|
print(f"Error: Could not create {USERS_FILE}.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r+') as f:
|
||||||
|
try:
|
||||||
|
users_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error: {USERS_FILE} contains invalid JSON.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for existing_username in users_data:
|
||||||
|
if existing_username.lower() == username_lower:
|
||||||
|
print("User already exists.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
users_data[username_lower] = {
|
||||||
|
"password": password,
|
||||||
|
"max_download_bytes": traffic_bytes,
|
||||||
|
"expiration_days": expiration_days,
|
||||||
|
"account_creation_date": creation_date,
|
||||||
|
"blocked": False
|
||||||
|
}
|
||||||
|
|
||||||
|
f.seek(0)
|
||||||
|
json.dump(users_data, f, indent=4)
|
||||||
|
f.truncate()
|
||||||
|
|
||||||
|
print(f"User {username} added successfully.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
print(f"Error: Could not write to {USERS_FILE}.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) not in [4, 6]:
|
||||||
|
print(f"Usage: {sys.argv[0]} <username> <traffic_limit_GB> <expiration_days> [password] [creation_date]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
username = sys.argv[1]
|
||||||
|
traffic_gb = sys.argv[2]
|
||||||
|
expiration_days = sys.argv[3]
|
||||||
|
password = sys.argv[4] if len(sys.argv) > 4 else None
|
||||||
|
creation_date = sys.argv[5] if len(sys.argv) > 5 else None
|
||||||
|
|
||||||
|
exit_code = add_user(username, traffic_gb, expiration_days, password, creation_date)
|
||||||
|
sys.exit(exit_code)
|
||||||
@ -1,62 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
add_user() {
|
|
||||||
if [ $# -ne 3 ] && [ $# -ne 5 ]; then
|
|
||||||
echo "Usage: $0 <username> <traffic_limit_GB> <expiration_days> [password] [creation_date]"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
username=$1
|
|
||||||
traffic_gb=$2
|
|
||||||
expiration_days=$3
|
|
||||||
password=$4
|
|
||||||
creation_date=$5
|
|
||||||
|
|
||||||
username_lower=$(echo "$username" | tr '[:upper:]' '[:lower:]')
|
|
||||||
|
|
||||||
if [ -z "$password" ]; then
|
|
||||||
password=$(pwgen -s 32 1)
|
|
||||||
fi
|
|
||||||
if [ -z "$creation_date" ]; then
|
|
||||||
creation_date=$(date +%Y-%m-%d)
|
|
||||||
else
|
|
||||||
if ! [[ "$creation_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
|
||||||
echo "Invalid date format. Expected YYYY-MM-DD."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! date -d "$creation_date" >/dev/null 2>&1; then
|
|
||||||
echo "Invalid date. Please provide a valid date in YYYY-MM-DD format."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
|
||||||
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
traffic=$(echo "$traffic_gb * 1073741824" | bc)
|
|
||||||
|
|
||||||
if [ ! -f "$USERS_FILE" ]; then
|
|
||||||
echo "{}" > "$USERS_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
user_exists=$(jq --arg username "$username_lower" '
|
|
||||||
to_entries[] | select(.key | ascii_downcase == $username) | .key' "$USERS_FILE")
|
|
||||||
|
|
||||||
if [ -n "$user_exists" ]; then
|
|
||||||
echo "User already exists."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
jq --arg username "$username_lower" --arg password "$password" --argjson traffic "$traffic" --argjson expiration_days "$expiration_days" --arg creation_date "$creation_date" \
|
|
||||||
'.[$username] = {password: $password, max_download_bytes: $traffic, expiration_days: $expiration_days, account_creation_date: $creation_date, blocked: false}' \
|
|
||||||
"$USERS_FILE" > "${USERS_FILE}.temp" && mv "${USERS_FILE}.temp" "$USERS_FILE"
|
|
||||||
|
|
||||||
echo -e "User $username added successfully."
|
|
||||||
}
|
|
||||||
|
|
||||||
add_user "$1" "$2" "$3" "$4" "$5"
|
|
||||||
52
core/scripts/hysteria2/change_port.py
Normal file
52
core/scripts/hysteria2/change_port.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def update_port(port):
|
||||||
|
"""
|
||||||
|
Update the port in the configuration file and restart the service.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port (str): The port number to set
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not re.match(r'^[0-9]+$', port) or int(port) < 1 or int(port) > 65535:
|
||||||
|
print("Invalid port number. Please enter a number between 1 and 65535.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Config file {CONFIG_FILE} not found.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
config['listen'] = f":{port}"
|
||||||
|
|
||||||
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],)
|
||||||
|
|
||||||
|
print(f"Port changed successfully to {port}.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating port: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python update_port.py <port_number>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
success = update_port(sys.argv[1])
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
update_port() {
|
|
||||||
local port=$1
|
|
||||||
|
|
||||||
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
|
|
||||||
echo "Invalid port number. Please enter a number between 1 and 65535."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
|
||||||
jq --arg port "$port" '.listen = ":" + $port' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
|
|
||||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
echo "Port changed successfully to $port."
|
|
||||||
else
|
|
||||||
echo "Error: Config file $CONFIG_FILE not found."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
update_port "$1"
|
|
||||||
64
core/scripts/hysteria2/get_user.py
Normal file
64
core/scripts/hysteria2/get_user.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def get_user_info(username):
|
||||||
|
"""
|
||||||
|
Retrieves and prints information for a specific user from the USERS_FILE.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username (str): The username to look up.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 0 on success, 1 on failure.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
print(f"users.json file not found at {USERS_FILE}!")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r') as f:
|
||||||
|
users_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error: {USERS_FILE} contains invalid JSON.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if username in users_data:
|
||||||
|
user_info = users_data[username]
|
||||||
|
print(json.dumps(user_info, indent=4)) # Print with indentation for readability
|
||||||
|
# upload_bytes = user_info.get('upload_bytes', "No upload data available")
|
||||||
|
# download_bytes = user_info.get('download_bytes', "No download data available")
|
||||||
|
# status = user_info.get('status', "Status unavailable")
|
||||||
|
# You can choose to print these individually as well, if needed
|
||||||
|
# print(f"Upload Bytes: {upload_bytes}")
|
||||||
|
# print(f"Download Bytes: {download_bytes}")
|
||||||
|
# print(f"Status: {status}")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print(f"User '{username}' not found in {USERS_FILE}.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
username = None
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "u:", ["username="])
|
||||||
|
except getopt.GetoptError as err:
|
||||||
|
print(str(err))
|
||||||
|
print(f"Usage: {sys.argv[0]} -u <username>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ("-u", "--username"):
|
||||||
|
username = arg
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
print(f"Usage: {sys.argv[0]} -u <username>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
exit_code = get_user_info(username)
|
||||||
|
sys.exit(exit_code)
|
||||||
@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
while getopts ":u:" opt; do
|
|
||||||
case ${opt} in
|
|
||||||
u )
|
|
||||||
USERNAME=$OPTARG
|
|
||||||
;;
|
|
||||||
\? )
|
|
||||||
echo "Usage: $0 -u <username>"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$USERNAME" ]; then
|
|
||||||
echo "Usage: $0 -u <username>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "$USERS_FILE" ]; then
|
|
||||||
echo "users.json file not found at $USERS_FILE!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
USER_INFO=$(jq -r --arg username "$USERNAME" '.[$username] // empty' "$USERS_FILE")
|
|
||||||
|
|
||||||
if [ -z "$USER_INFO" ]; then
|
|
||||||
echo "User '$USERNAME' not found in $USERS_FILE."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$USER_INFO" | jq .
|
|
||||||
|
|
||||||
UPLOAD_BYTES=$(echo "$USER_INFO" | jq -r '.upload_bytes // "No upload data available"')
|
|
||||||
DOWNLOAD_BYTES=$(echo "$USER_INFO" | jq -r '.download_bytes // "No download data available"')
|
|
||||||
STATUS=$(echo "$USER_INFO" | jq -r '.status // "Status unavailable"')
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@ -18,26 +18,8 @@ install_hysteria() {
|
|||||||
echo "Downloading geo data..."
|
echo "Downloading geo data..."
|
||||||
wget -O /etc/hysteria/geosite.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat >/dev/null 2>&1
|
wget -O /etc/hysteria/geosite.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat >/dev/null 2>&1
|
||||||
wget -O /etc/hysteria/geoip.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat >/dev/null 2>&1
|
wget -O /etc/hysteria/geoip.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat >/dev/null 2>&1
|
||||||
|
|
||||||
# fingerprint=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s/://g')
|
|
||||||
|
|
||||||
echo "Generating base64 encoded SHA-256 fingerprint..."
|
echo "Generating base64 encoded SHA-256 fingerprint..."
|
||||||
# cat <<EOF > generate.py
|
|
||||||
# import base64
|
|
||||||
# import binascii
|
|
||||||
|
|
||||||
# # Hexadecimal string
|
|
||||||
# hex_string = "$fingerprint"
|
|
||||||
|
|
||||||
# # Convert hex to binary
|
|
||||||
# binary_data = binascii.unhexlify(hex_string)
|
|
||||||
|
|
||||||
# # Encode binary data to base64
|
|
||||||
# base64_encoded = base64.b64encode(binary_data).decode('utf-8')
|
|
||||||
|
|
||||||
# # Print the result prefixed with 'sha256/'
|
|
||||||
# print('sha256/' + base64_encoded)
|
|
||||||
# EOF
|
|
||||||
|
|
||||||
sha256=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s///g')
|
sha256=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s///g')
|
||||||
|
|
||||||
@ -78,8 +60,8 @@ install_hysteria() {
|
|||||||
.trafficStats.secret = $UUID |
|
.trafficStats.secret = $UUID |
|
||||||
.outbounds[0].direct.bindDevice = $networkdef' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
|
.outbounds[0].direct.bindDevice = $networkdef' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
|
||||||
|
|
||||||
echo "Updating hysteria-server.service to use config.json..."
|
echo "Updating hysteria-server.service to use Blitz Panel config.json..."
|
||||||
sed -i 's|(config.yaml)||' /etc/systemd/system/hysteria-server.service
|
sed -i 's|(config.yaml)|(Blitz Panel)|' /etc/systemd/system/hysteria-server.service
|
||||||
sed -i "s|/etc/hysteria/config.yaml|$CONFIG_FILE|" /etc/systemd/system/hysteria-server.service
|
sed -i "s|/etc/hysteria/config.yaml|$CONFIG_FILE|" /etc/systemd/system/hysteria-server.service
|
||||||
rm /etc/hysteria/config.yaml
|
rm /etc/hysteria/config.yaml
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|||||||
89
core/scripts/hysteria2/manage_obfs.py
Normal file
89
core/scripts/hysteria2/manage_obfs.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import string
|
||||||
|
import secrets
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def restart_hysteria():
|
||||||
|
"""Restart the Hysteria2 service using the CLI script."""
|
||||||
|
try:
|
||||||
|
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to restart Hysteria2: {e}")
|
||||||
|
|
||||||
|
def remove_obfs():
|
||||||
|
"""Remove the 'obfs' section from the config."""
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
if 'obfs' in config:
|
||||||
|
del config['obfs']
|
||||||
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
print("✅ Successfully removed 'obfs' from config.json.")
|
||||||
|
else:
|
||||||
|
print("ℹ️ 'obfs' section not found in config.json.")
|
||||||
|
|
||||||
|
restart_hysteria()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"❌ Config file not found: {CONFIG_FILE}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error removing 'obfs': {e}")
|
||||||
|
|
||||||
|
def generate_obfs():
|
||||||
|
"""Generate and add an 'obfs' section with a random password."""
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
if 'obfs' in config:
|
||||||
|
print("ℹ️ 'obfs' section already exists. Replacing it.")
|
||||||
|
del config['obfs']
|
||||||
|
|
||||||
|
password = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32))
|
||||||
|
|
||||||
|
config['obfs'] = {
|
||||||
|
"type": "salamander",
|
||||||
|
"salamander": {
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(CONFIG_FILE, 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
print(f"✅ Successfully added 'obfs' to config.json with password: {password}")
|
||||||
|
|
||||||
|
restart_hysteria()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"❌ Config file not found: {CONFIG_FILE}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error generating 'obfs': {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("Usage: python3 obfs_manager.py --remove|-r | --generate|-g")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
option = sys.argv[1]
|
||||||
|
if option in ("--remove", "-r"):
|
||||||
|
print("Removing 'obfs' from config.json...")
|
||||||
|
remove_obfs()
|
||||||
|
elif option in ("--generate", "-g"):
|
||||||
|
print("Generating 'obfs' in config.json...")
|
||||||
|
generate_obfs()
|
||||||
|
else:
|
||||||
|
print("Invalid option. Use --remove|-r or --generate|-g")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -1,44 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
remove_obfs() {
|
|
||||||
if jq 'has("obfs")' "$CONFIG_FILE" | grep -q true; then
|
|
||||||
jq 'del(.obfs)' "$CONFIG_FILE" > temp_config.json && mv temp_config.json "$CONFIG_FILE"
|
|
||||||
echo "Successfully removed 'obfs' from config.json."
|
|
||||||
else
|
|
||||||
echo "'obfs' section not found in config.json."
|
|
||||||
fi
|
|
||||||
|
|
||||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_obfs() {
|
|
||||||
obfspassword=$(pwgen -s 32 1)
|
|
||||||
|
|
||||||
if jq 'has("obfs")' "$CONFIG_FILE" | grep -q true; then
|
|
||||||
echo "'obfs' section already exists. Replacing it with a new one."
|
|
||||||
jq 'del(.obfs)' "$CONFIG_FILE" > temp_config.json && mv temp_config.json "$CONFIG_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
jq '. + {obfs: {type: "salamander", salamander: {password: "'"$obfspassword"'"}}}' "$CONFIG_FILE" > temp_config.json && mv temp_config.json "$CONFIG_FILE"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "Successfully added 'obfs' to config.json with password: $obfspassword"
|
|
||||||
else
|
|
||||||
echo "Error: Failed to add 'obfs' to config.json."
|
|
||||||
fi
|
|
||||||
|
|
||||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ $1 == "--remove" || $1 == "-r" ]]; then
|
|
||||||
echo "Removing 'obfs' from config.json..."
|
|
||||||
remove_obfs
|
|
||||||
elif [[ $1 == "--generate" || $1 == "-g" ]]; then
|
|
||||||
echo "Generating 'obfs' in config.json..."
|
|
||||||
generate_obfs
|
|
||||||
else
|
|
||||||
echo "Usage: $0 --remove|-r | --generate|-g"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
46
core/scripts/hysteria2/remove_user.py
Normal file
46
core/scripts/hysteria2/remove_user.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def sync_remove_user(username):
|
||||||
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
return 1, f"Error: Config file {USERS_FILE} not found."
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r') as f:
|
||||||
|
try:
|
||||||
|
users_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return 1, f"Error: {USERS_FILE} contains invalid JSON."
|
||||||
|
|
||||||
|
if username in users_data:
|
||||||
|
del users_data[username]
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump(users_data, f, indent=4)
|
||||||
|
return 0, f"User {username} removed successfully."
|
||||||
|
else:
|
||||||
|
return 1, f"Error: User {username} not found."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return 1, f"Error: {str(e)}"
|
||||||
|
|
||||||
|
async def remove_user(username):
|
||||||
|
return await asyncio.to_thread(sync_remove_user, username)
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print(f"Usage: {sys.argv[0]} <username>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
username = sys.argv[1]
|
||||||
|
exit_code, message = await remove_user(username)
|
||||||
|
print(message)
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@ -1,28 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
source /etc/hysteria/core/scripts/utils.sh
|
|
||||||
define_colors
|
|
||||||
|
|
||||||
remove_user() {
|
|
||||||
if [ $# -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <username>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local username=$1
|
|
||||||
|
|
||||||
if [ -f "$USERS_FILE" ]; then
|
|
||||||
if jq -e "has(\"$username\")" "$USERS_FILE" > /dev/null; then
|
|
||||||
jq --arg username "$username" 'del(.[$username])' "$USERS_FILE" > "${USERS_FILE}.temp" && mv "${USERS_FILE}.temp" "$USERS_FILE"
|
|
||||||
|
|
||||||
echo "User $username removed successfully."
|
|
||||||
else
|
|
||||||
echo -e "${red}Error:${NC} User $username not found."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Error:${NC} Config file $USERS_FILE not found."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
# python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
remove_user "$1"
|
|
||||||
58
core/scripts/hysteria2/reset_user.py
Normal file
58
core/scripts/hysteria2/reset_user.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import date
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def reset_user(username):
|
||||||
|
"""
|
||||||
|
Resets the data usage, status, and creation date of a user in the USERS_FILE.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username (str): The username to reset.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 0 on success, 1 on failure.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
print(f"Error: File '{USERS_FILE}' not found.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r') as f:
|
||||||
|
users_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error: {USERS_FILE} contains invalid JSON.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if username not in users_data:
|
||||||
|
print(f"Error: User '{username}' not found in '{USERS_FILE}'.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
today = date.today().strftime("%Y-%m-%d")
|
||||||
|
users_data[username]['upload_bytes'] = 0
|
||||||
|
users_data[username]['download_bytes'] = 0
|
||||||
|
users_data[username]['status'] = "Offline"
|
||||||
|
users_data[username]['account_creation_date'] = today
|
||||||
|
users_data[username]['blocked'] = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump(users_data, f, indent=4)
|
||||||
|
print(f"User '{username}' has been reset successfully.")
|
||||||
|
return 0
|
||||||
|
except IOError:
|
||||||
|
print(f"Error: Failed to reset user '{username}' in '{USERS_FILE}'.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print(f"Usage: {sys.argv[0]} <username>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
username_to_reset = sys.argv[1]
|
||||||
|
exit_code = reset_user(username_to_reset)
|
||||||
|
sys.exit(exit_code)
|
||||||
@ -1,44 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Source required scripts
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
reset_user() {
|
|
||||||
local username=$1
|
|
||||||
|
|
||||||
if [ ! -f "$USERS_FILE" ]; then
|
|
||||||
echo "Error: File '$USERS_FILE' not found."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
user_exists=$(jq -e --arg username "$username" '.[$username]' "$USERS_FILE")
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: User '$username' not found in '$USERS_FILE'."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
today=$(date +%Y-%m-%d)
|
|
||||||
jq --arg username "$username" \
|
|
||||||
--arg today "$today" \
|
|
||||||
'
|
|
||||||
.[$username].upload_bytes = 0 |
|
|
||||||
.[$username].download_bytes = 0 |
|
|
||||||
.[$username].status = "Offline" |
|
|
||||||
.[$username].account_creation_date = $today |
|
|
||||||
.[$username].blocked = false
|
|
||||||
' "$USERS_FILE" > tmp.$$.json && mv tmp.$$.json "$USERS_FILE"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to reset user '$username' in '$USERS_FILE'."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "User '$username' has been reset successfully."
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $# -ne 1 ]; then
|
|
||||||
echo "Usage: $0 <username>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
reset_user "$1"
|
|
||||||
34
core/scripts/hysteria2/restart.py
Normal file
34
core/scripts/hysteria2/restart.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def restart_hysteria_server():
|
||||||
|
"""
|
||||||
|
Restarts the Hysteria server service.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 0 on success, 1 on failure.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
subprocess.run([sys.executable, CLI_PATH, "traffic-status"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
subprocess.run(["systemctl", "restart", "hysteria-server.service"], check=True)
|
||||||
|
print("Hysteria server restarted successfully.")
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error: Failed to restart the Hysteria server.")
|
||||||
|
return 1
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: CLI script not found at {CLI_PATH}.")
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit_code = restart_hysteria_server()
|
||||||
|
sys.exit(exit_code)
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
python3 /etc/hysteria/core/cli.py traffic-status > /dev/null 2>&1
|
|
||||||
if systemctl restart hysteria-server.service; then
|
|
||||||
echo "Hysteria server restarted successfully."
|
|
||||||
else
|
|
||||||
echo "Error: Failed to restart the Hysteria server."
|
|
||||||
fi
|
|
||||||
94
core/scripts/hysteria2/uninstall.py
Normal file
94
core/scripts/hysteria2/uninstall.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
SERVICES = [
|
||||||
|
"hysteria-server.service",
|
||||||
|
"hysteria-webpanel.service",
|
||||||
|
"hysteria-caddy.service",
|
||||||
|
"hysteria-telegram-bot.service",
|
||||||
|
"hysteria-normal-sub.service",
|
||||||
|
"hysteria-singbox.service",
|
||||||
|
"hysteria-ip-limit.service",
|
||||||
|
]
|
||||||
|
|
||||||
|
def run_command(command, error_message):
|
||||||
|
"""Runs a command and prints an error message if it fails."""
|
||||||
|
try:
|
||||||
|
subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(error_message)
|
||||||
|
return 1
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Command not found: {command[0]}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def uninstall_hysteria():
|
||||||
|
"""Uninstalls Hysteria2."""
|
||||||
|
print("Uninstalling Hysteria2...")
|
||||||
|
|
||||||
|
print("Running uninstallation script...")
|
||||||
|
run_command(["bash", "-c", "curl -fsSL https://get.hy2.sh/ | bash -- --remove"], "Error running the official uninstallation script.")
|
||||||
|
|
||||||
|
print("Removing WARP")
|
||||||
|
cli_path = "/etc/hysteria/core/cli.py"
|
||||||
|
if os.path.exists(cli_path):
|
||||||
|
run_command([sys.executable, cli_path, "uninstall-warp"], "Error during WARP removal.")
|
||||||
|
else:
|
||||||
|
print("Skipping WARP removal (CLI path not found)")
|
||||||
|
|
||||||
|
print("Removing Hysteria folder...")
|
||||||
|
run_command(["rm", "-rf", "/etc/hysteria"], "Error removing the Hysteria folder.")
|
||||||
|
|
||||||
|
print("Deleting hysteria user...")
|
||||||
|
run_command(["userdel", "-r", "hysteria"], "Error deleting the hysteria user.")
|
||||||
|
|
||||||
|
print("Stop/Disabling Hysteria Services...")
|
||||||
|
for service in SERVICES + ["hysteria-server@*.service"]:
|
||||||
|
print(f"Stopping and disabling {service}...")
|
||||||
|
run_command(["systemctl", "stop", service], f"Error stopping {service}.")
|
||||||
|
run_command(["systemctl", "disable", service], f"Error disabling {service}.")
|
||||||
|
|
||||||
|
print("Removing systemd service files...")
|
||||||
|
for service in SERVICES + ["hysteria-server@*.service"]:
|
||||||
|
print(f"Removing service file: {service}")
|
||||||
|
run_command(["rm", "-f", f"/etc/systemd/system/{service}", f"/etc/systemd/system/multi-user.target.wants/{service}"], f"Error removing service files for {service}.")
|
||||||
|
|
||||||
|
print("Reloading systemd daemon...")
|
||||||
|
run_command(["systemctl", "daemon-reload"], "Error reloading systemd daemon.")
|
||||||
|
|
||||||
|
print("Removing cron jobs...")
|
||||||
|
try:
|
||||||
|
crontab_list = subprocess.run(["crontab", "-l"], capture_output=True, text=True, check=False)
|
||||||
|
if "hysteria" in crontab_list.stdout:
|
||||||
|
new_crontab = "\n".join(line for line in crontab_list.stdout.splitlines() if "hysteria" not in line)
|
||||||
|
process = subprocess.run(["crontab", "-"], input=new_crontab.encode(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("Warning: crontab command not found.")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print("Warning: Could not access crontab.")
|
||||||
|
|
||||||
|
print("Removing alias 'hys2' from .bashrc...")
|
||||||
|
bashrc_path = os.path.expanduser("~/.bashrc")
|
||||||
|
if os.path.exists(bashrc_path):
|
||||||
|
try:
|
||||||
|
with open(bashrc_path, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
with open(bashrc_path, 'w') as f:
|
||||||
|
for line in lines:
|
||||||
|
if 'alias hys2=' not in line:
|
||||||
|
f.write(line)
|
||||||
|
except IOError:
|
||||||
|
print(f"Warning: Could not access or modify {bashrc_path}.")
|
||||||
|
else:
|
||||||
|
print(f"Warning: {bashrc_path} not found.")
|
||||||
|
|
||||||
|
print("Hysteria2 uninstalled!")
|
||||||
|
print("Rebooting server...")
|
||||||
|
run_command(["reboot"], "Error initiating reboot.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uninstall_hysteria()
|
||||||
@ -1,56 +0,0 @@
|
|||||||
source /etc/hysteria/core/scripts/path.sh || true
|
|
||||||
|
|
||||||
echo "Uninstalling Hysteria2..."
|
|
||||||
|
|
||||||
SERVICES=(
|
|
||||||
"hysteria-server.service"
|
|
||||||
"hysteria-webpanel.service"
|
|
||||||
"hysteria-caddy.service"
|
|
||||||
"hysteria-telegram-bot.service"
|
|
||||||
"hysteria-normal-sub.service"
|
|
||||||
"hysteria-singbox.service"
|
|
||||||
"hysteria-ip-limit.service"
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "Running uninstallation script..."
|
|
||||||
bash <(curl -fsSL https://get.hy2.sh/) --remove >/dev/null 2>&1
|
|
||||||
|
|
||||||
echo "Removing WARP"
|
|
||||||
if command -v python3 &> /dev/null && [ -f "$CLI_PATH" ]; then
|
|
||||||
python3 "$CLI_PATH" uninstall-warp || true
|
|
||||||
else
|
|
||||||
echo "Skipping WARP removal (python3 or CLI_PATH not found)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing Hysteria folder..."
|
|
||||||
rm -rf /etc/hysteria >/dev/null 2>&1
|
|
||||||
|
|
||||||
echo "Deleting hysteria user..."
|
|
||||||
userdel -r hysteria >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
echo "Stop/Disabling Hysteria Services..."
|
|
||||||
for service in "${SERVICES[@]}" "hysteria-server@*.service"; do
|
|
||||||
echo "Stopping and disabling $service..."
|
|
||||||
systemctl stop "$service" > /dev/null 2>&1 || true
|
|
||||||
systemctl disable "$service" > /dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Removing systemd service files..."
|
|
||||||
for service in "${SERVICES[@]}" "hysteria-server@*.service"; do
|
|
||||||
echo "Removing service file: $service"
|
|
||||||
rm -f "/etc/systemd/system/$service" "/etc/systemd/system/multi-user.target.wants/$service" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Reloading systemd daemon..."
|
|
||||||
systemctl daemon-reload >/dev/null 2>&1
|
|
||||||
|
|
||||||
echo "Removing cron jobs..."
|
|
||||||
if crontab -l 2>/dev/null | grep -q "hysteria"; then
|
|
||||||
(crontab -l | grep -v "hysteria" | crontab -) >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing alias 'hys2' from .bashrc..."
|
|
||||||
sed -i '/alias hys2=.*\/etc\/hysteria\/menu.sh/d' ~/.bashrc 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "Hysteria2 uninstalled!"
|
|
||||||
echo ""
|
|
||||||
105
core/scripts/hysteria2/update.py
Normal file
105
core/scripts/hysteria2/update.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
CONFIG_BACKUP = "/etc/hysteria/config_backup.json"
|
||||||
|
SERVICE_FILE = "/etc/systemd/system/hysteria-server.service"
|
||||||
|
OLD_CONFIG_PATH = "/etc/hysteria/config.yaml"
|
||||||
|
|
||||||
|
def backup_config():
|
||||||
|
print("📦 Backing up the current configuration...")
|
||||||
|
try:
|
||||||
|
shutil.copy(CONFIG_FILE, CONFIG_BACKUP)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: Failed to back up configuration: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restore_config():
|
||||||
|
print("♻️ Restoring configuration from backup...")
|
||||||
|
try:
|
||||||
|
shutil.move(CONFIG_BACKUP, CONFIG_FILE)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: Failed to restore configuration: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def install_latest_hysteria():
|
||||||
|
print("⬇️ Downloading and installing the latest version of Hysteria2...")
|
||||||
|
try:
|
||||||
|
cmd = 'bash -c "$(curl -fsSL https://get.hy2.sh/)"'
|
||||||
|
result = subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
return result.returncode == 0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error during installation: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def modify_systemd_service():
|
||||||
|
print("⚙️ Modifying systemd service to use config.json...")
|
||||||
|
try:
|
||||||
|
with open(SERVICE_FILE, 'r') as f:
|
||||||
|
service_data = f.read()
|
||||||
|
|
||||||
|
new_data = service_data.replace(
|
||||||
|
"Description=Hysteria Server Service (config.yaml)",
|
||||||
|
"Description=Hysteria Server Service (Blitz Panel)"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_data = new_data.replace(str(OLD_CONFIG_PATH), str(CONFIG_FILE))
|
||||||
|
|
||||||
|
with open(SERVICE_FILE, 'w') as f:
|
||||||
|
f.write(new_data)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: Failed to modify systemd service: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def restart_hysteria():
|
||||||
|
print("🔄 Restarting Hysteria2 service...")
|
||||||
|
try:
|
||||||
|
subprocess.run(["systemctl", "daemon-reload"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
subprocess.run(["python3", CLI_PATH, "restart-hysteria2"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: Failed to restart Hysteria2: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🚀 Starting the update process for Hysteria2...")
|
||||||
|
|
||||||
|
if not backup_config():
|
||||||
|
print("❌ Aborting update due to failed backup.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not install_latest_hysteria():
|
||||||
|
print("❌ Installation failed. Restoring previous config...")
|
||||||
|
restore_config()
|
||||||
|
restart_hysteria()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not restore_config():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not modify_systemd_service():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.exists(OLD_CONFIG_PATH):
|
||||||
|
os.remove(OLD_CONFIG_PATH)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Failed to remove old YAML config: {e}")
|
||||||
|
|
||||||
|
if not restart_hysteria():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("\n✅ Hysteria2 has been successfully updated.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -1,47 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Source the path.sh script to load the CONFIG_FILE variable
|
|
||||||
source /etc/hysteria/core/scripts/path.sh
|
|
||||||
|
|
||||||
echo "Starting the update process for Hysteria2..."
|
|
||||||
echo "Backing up the current configuration..."
|
|
||||||
cp "$CONFIG_FILE" /etc/hysteria/config_backup.json
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to back up configuration. Aborting update."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Downloading and installing the latest version of Hysteria2..."
|
|
||||||
bash <(curl -fsSL https://get.hy2.sh/) >/dev/null 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to download or install the latest version. Restoring backup configuration."
|
|
||||||
mv /etc/hysteria/config_backup.json "$CONFIG_FILE"
|
|
||||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Restoring configuration from backup..."
|
|
||||||
mv /etc/hysteria/config_backup.json "$CONFIG_FILE"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to restore configuration from backup."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Modifying systemd service to use config.json..."
|
|
||||||
sed -i "s|/etc/hysteria/config.yaml|$CONFIG_FILE|" /etc/systemd/system/hysteria-server.service
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to modify systemd service."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm /etc/hysteria/config.yaml
|
|
||||||
systemctl daemon-reload >/dev/null 2>&1
|
|
||||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to restart Hysteria2 service."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Hysteria2 has been successfully updated."
|
|
||||||
echo ""
|
|
||||||
exit 0
|
|
||||||
4
menu.sh
4
menu.sh
@ -180,8 +180,8 @@ hysteria2_get_user_handler() {
|
|||||||
|
|
||||||
user_data=$(python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null)
|
user_data=$(python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null)
|
||||||
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $exit_code -ne 0 || -z "$user_data" ]]; then
|
||||||
echo -e "${red}Error:${NC} User '$username' not found."
|
echo -e "${red}Error:${NC} User '$username' not found or invalid response."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user