Merge pull request #1 from Iam54r1n4/main

Add cli and such...
This commit is contained in:
Whispering Wind
2024-07-29 17:44:05 +03:30
committed by GitHub
25 changed files with 1321 additions and 629 deletions

View File

@ -14,7 +14,7 @@
},
"auth": {
"type": "command",
"command": "/etc/hysteria/users/user.sh"
"command": "/etc/hysteria/core/scripts/hysteria2/user.sh"
},
"quic": {
"initStreamReceiveWindow": 8388608,
@ -80,4 +80,4 @@
"listen": "127.0.0.1:25413",
"secret": "$UUID"
}
}
}

229
core/cli.py Normal file
View File

@ -0,0 +1,229 @@
#!/usr/bin/env python3
from datetime import datetime
import os
import io
import click
import subprocess
from enum import Enum
import traffic
import validator
SCRIPT_DIR = '/etc/hysteria/core/scripts'
DEBUG = True
class Command(Enum):
'''Constais 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')
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.sh')
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.sh')
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.sh')
SHOW_USER_URI = os.path.join(SCRIPT_DIR, 'hysteria2', 'show_user_uri.sh')
TRAFFIC_STATUS = 'traffic.py' # won't be call directly (it's a python module)
LIST_USERS = os.path.join(SCRIPT_DIR, 'hysteria2', 'list_users.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')
# region utils
def run_cmd(command: list[str]):
'''
Runs a command and returns the output.
Could raise subprocess.CalledProcessError
'''
if DEBUG and Command.GET_USER.value not in command and Command.LIST_USERS.value not in command:
print(' '.join(command))
result = subprocess.check_output(command, shell=False)
if DEBUG:
print(result.decode().strip())
def generate_password() -> str:
'''
Generates a random password using pwgen for user.
Could raise subprocess.CalledProcessError
'''
return subprocess.check_output(['pwgen', '-s', '32', '1'], shell=False).decode().strip()
# endregion
@click.group()
def cli():
pass
# region hysteria2 menu options
@cli.command('install-hysteria2')
@click.option('--port', '-p', required=True, help='New port for Hysteria2', type=int, callback=validator.validate_port)
def install_hysteria2(port: int):
run_cmd(['bash', Command.INSTALL_HYSTERIA2.value, str(port)])
@cli.command('uninstall-hysteria2')
def uninstall_hysteria2():
run_cmd(['bash', Command.UNINSTALL_HYSTERIA2.value])
@cli.command('update-hysteria2')
def update_hysteria2():
run_cmd(['bash', Command.UPDATE_HYSTERIA2.value])
@cli.command('restart-hysteria2')
def restart_hysteria2():
run_cmd(['bash', Command.RESTART_HYSTERIA2.value])
@cli.command('change-hysteria2-port')
@click.option('--port', '-p', required=True, help='New port for Hysteria2', type=int, callback=validator.validate_port)
def change_hysteria2_port(port: int):
run_cmd(['bash', Command.CHANGE_PORT_HYSTERIA2.value, str(port)])
@cli.command('get-user')
@click.option('--username', '-u', required=True, help='Username for the user to get', type=str)
def get_user(username: str):
run_cmd(['bash', Command.GET_USER.value, username])
@cli.command('add-user')
@click.option('--username', '-u', required=True, help='Username for the new user', type=str)
@click.option('--traffic-limit', '-t', required=True, help='Traffic limit for the new user in GB', type=int)
@click.option('--expiration-days', '-e', required=True, help='Expiration days for the new user', type=int)
@click.option('--password', '-p', required=False, help='Password for the user', type=str)
@click.option('--creation-date', '-c', required=False, help='Creation date for the user', type=str)
def add_user(username: str, traffic_limit: int, expiration_days: int, password: str, creation_date: str):
if not password:
try:
password = generate_password()
except subprocess.CalledProcessError as e:
print(f'Error: failed to generate password\n{e}')
exit(1)
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])
@cli.command('edit-user')
@click.option('--username', '-u', required=True, help='Username for the user to edit', type=str)
@click.option('--new-username', '-nu', required=False, help='New username for the user', type=str)
@click.option('--new-traffic-limit', '-nt', required=False, help='Traffic limit for the new user in GB', type=int)
@click.option('--new-expiration-days', '-ne', required=False, help='Expiration days for the new user', type=int)
@click.option('--renew-password', '-rp', is_flag=True, help='Renew password for the user')
@click.option('--renew-creation-date', '-rc', is_flag=True, help='Renew creation date for the user')
@click.option('--blocked', '-b', is_flag=True, help='Block the user')
def edit_user(username: str, new_username: str, new_traffic_limit: int, new_expiration_days: int, renew_password: bool, renew_creation_date: bool, blocked: bool):
if not username:
print('Error: username is required')
exit(1)
if not any([new_username, new_traffic_limit, new_expiration_days, renew_password, renew_creation_date, blocked is not None]):
print('Error: at least one option is required')
exit(1)
if new_traffic_limit is not None and new_traffic_limit <= 0:
print('Error: traffic limit must be greater than 0')
exit(1)
if new_expiration_days is not None and new_expiration_days <= 0:
print('Error: expiration days must be greater than 0')
exit(1)
# Handle renewing password and creation date
if renew_password:
try:
password = generate_password()
except subprocess.CalledProcessError as e:
print(f'Error: failed to generate password\n{e}')
exit(1)
else:
password = ""
if renew_creation_date:
creation_date = datetime.now().strftime('%Y-%m-%d')
else:
creation_date = ""
# Prepare arguments for the command
command_args = [
'bash',
Command.EDIT_USER.value,
username,
new_username or '',
str(new_traffic_limit) if new_traffic_limit is not None else '',
str(new_expiration_days) if new_expiration_days is not None else '',
password,
creation_date,
str(blocked).lower() if blocked is not None else 'false'
]
run_cmd(command_args)
@ cli.command('remove-user')
@ click.option('--username', '-u', required=True, help='Username for the user to remove', type=str)
def remove_user(username: str):
run_cmd(['bash', Command.REMOVE_USER.value, username])
@ cli.command('show-user-uri')
@ click.option('--username', '-u', required=True, help='Username for the user to show the URI', type=str)
def show_user_uri(username: str):
run_cmd(['bash', Command.SHOW_USER_URI.value, username])
@ cli.command('traffic-status')
def traffic_status():
traffic.traffic_status()
@ cli.command('list-users')
def list_users():
run_cmd(['bash', Command.LIST_USERS.value])
# endregion
# region advanced menu
@ cli.command('install-tcp-brutal')
def install_tcp_brutal():
run_cmd(['bash', Command.INSTALL_TCP_BRUTAL.value])
@ cli.command('install-warp')
def install_warp():
run_cmd(['bash', Command.INSTALL_WARP.value])
@ cli.command('uninstall-warp')
def uninstall_warp():
run_cmd(['bash', Command.UNINSTALL_WARP.value])
@ cli.command('configure-warp')
@ click.option('--all', '-a', is_flag=True, help='Use WARP for all connections')
@ 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)')
def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool):
run_cmd(['bash', Command.CONFIGURE_WARP.value, str(all).lower(), str(popular_sites).lower(), str(domestic_sites).lower(), str(block_adult_sites).lower()])
# endregion
if __name__ == '__main__':
cli()

View File

@ -0,0 +1,62 @@
#!/bin/bash
# Source the path.sh script to load the necessary variables
source /etc/hysteria/core/scripts/path.sh
source /etc/hysteria/core/scripts/utils.sh
define_colors
# Function to add a new user to the configuration
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
if [ -z "$password" ]; then
password=$(pwgen -s 32 1)
fi
if [ -z "$creation_date" ]; then
creation_date=$(date +%Y-%m-%d)
else
# Validate the date format (YYYY-MM-DD)
if ! [[ "$creation_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Invalid date format. Expected YYYY-MM-DD."
exit 1
fi
# Check if the date is valid
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
# Validate the username
if ! [[ "$username" =~ ^[a-z0-9]+$ ]]; then
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
exit 1
fi
# Convert GB to bytes (1 GB = 1073741824 bytes)
traffic=$(echo "$traffic_gb * 1073741824" | bc)
if [ ! -f "$USERS_FILE" ]; then
echo "{}" > "$USERS_FILE"
fi
jq --arg username "$username" --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"
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
echo -e "${green}User $username added successfully.${NC}"
}
# Call the function with the provided arguments
add_user "$1" "$2" "$3" "$4" "$5"

View File

@ -0,0 +1,27 @@
#!/bin/bash
# Source the path.sh script to load the CONFIG_FILE variable
source /etc/hysteria/core/scripts/path.sh
# Function to update port number in configuration
update_port() {
local port=$1
# Validate the port number
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
# Check if the config file exists and update the port number
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,177 @@
#!/bin/bash
source /etc/hysteria/core/scripts/utils.sh
source /etc/hysteria/core/scripts/path.sh
# Function to validate all user input fields
validate_inputs() {
local new_username=$1
local new_password=$2
local new_traffic_limit=$3
local new_expiration_days=$4
local new_creation_date=$5
local new_blocked=$6
# Validate username
if [ -n "$new_username" ]; then
if ! [[ "$new_username" =~ ^[a-z0-9]+$ ]]; then
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
exit 1
fi
fi
# Validate traffic limit
if [[ ! "$new_traffic_limit" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
if ! [[ "$new_traffic_limit" =~ ^[0-9]+$ ]]; then
echo -e "${red}Error:${NC} Traffic limit must be a valid integer."
exit 1
fi
fi
# Validate expiration days
if [ -n "$new_expiration_days" ]; then
if ! [[ "$new_expiration_days" =~ ^[0-9]+$ ]]; then
echo -e "${red}Error:${NC} Expiration days must be a valid integer."
exit 1
fi
fi
# Validate date format
if [ -n "$new_creation_date" ]; then
if ! [[ "$new_creation_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Invalid date format. Expected YYYY-MM-DD."
exit 1
elif ! date -d "$new_creation_date" >/dev/null 2>&1; then
echo "Invalid date. Please provide a valid date in YYYY-MM-DD format."
exit 1
fi
fi
# Validate blocked status
if [ -n "$new_blocked" ]; then
if [ "$new_blocked" != "true" ] && [ "$new_blocked" != "false" ]; then
echo -e "${red}Error:${NC} Blocked status must be 'true' or 'false'."
exit 1
fi
fi
}
# Function to get user info
get_user_info() {
local username=$1
python3 $CLI_PATH get-user -u "$username"
}
# Function to update user info in JSON
update_user_info() {
local old_username=$1
local new_username=$2
local new_password=$3
local new_max_download_bytes=$4
local new_expiration_days=$5
local new_account_creation_date=$6
local new_blocked=$7
if [ ! -f "$USERS_FILE" ]; then
echo "Error: File '$USERS_FILE' not found."
return 1
fi
echo "checking if user exists"
# Check if the old username exists
user_exists=$(jq -e --arg username "$old_username" '.[$username]' "$USERS_FILE")
if [ $? -ne 0 ]; then
echo "Error: User '$old_username' not found."
return 1
fi
echo "user exists done"
echo "change key"
# If new_username is provided and different from old_username, rename the key
if [ -n "$new_username" ] && [ "$old_username" != "$new_username" ]; then
jq --arg old_username "$old_username" \
--arg new_username "$new_username" \
'if .[$new_username] then error("User already exists with new username") else . end |
.[$new_username] = .[$old_username] | del(.[$old_username])' \
"$USERS_FILE" > tmp.$$.json && mv tmp.$$.json "$USERS_FILE"
if [ $? -ne 0 ]; then
echo "Error: Failed to rename user '$old_username' to '$new_username'."
return 1
fi
fi
echo "change key done"
echo "update user fields"
# print all new values
echo "Old username" "$old_username"
echo "New username: $new_username"
echo "New password: $new_password"
echo "New traffic limit: $new_max_download_bytes"
echo "New expiration days: $new_expiration_days"
echo "New creation date: $new_account_creation_date"
echo "New blocked status: $new_blocked"
jq --arg username "$new_username" \
--arg password "$new_password" \
--argjson max_download_bytes "$new_max_download_bytes" \
--argjson expiration_days "$new_expiration_days" \
--arg account_creation_date "$new_account_creation_date" \
--argjson blocked "$new_blocked" \
'.[$username] |= (.password = $password | .max_download_bytes = $max_download_bytes | .expiration_days = $expiration_days | .account_creation_date = $account_creation_date | .blocked = $blocked)' \
"$USERS_FILE" > tmp.$$.json && mv tmp.$$.json "$USERS_FILE"
echo "update user fields done"
if [ $? -ne 0 ]; then
echo "Error: Failed to update user '$old_username'."
return 1
fi
echo "User '$old_username' updated successfully."
}
# Main function to edit user
edit_user() {
local username=$1
local new_username=$2
local new_traffic_limit=$3
local new_expiration_days=$4
local new_password=$5
local new_creation_date=$6
local new_blocked=$7
# Get user info
user_info=$(get_user_info "$username")
if [ $? -ne 0 ] || [ -z "$user_info" ]; then
echo -e "${red}Error:${NC} User '$username' not found."
exit 1
fi
# Extract user info
local password=$(echo "$user_info" | jq -r '.password')
local traffic_limit=$(echo "$user_info" | jq -r '.max_download_bytes')
local expiration_days=$(echo "$user_info" | jq -r '.expiration_days')
local creation_date=$(echo "$user_info" | jq -r '.account_creation_date')
local blocked=$(echo "$user_info" | jq -r '.blocked')
# Validate all inputs
validate_inputs "$new_username" "$new_password" "$new_traffic_limit" "$new_expiration_days" "$new_creation_date" "$new_blocked"
# Set new values with validation
new_username=${new_username:-$username}
new_password=${new_password:-$password}
new_traffic_limit=${new_traffic_limit:-$traffic_limit}
new_traffic_limit=$(echo "$new_traffic_limit * 1073741824" | bc)
new_expiration_days=${new_expiration_days:-$expiration_days}
new_creation_date=${new_creation_date:-$creation_date}
new_blocked=${new_blocked:-$blocked}
# Update user info in JSON file
update_user_info "$username" "$new_username" "$new_password" "$new_traffic_limit" "$new_expiration_days" "$new_creation_date" "$new_blocked"
}
# Run the script
edit_user "$1" "$2" "$3" "$4" "$5" "$6" "$7"

View File

@ -0,0 +1,32 @@
#!/bin/bash
source /etc/hysteria/core/scripts/path.sh
# Check if a username is provided
if [ -z "$1" ]; then
echo "Usage: $0 <username>"
exit 1
fi
USERNAME=$1
# Check if users.json file exists
if [ ! -f "$USERS_FILE" ]; then
echo "users.json file not found!"
exit 1
fi
# Extract user info using jq
USER_INFO=$(jq -r --arg username "$USERNAME" '.[$username] // empty' $USERS_FILE)
# Check if user info is found
if [ -z "$USER_INFO" ]; then
echo "User '$USERNAME' not found."
exit 1
fi
# Print user info
echo "$USER_INFO" | jq .
exit 0

View File

@ -0,0 +1,140 @@
#!/bin/bash
# Source the path.sh script to load the necessary variables
source /etc/hysteria/core/scripts/path.sh
source /etc/hysteria/core/scripts/utils.sh
define_colors
install_hysteria() {
local port=$1
# Step 1: Install Hysteria2
echo "Installing Hysteria2..."
bash <(curl -fsSL https://get.hy2.sh/) >/dev/null 2>&1
# Step 2: Create hysteria directory and navigate into it
mkdir -p /etc/hysteria && cd /etc/hysteria/
# Step 3: Generate CA key and certificate and download geo data
echo "Generating CA key and certificate..."
openssl ecparam -genkey -name prime256v1 -out ca.key >/dev/null 2>&1
openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/CN=bts.com" >/dev/null 2>&1
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/geoip.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat >/dev/null 2>&1
# Step 4: Extract the SHA-256 fingerprint
fingerprint=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s/://g')
# Step 5: Generate the 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
# Execute the Python script and capture the output
sha256=$(python3 generate.py)
# Step 7: Ask for the port number and validate input
if [[ $port =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )); then
# Check if the port is in use
if ss -tuln | grep -q ":$port\b"; then
echo -e "${red}Port $port is already in use. Please choose another port.${NC}"
exit 1
fi
else
echo "Invalid port number. Please enter a number between 1 and 65535."
exit 1
fi
# Step 8: Generate required passwords and UUID
echo "Generating passwords and UUID..."
obfspassword=$(pwgen -s 32 1)
UUID=$(uuidgen)
# Step 9: Adjust file permissions for Hysteria service
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
# Create hysteria user without login permissions
if ! id -u hysteria &> /dev/null; then
useradd -r -s /usr/sbin/nologin hysteria
fi
# Get the default network interface
networkdef=$(ip route | grep "^default" | awk '{print $5}')
# Step 10: Customize the config.json file
echo "Customizing config.json..."
jq --arg port "$port" \
--arg sha256 "$sha256" \
--arg obfspassword "$obfspassword" \
--arg UUID "$UUID" \
--arg networkdef "$networkdef" \
'.listen = ":\($port)" |
.tls.cert = "/etc/hysteria/ca.crt" |
.tls.key = "/etc/hysteria/ca.key" |
.tls.pinSHA256 = $sha256 |
.obfs.salamander.password = $obfspassword |
.trafficStats.secret = $UUID |
.outbounds[0].direct.bindDevice = $networkdef' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
# Step 11: Modify the systemd service file to use config.json
echo "Updating hysteria-server.service to use config.json..."
sed -i 's|(config.yaml)||' /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
# Step 12: Start and enable the Hysteria service
echo "Starting and enabling Hysteria service..."
systemctl daemon-reload >/dev/null 2>&1
systemctl start hysteria-server.service >/dev/null 2>&1
systemctl enable hysteria-server.service >/dev/null 2>&1
systemctl restart hysteria-server.service >/dev/null 2>&1
# Step 13: Check if the hysteria-server.service is active
if systemctl is-active --quiet hysteria-server.service; then
echo "${cyan}Hysteria2${green} has been successfully installed."
else
echo "${red}Error:${NC} hysteria-server.service is not active."
exit 1
fi
# Step 15: Give right permissions to scripts
chmod +x /etc/hysteria/core/scripts/hysteria2/user.sh
chmod +x /etc/hysteria/core/scripts/hysteria2/kick.sh
# Add the scripts to the crontab
(crontab -l ; echo "*/1 * * * * python3 /etc/hysteria/core/cli.py traffic-status >/dev/null 2>&1") | crontab -
(crontab -l ; echo "*/1 * * * * /etc/hysteria/core/scripts/hysteria2/kick.sh >/dev/null 2>&1") | crontab -
}
if systemctl is-active --quiet hysteria-server.service; then
echo -e "${red}Error:${NC} Hysteria2 is already installed and running."
echo
echo "If you need to update the core, please use the 'Update Core' option."
else
echo "Installing and configuring Hysteria2..."
install_hysteria "$1"
echo -e "\n"
if systemctl is-active --quiet hysteria-server.service; then
echo "Installation and configuration complete."
else
echo -e "${red}Error:${NC} Hysteria2 service is not active. Please check the logs for more details."
fi
fi

View File

@ -1,8 +1,6 @@
#!/bin/bash
USERS_FILE="/etc/hysteria/users/users.json"
TRAFFIC_FILE="/etc/hysteria/traffic_data.json"
CONFIG_FILE="/etc/hysteria/config.json"
source /etc/hysteria/core/scripts/path.sh
kick_user() {
local username=$1

View File

@ -0,0 +1,6 @@
#!/bin/bash
source /etc/hysteria/core/scripts/path.sh
cat "$USERS_FILE"

View File

@ -0,0 +1,37 @@
#!/bin/bash
# Source the path.sh script to load the necessary variables
source /etc/hysteria/core/scripts/path.sh
source /etc/hysteria/core/scripts/utils.sh
define_colors
# Function to remove a user from the configuration
remove_user() {
if [ $# -ne 1 ]; then
echo "Usage: $0 <username>"
exit 1
fi
username=$1
if [ -f "$USERS_FILE" ]; then
# Check if the username exists in the users.json file
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"
if [ -f "$TRAFFIC_FILE" ]; then
jq --arg username "$username" 'del(.[$username])' "$TRAFFIC_FILE" > "${TRAFFIC_FILE}.temp" && mv "${TRAFFIC_FILE}.temp" "$TRAFFIC_FILE"
fi
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
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
}
# Call the function with the provided username argument
remove_user "$1"

View File

@ -0,0 +1,4 @@
#!/bin/bash
python3 /etc/hysteria/core/cli.py traffic-status > /dev/null 2>&1
systemctl restart hysteria-server.service

View File

@ -0,0 +1,67 @@
#!/bin/bash
# Source the path.sh script to load the configuration variables
source /etc/hysteria/core/scripts/path.sh
# Function to show URI if Hysteria2 is installed and active
show_uri() {
if [ -f "$USERS_FILE" ]; then
if systemctl is-active --quiet hysteria-server.service; then
# Check if the username is provided as an argument
if [ -z "$1" ]; then
echo "Usage: $0 <username>"
exit 1
fi
username=$1
# Validate the username
if jq -e "has(\"$username\")" "$USERS_FILE" > /dev/null; then
# Get the selected user's details
authpassword=$(jq -r ".\"$username\".password" "$USERS_FILE")
port=$(jq -r '.listen' "$CONFIG_FILE" | cut -d':' -f2)
sha256=$(jq -r '.tls.pinSHA256' "$CONFIG_FILE")
obfspassword=$(jq -r '.obfs.salamander.password' "$CONFIG_FILE")
# Get IP addresses
IP=$(curl -s -4 ip.gs)
IP6=$(curl -s -6 ip.gs)
# Construct URI
URI="hy2://$username%3A$authpassword@$IP:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv4"
URI6="hy2://$username%3A$authpassword@[$IP6]:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv6"
# Generate QR codes
qr1=$(echo -n "$URI" | qrencode -t UTF8 -s 3 -m 2)
qr2=$(echo -n "$URI6" | qrencode -t UTF8 -s 3 -m 2)
# Display QR codes and URIs
cols=$(tput cols)
echo -e "\nIPv4:\n"
echo "$qr1" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo -e "\nIPv6:\n"
echo "$qr2" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo
echo "IPv4: $URI"
echo
echo "IPv6: $URI6"
echo
else
echo "Invalid username. Please try again."
fi
else
echo -e "\033[0;31mError:\033[0m Hysteria2 is not active."
fi
else
echo -e "\033[0;31mError:\033[0m Config file $USERS_FILE not found."
fi
}
# Call the function with the provided username argument
show_uri "$1"

View File

@ -0,0 +1,28 @@
source /etc/hysteria/core/scripts/path.sh
echo "Uninstalling Hysteria2..."
#sleep 1
echo "Running uninstallation script..."
bash <(curl -fsSL https://get.hy2.sh/) --remove >/dev/null 2>&1
#sleep 1
echo "Removing WARP"
python3 $CLI_PATH uninstall-warp
echo "Removing Hysteria folder..."
rm -rf /etc/hysteria >/dev/null 2>&1
#sleep 1
echo "Deleting hysteria user..."
userdel -r hysteria >/dev/null 2>&1
#sleep 1
echo "Removing systemd service files..."
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server.service >/dev/null 2>&1
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server@*.service >/dev/null 2>&1
#sleep 1
echo "Reloading systemd daemon..."
systemctl daemon-reload >/dev/null 2>&1
#sleep 1
echo "Removing cron jobs..."
(crontab -l | grep -v "python3 /etc/hysteria/core/cli.py traffic-status" | crontab -) >/dev/null 2>&1
(crontab -l | grep -v "/etc/hysteria/core/scripts/hysteria2/kick.sh" | crontab -) >/dev/null 2>&1
#sleep 1
echo "Hysteria2 uninstalled!"
echo ""

View File

@ -0,0 +1,47 @@
#!/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

@ -4,26 +4,30 @@ ADDR="$1"
AUTH="$2"
TX="$3"
USERS_FILE="/etc/hysteria/users/users.json"
TRAFFIC_FILE="/etc/hysteria/traffic_data.json"
CONFIG_FILE="/etc/hysteria/config.json"
# Source the path.sh script to load variables
source /etc/hysteria/core/scripts/path.sh
# Extract username and password from AUTH
IFS=':' read -r USERNAME PASSWORD <<< "$AUTH"
# Retrieve stored user data
STORED_PASSWORD=$(jq -r --arg user "$USERNAME" '.[$user].password' "$USERS_FILE")
MAX_DOWNLOAD_BYTES=$(jq -r --arg user "$USERNAME" '.[$user].max_download_bytes' "$USERS_FILE")
EXPIRATION_DAYS=$(jq -r --arg user "$USERNAME" '.[$user].expiration_days' "$USERS_FILE")
ACCOUNT_CREATION_DATE=$(jq -r --arg user "$USERNAME" '.[$user].account_creation_date' "$USERS_FILE")
BLOCKED=$(jq -r --arg user "$USERNAME" '.[$user].blocked' "$USERS_FILE")
# Check if the user is blocked
if [ "$BLOCKED" == "true" ]; then
exit 1
fi
# Check if the provided password matches the stored password
if [ "$STORED_PASSWORD" != "$PASSWORD" ]; then
exit 1
fi
# Check if the user's account has expired
CURRENT_DATE=$(date +%s)
EXPIRATION_DATE=$(date -d "$ACCOUNT_CREATION_DATE + $EXPIRATION_DAYS days" +%s)
@ -32,6 +36,7 @@ if [ "$CURRENT_DATE" -ge "$EXPIRATION_DATE" ]; then
exit 1
fi
# Check if the user's download limit has been exceeded
CURRENT_DOWNLOAD_BYTES=$(jq -r --arg user "$USERNAME" '.[$user].download_bytes' "$TRAFFIC_FILE")
if [ "$CURRENT_DOWNLOAD_BYTES" -ge "$MAX_DOWNLOAD_BYTES" ]; then
@ -43,5 +48,6 @@ if [ "$CURRENT_DOWNLOAD_BYTES" -ge "$MAX_DOWNLOAD_BYTES" ]; then
exit 1
fi
# If all checks pass, print the username and exit successfully
echo "$USERNAME"
exit 0
exit 0

4
core/scripts/path.sh Normal file
View File

@ -0,0 +1,4 @@
CLI_PATH="/etc/hysteria/core/cli.py"
USERS_FILE="/etc/hysteria/users.json"
TRAFFIC_FILE="/etc/hysteria/traffic_data.json"
CONFIG_FILE="/etc/hysteria/config.json"

View File

@ -0,0 +1,6 @@
#!/bin/bash
echo "Installing TCP Brutal..."
bash <(curl -fsSL https://tcp.hy2.sh/)
sleep 3
clear
echo "TCP Brutal installation complete."

22
core/scripts/utils.sh Normal file
View File

@ -0,0 +1,22 @@
# Function to define colors
define_colors() {
green='\033[0;32m'
cyan='\033[0;36m'
red='\033[0;31m'
yellow='\033[0;33m'
LPurple='\033[1;35m'
NC='\033[0m' # No Color
}
# Function to get system information
get_system_info() {
OS=$(lsb_release -d | awk -F'\t' '{print $2}')
ARCH=$(uname -m)
# Fetching detailed IP information in JSON format
IP_API_DATA=$(curl -s https://ipapi.co/json/ -4)
ISP=$(echo "$IP_API_DATA" | jq -r '.org')
IP=$(echo "$IP_API_DATA" | jq -r '.ip')
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}')
RAM=$(free -m | awk 'NR==2{printf "%.2f%%", $3*100/$2 }')
}

View File

@ -0,0 +1,105 @@
#!/bin/bash
# Source the path.sh script to load the CONFIG_FILE and CLI_PATH variables
source /etc/hysteria/core/scripts/path.sh
check_warp_service() {
if ! systemctl is-active --quiet wg-quick@wgcf.service; then
echo "WARP is not active. Please install WARP before configuring."
exit 1
fi
}
check_config_file() {
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Config file $CONFIG_FILE not found."
exit 1
fi
}
get_status() {
warp_all_status=$(jq -r 'if .acl.inline | index("warps(all)") then "WARP active" else "Direct" end' "$CONFIG_FILE")
google_openai_status=$(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")
iran_status=$(jq -r 'if (.acl.inline | index("warps(geosite:ir)")) and (.acl.inline | index("warps(geoip:ir)")) then "Use WARP" else "Reject" end' "$CONFIG_FILE")
adult_content_status=$(jq -r 'if .acl.inline | index("reject(geosite:category-porn)") then "Blocked" else "Not blocked" end' "$CONFIG_FILE")
}
display_menu() {
echo "===== Configuration Menu ====="
echo "1. Use WARP for all traffic ($warp_all_status)"
echo "2. Use WARP for Google, OpenAI, etc. ($google_openai_status)"
echo "3. Use WARP for geosite:ir and geoip:ir ($iran_status)"
echo "4. Block adult content ($adult_content_status)"
echo "5. Back to Advance Menu"
echo "==================================="
}
update_config() {
local jq_command=$1
jq "$jq_command" "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
}
configure_warp_all() {
if [ "$warp_all_status" == "WARP active" ]; then
update_config 'del(.acl.inline[] | select(. == "warps(all)"))'
echo "Traffic configuration changed to Direct."
else
update_config '.acl.inline += ["warps(all)"]'
echo "Traffic configuration changed to WARP."
fi
}
configure_google_openai() {
if [ "$google_openai_status" == "WARP active" ]; then
update_config '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)"))'
echo "WARP configuration for Google, OpenAI, etc. removed."
else
update_config '.acl.inline += ["warps(geoip:google)", "warps(geosite:google)", "warps(geosite:netflix)", "warps(geosite:spotify)", "warps(geosite:openai)", "warps(geoip:openai)"]'
echo "WARP configured for Google, OpenAI, etc."
fi
}
configure_iran() {
if [ "$iran_status" == "Use WARP" ]; then
update_config '(.acl.inline[] | select(. == "warps(geosite:ir)")) = "reject(geosite:ir)" | (.acl.inline[] | select(. == "warps(geoip:ir)")) = "reject(geoip:ir)"'
echo "Configuration changed to Reject for geosite:ir and geoip:ir."
else
update_config '(.acl.inline[] | select(. == "reject(geosite:ir)")) = "warps(geosite:ir)" | (.acl.inline[] | select(. == "reject(geoip:ir)")) = "warps(geoip:ir)"'
echo "Configuration changed to Use WARP for geosite:ir and geoip:ir."
fi
}
configure_adult_content() {
if [ "$adult_content_status" == "Blocked" ]; then
update_config 'del(.acl.inline[] | select(. == "reject(geosite:category-porn)"))'
update_config '.resolver.tls.addr = "1.1.1.1:853"'
echo "Adult content blocking removed and resolver updated."
else
update_config '.acl.inline += ["reject(geosite:category-porn)"]'
update_config '.resolver.tls.addr = "1.1.1.3:853"'
echo "Adult content blocked and resolver updated."
fi
}
handle_choice() {
case $choice in
1) configure_warp_all ;;
2) configure_google_openai ;;
3) configure_iran ;;
4) configure_adult_content ;;
5) exit 0 ;;
*) echo "Invalid option. Please try again." ;;
esac
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
}
main() {
check_warp_service
check_config_file
get_status
display_menu
read -p "Enter your choice: " choice
handle_choice
}
main

View File

@ -0,0 +1,23 @@
#!/bin/bash
# Source the path.sh script to load the CONFIG_FILE variable
source /etc/hysteria/core/scripts/path.sh
# Check if wg-quick@wgcf.service is active
if systemctl is-active --quiet wg-quick@wgcf.service; then
echo "WARP is already active. Skipping installation and configuration update."
else
echo "Installing WARP..."
bash <(curl -fsSL git.io/warp.sh) wgx
# Check if the config file exists
if [ -f "$CONFIG_FILE" ]; then
# Add the outbound configuration to the config.json file
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"
# Restart the hysteria-server service
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
echo "WARP installed and outbound added to config.json."
else
echo "Error: Config file $CONFIG_FILE not found."
fi
fi

View File

@ -0,0 +1,38 @@
#!/bin/bash
# Source the path.sh script to load the CONFIG_FILE and CLI_PATH variables
source /etc/hysteria/core/scripts/path.sh
# Check if WARP is active
if systemctl is-active --quiet wg-quick@wgcf.service; then
echo "Uninstalling WARP..."
bash <(curl -fsSL git.io/warp.sh) dwg
# Check if the config file exists
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"
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

@ -3,15 +3,21 @@ import subprocess
import json
import os
# Define static variables for paths and URLs
CONFIG_FILE = '/etc/hysteria/config.json'
TRAFFIC_FILE = '/etc/hysteria/traffic_data.json'
TRAFFIC_API_URL = 'http://127.0.0.1:25413/traffic?clear=1'
ONLINE_API_URL = 'http://127.0.0.1:25413/online'
def traffic_status():
green = '\033[0;32m'
cyan = '\033[0;36m'
NC = '\033[0m'
try:
secret = subprocess.check_output(['jq', '-r', '.trafficStats.secret', '/etc/hysteria/config.json']).decode().strip()
secret = subprocess.check_output(['jq', '-r', '.trafficStats.secret', CONFIG_FILE]).decode().strip()
except subprocess.CalledProcessError as e:
print(f"Error: Failed to read secret from config.json. Details: {e}")
print(f"Error: Failed to read secret from {CONFIG_FILE}. Details: {e}")
return
if not secret:
@ -19,7 +25,7 @@ def traffic_status():
return
try:
response = subprocess.check_output(['curl', '-s', '-H', f'Authorization: {secret}', 'http://127.0.0.1:25413/traffic?clear=1']).decode().strip()
response = subprocess.check_output(['curl', '-s', '-H', f'Authorization: {secret}', TRAFFIC_API_URL]).decode().strip()
except subprocess.CalledProcessError as e:
print(f"Error: Failed to fetch traffic data. Details: {e}")
return
@ -29,7 +35,7 @@ def traffic_status():
return
try:
online_response = subprocess.check_output(['curl', '-s', '-H', f'Authorization: {secret}', 'http://127.0.0.1:25413/online']).decode().strip()
online_response = subprocess.check_output(['curl', '-s', '-H', f'Authorization: {secret}', ONLINE_API_URL]).decode().strip()
except subprocess.CalledProcessError as e:
print(f"Error: Failed to fetch online status data. Details: {e}")
return
@ -55,9 +61,9 @@ def traffic_status():
}
existing_data = {}
if os.path.exists('/etc/hysteria/traffic_data.json'):
if os.path.exists(TRAFFIC_FILE):
try:
with open('/etc/hysteria/traffic_data.json', 'r') as json_file:
with open(TRAFFIC_FILE, 'r') as json_file:
existing_data = json.load(json_file)
except json.JSONDecodeError:
print("Error: Failed to parse existing traffic data JSON file.")
@ -71,7 +77,7 @@ def traffic_status():
else:
existing_data[user] = data
with open('/etc/hysteria/traffic_data.json', 'w') as json_file:
with open(TRAFFIC_FILE, 'w') as json_file:
json.dump(existing_data, json_file, indent=4)
display_traffic_data(existing_data, green, cyan, NC)
@ -108,5 +114,3 @@ def format_bytes(bytes):
return f"{bytes / 1073741824:.2f}GB"
else:
return f"{bytes / 1099511627776:.2f}TB"
traffic_status()

10
core/validator.py Normal file
View File

@ -0,0 +1,10 @@
import os
import click
def validate_port(ctx,param,value:int) -> int:
if value < 1 or value > 65535:
raise click.BadParameter('Port must be between 1 and 65535')
# check if port is in use
if os.system(f'lsof -i:{value}') == 0:
raise click.BadParameter(f'Port {value} is in use')
return value

View File

@ -1,126 +1,28 @@
#!/bin/bash
define_colors() {
green='\033[0;32m'
cyan='\033[0;36m'
red='\033[0;31m'
yellow='\033[0;33m'
LPurple='\033[1;35m'
NC='\033[0m'
}
# Step 1: Install Hysteria2
echo "Installing Hysteria2..."
bash <(curl -fsSL https://get.hy2.sh/) >/dev/null 2>&1
# Step 2: Create hysteria directory and navigate into it
mkdir -p /etc/hysteria && cd /etc/hysteria/
# Step 3: Generate CA key and certificate and download geo data
echo "Generating CA key and certificate..."
openssl ecparam -genkey -name prime256v1 -out ca.key >/dev/null 2>&1
openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/CN=bts.com" >/dev/null 2>&1
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/geoip.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat >/dev/null 2>&1
# Step 4: Extract the SHA-256 fingerprint
fingerprint=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s/://g')
# Step 5: Generate the base64 encoded SHA-256 fingerprint
echo "Generating base64 encoded SHA-256 fingerprint..."
echo "import re, base64, binascii
hex_string = \"$fingerprint\"
binary_data = binascii.unhexlify(hex_string)
base64_encoded = base64.b64encode(binary_data).decode('utf-8')
print(\"sha256/\" + base64_encoded)" > generate.py
sha256=$(python3 generate.py)
# Step 6: Download the config.json file
echo "Downloading config.json..."
wget https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/config.json -O /etc/hysteria/config.json >/dev/null 2>&1
echo
# Step 7: Ask for the port number and validate input
while true; do
read -p "Enter the port number you want to use (1-65535): " port
if [[ $port =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )); then
# Check if the port is in use
if ss -tuln | grep -q ":$port\b"; then
clear
echo -e "\e[91mPort $port is already in use. Please choose another port.\e[0m"
echo
else
break
fi
else
echo "Invalid port number. Please enter a number between 1 and 65535."
fi
done
# Step 8: Generate required passwords and UUID
echo "Generating passwords and UUID..."
obfspassword=$(pwgen -s 32 1)
UUID=$(uuidgen)
# Step 9: Adjust file permissions for Hysteria service
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
# Create hysteria user without login permissions
if ! id -u hysteria &> /dev/null; then
useradd -r -s /usr/sbin/nologin hysteria
# Ensure necessary packages are installed
# Check if the script is being run by the root user
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root."
exit 1
fi
# Get the default network interface
networkdef=$(ip route | grep "^default" | awk '{print $5}')
# Step 10: Customize the config.json file
echo "Customizing config.json..."
jq --arg port "$port" \
--arg sha256 "$sha256" \
--arg obfspassword "$obfspassword" \
--arg UUID "$UUID" \
--arg networkdef "$networkdef" \
'.listen = ":\($port)" |
.tls.cert = "/etc/hysteria/ca.crt" |
.tls.key = "/etc/hysteria/ca.key" |
.tls.pinSHA256 = $sha256 |
.obfs.salamander.password = $obfspassword |
.trafficStats.secret = $UUID |
.outbounds[0].direct.bindDevice = $networkdef' /etc/hysteria/config.json > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json /etc/hysteria/config.json
# Step 11: Modify the systemd service file to use config.json
echo "Updating hysteria-server.service to use config.json..."
sed -i 's|(config.yaml)||' /etc/systemd/system/hysteria-server.service
sed -i 's|/etc/hysteria/config.yaml|/etc/hysteria/config.json|' /etc/systemd/system/hysteria-server.service
rm /etc/hysteria/config.yaml
sleep 1
# Step 12: Start and enable the Hysteria service
echo "Starting and enabling Hysteria service..."
systemctl daemon-reload >/dev/null 2>&1
systemctl start hysteria-server.service >/dev/null 2>&1
systemctl enable hysteria-server.service >/dev/null 2>&1
systemctl restart hysteria-server.service >/dev/null 2>&1
# Step 13: Check if the hysteria-server.service is active
if systemctl is-active --quiet hysteria-server.service; then
echo "${cyan}Hysteria2${green} has been successfully install."
else
echo "${red}Error:${NC} hysteria-server.service is not active."
clear
if ! command -v jq &> /dev/null || ! command -v git &> /dev/null || ! command -v qrencode &> /dev/null || ! command -v curl &> /dev/null; then
echo "${yellow}Necessary packages are not installed. Please wait while they are being installed..."
sleep 3
echo
apt update && apt upgrade -y && apt install jq qrencode curl pwgen uuid-runtime python3 python3-pip git -y
fi
# Step 15: wget Traffic/user/kick script
wget https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/traffic.py -O /etc/hysteria/traffic.py >/dev/null 2>&1
mkdir -p /etc/hysteria/users
wget https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/user.sh -O /etc/hysteria/users/user.sh >/dev/null 2>&1
wget https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/kick.sh -O /etc/hysteria/users/kick.sh >/dev/null 2>&1
wget https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/modify.py -O /etc/hysteria/users/modify.py >/dev/null 2>&1
chmod +x /etc/hysteria/users/user.sh
chmod +x /etc/hysteria/users/kick.sh
# Add the commands to the crontab
(crontab -l ; echo "*/1 * * * * python3 /etc/hysteria/traffic.py >/dev/null 2>&1") | crontab -
(crontab -l ; echo "*/1 * * * * /etc/hysteria/users/kick.sh >/dev/null 2>&1") | crontab -
# TODO: change the url later
git clone https://github.com/Iam54r1n4/Hysteria2 /etc/hysteria
# Add alias 'hys2' for Hysteria2
if ! grep -q "alias hys2='/etc/hysteria/menu.sh'" ~/.bashrc; then
echo "alias hys2='/etc/hysteria/menu.sh'" >> ~/.bashrc
source ~/.bashrc
fi
cd /etc/hysteria
chmod +x menu.sh
./menu.sh

698
menu.sh
View File

@ -1,109 +1,10 @@
#!/bin/bash
# Function to define colors
define_colors() {
green='\033[0;32m'
cyan='\033[0;36m'
red='\033[0;31m'
yellow='\033[0;33m'
LPurple='\033[1;35m'
NC='\033[0m' # No Color
}
source /etc/hysteria/core/scripts/utils.sh
source /etc/hysteria/core/scripts/path.sh
# Ensure necessary packages are installed
clear
if ! command -v jq &> /dev/null || ! command -v qrencode &> /dev/null || ! command -v curl &> /dev/null; then
echo "${yellow}Necessary packages are not installed. Please wait while they are being installed..."
sleep 3
echo
apt update && apt upgrade -y && apt install jq qrencode curl pwgen uuid-runtime python3 python3-pip -y
fi
# Add alias 'hys2' for Hysteria2
if ! grep -q "alias hys2='bash <(curl https://raw.githubusercontent.com/H-Return/Hysteria2/main/menu.sh)'" ~/.bashrc; then
echo "alias hys2='bash <(curl https://raw.githubusercontent.com/H-Return/Hysteria2/main/menu.sh)'" >> ~/.bashrc
source ~/.bashrc
fi
# Function to get system information
get_system_info() {
OS=$(lsb_release -d | awk -F'\t' '{print $2}')
ARCH=$(uname -m)
# Fetching detailed IP information in JSON format
IP_API_DATA=$(curl -s https://ipapi.co/json/ -4)
ISP=$(echo "$IP_API_DATA" | jq -r '.org')
IP=$(echo "$IP_API_DATA" | jq -r '.ip')
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}')
RAM=$(free -m | awk 'NR==2{printf "%.2f%%", $3*100/$2 }')
}
# Function to install and configure Hysteria2
install_and_configure() {
if systemctl is-active --quiet hysteria-server.service; then
echo -e "${red}Error:${NC} Hysteria2 is already installed and running."
echo
echo "If you need to update the core, please use the 'Update Core' option."
else
echo "Installing and configuring Hysteria2..."
bash <(curl -s https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/install.sh)
echo -e "\n"
if systemctl is-active --quiet hysteria-server.service; then
echo "Installation and configuration complete."
else
echo -e "${red}Error:${NC} Hysteria2 service is not active. Please check the logs for more details."
fi
fi
}
# Function to update Hysteria2
update_core() {
echo "Starting the update process for Hysteria2..."
echo "Backing up the current configuration..."
cp /etc/hysteria/config.json /etc/hysteria/config_backup.json
if [ $? -ne 0 ]; then
echo "${red}Error:${NC} Failed to back up configuration. Aborting update."
return 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 "${red}Error:${NC} Failed to download or install the latest version. Restoring backup configuration."
mv /etc/hysteria/config_backup.json /etc/hysteria/config.json
restart_hysteria_service >/dev/null 2>&1
return 1
fi
echo "Restoring configuration from backup..."
mv /etc/hysteria/config_backup.json /etc/hysteria/config.json
if [ $? -ne 0 ]; then
echo "${red}Error:${NC} Failed to restore configuration from backup."
return 1
fi
echo "Modifying systemd service to use config.json..."
sed -i 's|/etc/hysteria/config.yaml|/etc/hysteria/config.json|' /etc/systemd/system/hysteria-server.service
if [ $? -ne 0 ]; then
echo "${red}Error:${NC} Failed to modify systemd service."
return 1
fi
rm /etc/hysteria/config.yaml
systemctl daemon-reload >/dev/null 2>&1
restart_hysteria_service >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "${red}Error:${NC} Failed to restart Hysteria2 service."
return 1
fi
echo "Hysteria2 has been successfully updated."
echo ""
return 0
}
# Function to change port
change_port() {
# OPTION HANDLERS (ONLY NEEDED ONE)
hysteria2_install_handler() {
while true; do
read -p "Enter the new port number you want to use: " port
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
@ -112,385 +13,200 @@ change_port() {
break
fi
done
if [ -f "/etc/hysteria/config.json" ]; then
jq --arg port "$port" '.listen = ":" + $port' /etc/hysteria/config.json > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json /etc/hysteria/config.json
restart_hysteria_service >/dev/null 2>&1
echo "Port changed successfully to $port."
else
echo "${red}Error:${NC} Config file /etc/hysteria/config.json not found."
fi
python3 $CLI_PATH install-hysteria2 --port "$port"
}
# Function to show URI if Hysteria2 is installed and active
show_uri() {
if [ -f "/etc/hysteria/users/users.json" ]; then
if systemctl is-active --quiet hysteria-server.service; then
# Get the list of configured usernames
usernames=$(jq -r 'keys_unsorted[]' /etc/hysteria/users/users.json)
# Prompt the user to select a username
PS3="Select a username: "
select username in $usernames; do
if [ -n "$username" ]; then
# Get the selected user's details
authpassword=$(jq -r ".\"$username\".password" /etc/hysteria/users/users.json)
port=$(jq -r '.listen' /etc/hysteria/config.json | cut -d':' -f2)
sha256=$(jq -r '.tls.pinSHA256' /etc/hysteria/config.json)
obfspassword=$(jq -r '.obfs.salamander.password' /etc/hysteria/config.json)
# Get IP addresses
IP=$(curl -s -4 ip.gs)
IP6=$(curl -s -6 ip.gs)
# Construct URI
URI="hy2://$username%3A$authpassword@$IP:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv4"
URI6="hy2://$username%3A$authpassword@[$IP6]:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv6"
# Generate QR codes
qr1=$(echo -n "$URI" | qrencode -t UTF8 -s 3 -m 2)
qr2=$(echo -n "$URI6" | qrencode -t UTF8 -s 3 -m 2)
# Display QR codes and URIs
cols=$(tput cols)
echo -e "\nIPv4:\n"
echo "$qr1" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo -e "\nIPv6:\n"
echo "$qr2" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo
echo "IPv4: $URI"
echo
echo "IPv6: $URI6"
echo
break
else
echo "Invalid selection. Please try again."
fi
done
else
echo -e "\033[0;31mError:\033[0m Hysteria2 is not active."
fi
else
echo -e "\033[0;31mError:\033[0m Config file /etc/hysteria/users/users.json not found."
fi
}
# Function to check traffic status for each user
traffic_status() {
if [ -f "/etc/hysteria/traffic.py" ]; then
python3 /etc/hysteria/traffic.py >/dev/null 2>&1
else
echo "Error: /etc/hysteria/traffic.py not found."
return 1
fi
if [ ! -f "/etc/hysteria/traffic_data.json" ]; then
echo "Error: /etc/hysteria/traffic_data.json not found."
return 1
fi
data=$(cat /etc/hysteria/traffic_data.json)
echo "Traffic Data:"
echo "---------------------------------------------------------------------------"
echo -e "User Upload (TX) Download (RX) Status"
echo "---------------------------------------------------------------------------"
echo "$data" | jq -r 'to_entries[] | [.key, .value.upload_bytes, .value.download_bytes, .value.status] | @tsv' | while IFS=$'\t' read -r user upload_bytes download_bytes status; do
if [ $(echo "$upload_bytes < 1073741824" | bc -l) -eq 1 ]; then
upload=$(echo "scale=2; $upload_bytes / 1024 / 1024" | bc)
upload_unit="MB"
else
upload=$(echo "scale=2; $upload_bytes / 1024 / 1024 / 1024" | bc)
upload_unit="GB"
fi
if [ $(echo "$download_bytes < 1073741824" | bc -l) -eq 1 ]; then
download=$(echo "scale=2; $download_bytes / 1024 / 1024" | bc)
download_unit="MB"
else
download=$(echo "scale=2; $download_bytes / 1024 / 1024 / 1024" | bc)
download_unit="GB"
fi
printf "${yellow}%-15s ${cyan}%-15s ${green}%-15s ${NC}%-10s\n" "$user" "$(printf "%.2f%s" "$upload" "$upload_unit")" "$(printf "%.2f%s" "$download" "$download_unit")" "$status"
echo "---------------------------------------------------------------------------"
done
}
# Function to restart Hysteria2 service
restart_hysteria_service() {
python3 /etc/hysteria/traffic.py >/dev/null 2>&1
systemctl restart hysteria-server.service
}
# Function to modify users
modify_users() {
modify_script="/etc/hysteria/users/modify.py"
github_raw_url="https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/modify.py"
[ -f "$modify_script" ] || wget "$github_raw_url" -O "$modify_script" >/dev/null 2>&1
python3 "$modify_script"
}
# Function to uninstall Hysteria2
uninstall_hysteria() {
echo "Uninstalling Hysteria2..."
sleep 1
echo "Running uninstallation script..."
bash <(curl -fsSL https://get.hy2.sh/) --remove >/dev/null 2>&1
sleep 1
echo "Removing Hysteria folder..."
rm -rf /etc/hysteria >/dev/null 2>&1
sleep 1
echo "Deleting hysteria user..."
userdel -r hysteria >/dev/null 2>&1
sleep 1
echo "Removing systemd service files..."
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server.service >/dev/null 2>&1
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server@*.service >/dev/null 2>&1
sleep 1
echo "Reloading systemd daemon..."
systemctl daemon-reload >/dev/null 2>&1
sleep 1
echo "Removing cron jobs..."
(crontab -l | grep -v "python3 /etc/hysteria/traffic.py" | crontab -) >/dev/null 2>&1
(crontab -l | grep -v "/etc/hysteria/users/kick.sh" | crontab -) >/dev/null 2>&1
sleep 1
echo "Hysteria2 uninstalled!"
echo ""
}
# Function to install TCP Brutal
install_tcp_brutal() {
echo "Installing TCP Brutal..."
bash <(curl -fsSL https://tcp.hy2.sh/)
sleep 3
clear
echo "TCP Brutal installation complete."
}
# Function to install WARP and update config.json
install_warp() {
# Check if wg-quick@wgcf.service is active
if systemctl is-active --quiet wg-quick@wgcf.service; then
echo "WARP is already active. Skipping installation and configuration update."
else
echo "Installing WARP..."
bash <(curl -fsSL git.io/warp.sh) wgx
# Check if the config file exists
if [ -f "/etc/hysteria/config.json" ]; then
# Add the outbound configuration to the config.json file
jq '.outbounds += [{"name": "warps", "type": "direct", "direct": {"mode": 4, "bindDevice": "wgcf"}}]' /etc/hysteria/config.json > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json /etc/hysteria/config.json
# Restart the hysteria-server service
restart_hysteria_service >/dev/null 2>&1
echo "WARP installed and outbound added to config.json."
else
echo "${red}Error:${NC} Config file /etc/hysteria/config.json not found."
fi
fi
}
# Function to uninstall WARP and update config.json
uninstall_warp() {
if systemctl is-active --quiet wg-quick@wgcf.service; then
echo "Uninstalling WARP..."
bash <(curl -fsSL git.io/warp.sh) dwg
if [ -f "/etc/hysteria/config.json" ]; 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"))))
' /etc/hysteria/config.json > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json /etc/hysteria/config.json
jq 'del(.outbounds[] | select(.name == "warps" and .type == "direct" and .direct.mode == 4 and .direct.bindDevice == "wgcf"))' /etc/hysteria/config.json > /etc/hysteria/config_temp.json && mv /etc/hysteria/config_temp.json /etc/hysteria/config.json
restart_hysteria_service >/dev/null 2>&1
echo "WARP uninstalled and configurations reset to default."
else
echo "${red}Error:${NC} Config file /etc/hysteria/config.json not found."
fi
else
echo "WARP is not active. Skipping uninstallation."
fi
}
# Function to configure WARP
configure_warp() {
# Check if wg-quick@wgcf.service is active
if ! systemctl is-active --quiet wg-quick@wgcf.service; then
echo "WARP is not active. Please install WARP before configuring."
return
fi
CONFIG_FILE="/etc/hysteria/config.json"
if [ -f "$CONFIG_FILE" ]; then
# Check the current status of WARP configurations
warp_all_status=$(jq -r 'if .acl.inline | index("warps(all)") then "WARP active" else "Direct" end' "$CONFIG_FILE")
google_openai_status=$(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")
iran_status=$(jq -r 'if (.acl.inline | index("warps(geosite:ir)")) and (.acl.inline | index("warps(geoip:ir)")) then "Use WARP" else "Reject" end' "$CONFIG_FILE")
adult_content_status=$(jq -r 'if .acl.inline | index("reject(geosite:category-porn)") then "Blocked" else "Not blocked" end' "$CONFIG_FILE")
echo "===== Configuration Menu ====="
echo "1. Use WARP for all traffic ($warp_all_status)"
echo "2. Use WARP for Google, OpenAI, etc. ($google_openai_status)"
echo "3. Use WARP for geosite:ir and geoip:ir ($iran_status)"
echo "4. Block adult content ($adult_content_status)"
echo "5. Back to Advance Menu"
echo "==================================="
read -p "Enter your choice: " choice
case $choice in
1)
if [ "$warp_all_status" == "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
;;
2)
if [ "$google_openai_status" == "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
;;
3)
if [ "$iran_status" == "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
;;
4)
if [ "$adult_content_status" == "Blocked" ]; then
jq 'del(.acl.inline[] | select(. == "reject(geosite:category-porn)"))' "$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:category-porn)"]' "$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
;;
5)
return
;;
*)
echo "Invalid option. Please try again."
;;
esac
restart_hysteria_service >/dev/null 2>&1
else
echo "${red}Error:${NC} Config file $CONFIG_FILE not found."
fi
}
# Function to add a new user to the configuration
add_user() {
hysteria2_add_user_handler() {
while true; do
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-z0-9]+$ ]]; then
break
else
echo -e "\033[0;31mError:\033[0m Username can only contain lowercase letters and numbers."
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
fi
done
read -p "Enter the traffic limit (in GB): " traffic_gb
# Convert GB to bytes (1 GB = 1073741824 bytes)
traffic=$((traffic_gb * 1073741824))
read -p "Enter the traffic limit (in GB): " traffic_limit_GB
read -p "Enter the expiration days: " expiration_days
password=$(pwgen -s 32 1)
creation_date=$(date +%Y-%m-%d)
if [ ! -f "/etc/hysteria/users/users.json" ]; then
echo "{}" > /etc/hysteria/users/users.json
fi
jq --arg username "$username" --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}' \
/etc/hysteria/users/users.json > /etc/hysteria/users/users_temp.json && mv /etc/hysteria/users/users_temp.json /etc/hysteria/users/users.json
restart_hysteria_service >/dev/null 2>&1
echo -e "\033[0;32mUser $username added successfully.\033[0m"
python3 $CLI_PATH add-user --username "$username" --traffic-limit "$traffic_limit_GB" --expiration-days "$expiration_days" --password "$password" --creation-date "$creation_date"
}
hysteria2_edit_user() {
# Function to prompt for user input with validation
prompt_for_input() {
local prompt_message="$1"
local validation_regex="$2"
local default_value="$3"
local input_variable_name="$4"
# Function to remove a user from the configuration
remove_user() {
if [ -f "/etc/hysteria/users/users.json" ]; then
# Extract current users from the users.json file
users=$(jq -r 'keys[]' /etc/hysteria/users/users.json)
if [ -z "$users" ]; then
echo "No users found."
return
fi
# Display current users with numbering
echo "Current users:"
echo "-----------------"
i=1
for user in $users; do
echo "$i. $user"
((i++))
while true; do
read -p "$prompt_message" input
if [[ -z "$input" ]]; then
input="$default_value"
fi
if [[ "$input" =~ $validation_regex ]]; then
eval "$input_variable_name='$input'"
break
else
echo -e "${red}Error:${NC} Invalid input. Please try again."
fi
done
echo "-----------------"
}
read -p "Enter the number of the user to remove: " selected_number
# Prompt for username
prompt_for_input "Enter the username you want to edit: " '^[a-z0-9]+$' '' username
if ! [[ "$selected_number" =~ ^[0-9]+$ ]]; then
echo "${red}Error:${NC} Invalid input. Please enter a number."
return
# Check if user exists
if ! python3 $CLI_PATH get-user --username "$username" > /dev/null 2>&1; then
echo -e "${red}Error:${NC} User '$username' not found."
return 1
fi
# Prompt for new username
prompt_for_input "Enter the new username (leave empty to keep the current username): " '^[a-z0-9]*$' '' new_username
# Prompt for new traffic limit
prompt_for_input "Enter the new traffic limit (in GB) (leave empty to keep the current limit): " '^[0-9]*$' '' new_traffic_limit_GB
# Prompt for new expiration days
prompt_for_input "Enter the new expiration days (leave empty to keep the current expiration days): " '^[0-9]*$' '' new_expiration_days
# Prompt for renewing password
while true; do
read -p "Do you want to generate a new password? (y/n): " renew_password
case "$renew_password" in
y|Y) renew_password=true; break ;;
n|N) renew_password=false; break ;;
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
esac
done
# Prompt for renewing creation date
while true; do
read -p "Do you want to generate a new creation date? (y/n): " renew_creation_date
case "$renew_creation_date" in
y|Y) renew_creation_date=true; break ;;
n|N) renew_creation_date=false; break ;;
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
esac
done
# Prompt for blocking user
while true; do
read -p "Do you want to block the user? (y/n): " block_user
case "$block_user" in
y|Y) blocked=true; break ;;
n|N) blocked=false; break ;;
*) echo -e "${red}Error:${NC} Please answer 'y' or 'n'." ;;
esac
done
# Call the edit-user script with appropriate flags
python3 $CLI_PATH edit-user \
--username "$username" \
${new_username:+--new-username "$new_username"} \
${new_traffic_limit_GB:+--new-traffic-limit "$new_traffic_limit_GB"} \
${new_expiration_days:+--new-expiration-days "$new_expiration_days"} \
${renew_password:+--renew-password} \
${renew_creation_date:+--renew-creation-date} \
${blocked:+--blocked}
}
hysteria2_remove_user_handler() {
while true; do
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
fi
done
python3 $CLI_PATH remove-user --username "$username"
}
if [ "$selected_number" -lt 1 ] || [ "$selected_number" -gt "$i" ]; then
echo "${red}Error:${NC} Invalid selection. Please enter a number within the range."
return
hysteria2_get_user_handler() {
while true; do
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
fi
done
selected_user=$(echo "$users" | sed -n "${selected_number}p")
jq --arg selected_user "$selected_user" 'del(.[$selected_user])' /etc/hysteria/users/users.json > /etc/hysteria/users/users_temp.json && mv /etc/hysteria/users/users_temp.json /etc/hysteria/users/users.json
if [ -f "/etc/hysteria/traffic_data.json" ]; then
jq --arg selected_user "$selected_user" 'del(.[$selected_user])' /etc/hysteria/traffic_data.json > /etc/hysteria/traffic_data_temp.json && mv /etc/hysteria/traffic_data_temp.json /etc/hysteria/traffic_data.json
fi
restart_hysteria_service >/dev/null 2>&1
echo "User $selected_user removed successfully."
else
echo "${red}Error:${NC} Config file /etc/hysteria/traffic_data.json not found."
# Run the command and suppress error output
if ! python3 "$CLI_PATH" get-user --username "$username" > /dev/null 2>&1; then
echo -e "${red}Error:${NC} User '$username' not found."
return 1
fi
}
hysteria2_list_users_handler() {
users_json=$(python3 $CLI_PATH list-users 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$users_json" ]; then
echo -e "${red}Error:${NC} Failed to list users."
return 1
fi
# Extract keys (usernames) from JSON
users_keys=$(echo "$users_json" | jq -r 'keys[]')
if [ -z "$users_keys" ]; then
echo -e "${red}Error:${NC} No users found."
return 1
fi
# Print headers
printf "%-20s %-20s %-15s %-20s %-30s %-10s\n" "Username" "Traffic Limit (GB)" "Expiration (Days)" "Creation Date" "Password" "Blocked"
# Print user details
for key in $users_keys; do
echo "$users_json" | jq -r --arg key "$key" '
"\($key) \(.[$key].max_download_bytes / 1073741824) \(.[$key].expiration_days) \(.[$key].account_creation_date) \(.[$key].password) \(.[$key].blocked)"' | \
while IFS= read -r line; do
IFS=' ' read -r username traffic_limit expiration_date creation_date password blocked <<< "$line"
printf "%-20s %-20s %-15s %-20s %-30s %-10s\n" "$username" "$traffic_limit" "$expiration_date" "$creation_date" "$password" "$blocked"
done
done
}
hysteria2_show_user_uri_handler() {
while true; do
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain lowercase letters and numbers."
fi
done
python3 $CLI_PATH show-user-uri --username "$username"
}
hysteria2_change_port_handler() {
while true; do
read -p "Enter the new port number you want to use: " port
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
echo "Invalid port number. Please enter a number between 1 and 65535."
else
break
fi
done
python3 $CLI_PATH change-hysteria2-port --port "$port"
}
warp_configure_handler() {
# Placeholder function, add implementation here if needed
echo "empty"
}
# Function to display the main menu
display_main_menu() {
clear
@ -518,7 +234,6 @@ main_menu() {
clear
local choice
while true; do
define_colors
get_system_info
display_main_menu
read -r choice
@ -544,10 +259,12 @@ display_hysteria2_menu() {
echo -e "${green}[1] ${NC}↝ Install and Configure Hysteria2"
echo -e "${cyan}[2] ${NC}↝ Add User"
echo -e "${cyan}[3] ${NC}Modify User"
echo -e "${cyan}[4] ${NC}Show URI"
echo -e "${cyan}[5] ${NC}Check Traffic Status"
echo -e "${cyan}[6] ${NC}Remove User"
echo -e "${cyan}[3] ${NC}Edit User"
echo -e "${cyan}[4] ${NC}Remove User"
echo -e "${cyan}[5] ${NC}Get User"
echo -e "${cyan}[6] ${NC}List Users (WIP)"
echo -e "${cyan}[7] ${NC}↝ Check Traffic Status"
echo -e "${cyan}[8] ${NC}↝ Show User URI"
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
@ -561,41 +278,18 @@ hysteria2_menu() {
clear
local choice
while true; do
define_colors
get_system_info
display_hysteria2_menu
read -r choice
case $choice in
1) install_and_configure ;;
2) add_user ;;
3) modify_users ;;
4) show_uri ;;
5) traffic_status ;;
6) remove_user ;;
0) return ;;
*) echo "Invalid option. Please try again." ;;
esac
echo
read -rp "Press Enter to continue..."
done
}
# Function to handle Advance menu options
advance_menu() {
clear
local choice
while true; do
define_colors
display_advance_menu
read -r choice
case $choice in
1) install_tcp_brutal ;;
2) install_warp ;;
3) configure_warp ;;
4) uninstall_warp ;;
5) change_port ;;
6) update_core ;;
7) uninstall_hysteria ;;
1) hysteria2_install_handler ;;
2) hysteria2_add_user_handler ;;
3) hysteria2_edit_user ;;
4) hysteria2_remove_user_handler ;;
5) hysteria2_get_user_handler ;;
6) hysteria2_list_users_handler ;;
7) python3 $CLI_PATH traffic-status ;;
8) hysteria2_show_user_uri_handler ;;
0) return ;;
*) echo "Invalid option. Please try again." ;;
esac
@ -622,10 +316,34 @@ display_advance_menu() {
echo -ne "${yellow}➜ Enter your option: ${NC}"
}
# Function to handle Advance menu options
advance_menu() {
clear
local choice
while true; do
display_advance_menu
read -r choice
case $choice in
1) python3 $CLI_PATH install-tcp-brutal ;;
2) python3 $CLI_PATH install-warp ;;
3) warp_configure_handler ;;
4) python3 $CLI_PATH uninstall-warp ;;
5) hysteria2_change_port_handler ;;
6) python3 $CLI_PATH update-hysteria2 ;;
7) python3 $CLI_PATH uninstall-hysteria2 ;;
0) return ;;
*) echo "Invalid option. Please try again." ;;
esac
echo
read -rp "Press Enter to continue..."
done
}
# Main function to run the script
main() {
main_menu
}
define_colors
# Run the main function
main