Merge pull request #134 from ReturnFI/beta

feat(core): Comprehensive refactoring and enhancements
This commit is contained in:
Whispering Wind
2025-05-02 15:50:00 +03:30
committed by GitHub
21 changed files with 688 additions and 396 deletions

View File

@ -17,20 +17,20 @@ WEBPANEL_ENV_FILE = '/etc/hysteria/core/scripts/webpanel/.env'
class Command(Enum):
'''Contains path to command's script'''
INSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'install.sh')
UNINSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'uninstall.sh')
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.sh')
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.sh')
CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.sh')
UNINSTALL_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'uninstall.py')
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.py')
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.py')
CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.py')
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.sh')
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.sh')
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.sh')
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py')
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.sh')
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.sh')
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.py')
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')
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')
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')
@ -110,8 +110,11 @@ def generate_password() -> str:
'''
try:
return subprocess.check_output(['pwgen', '-s', '32', '1'], shell=False).decode().strip()
except subprocess.CalledProcessError as e:
raise PasswordGenerationError(f'Failed to generate password: {e}')
except (subprocess.CalledProcessError, FileNotFoundError):
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
@ -129,17 +132,17 @@ def install_hysteria2(port: int, sni: str):
def uninstall_hysteria2():
'''Uninstalls Hysteria2.'''
run_cmd(['bash', Command.UNINSTALL_HYSTERIA2.value])
run_cmd(['python3', Command.UNINSTALL_HYSTERIA2.value])
def update_hysteria2():
'''Updates Hysteria2.'''
run_cmd(['bash', Command.UPDATE_HYSTERIA2.value])
run_cmd(['python3', Command.UPDATE_HYSTERIA2.value])
def restart_hysteria2():
'''Restarts Hysteria2.'''
run_cmd(['bash', Command.RESTART_HYSTERIA2.value])
run_cmd(['python3', Command.RESTART_HYSTERIA2.value])
def get_hysteria2_port() -> int | None:
@ -158,7 +161,7 @@ def change_hysteria2_port(port: int):
'''
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:
@ -198,12 +201,12 @@ def restore_hysteria2(backup_file_path: str):
def enable_hysteria2_obfs():
'''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():
'''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):
@ -243,7 +246,7 @@ def get_user(username: str) -> dict[str, Any] | None:
'''
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)
@ -255,7 +258,7 @@ def add_user(username: str, traffic_limit: int, expiration_days: int, password:
password = generate_password()
if not creation_date:
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):
@ -296,14 +299,14 @@ def reset_user(username: str):
'''
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):
'''
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):
'''Kicks a specific user by username.'''

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

View File

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

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

View File

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

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

View File

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

View File

@ -19,25 +19,7 @@ install_hysteria() {
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
# fingerprint=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s/://g')
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')
@ -78,8 +60,8 @@ install_hysteria() {
.trafficStats.secret = $UUID |
.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..."
sed -i 's|(config.yaml)||' /etc/systemd/system/hysteria-server.service
echo "Updating hysteria-server.service to use Blitz Panel config.json..."
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
rm /etc/hysteria/config.yaml
sleep 1

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

View File

@ -180,8 +180,8 @@ hysteria2_get_user_handler() {
user_data=$(python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo -e "${red}Error:${NC} User '$username' not found."
if [[ $exit_code -ne 0 || -z "$user_data" ]]; then
echo -e "${red}Error:${NC} User '$username' not found or invalid response."
return 1
fi