@ -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
229
core/cli.py
Normal 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()
|
||||
62
core/scripts/hysteria2/add_user.sh
Normal file
62
core/scripts/hysteria2/add_user.sh
Normal 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"
|
||||
27
core/scripts/hysteria2/change_port.sh
Normal file
27
core/scripts/hysteria2/change_port.sh
Normal 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"
|
||||
177
core/scripts/hysteria2/edit_user.sh
Normal file
177
core/scripts/hysteria2/edit_user.sh
Normal 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"
|
||||
32
core/scripts/hysteria2/get_user.sh
Normal file
32
core/scripts/hysteria2/get_user.sh
Normal 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
|
||||
140
core/scripts/hysteria2/install.sh
Normal file
140
core/scripts/hysteria2/install.sh
Normal 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
|
||||
@ -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
|
||||
6
core/scripts/hysteria2/list_users.sh
Normal file
6
core/scripts/hysteria2/list_users.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
source /etc/hysteria/core/scripts/path.sh
|
||||
|
||||
|
||||
cat "$USERS_FILE"
|
||||
37
core/scripts/hysteria2/remove_user.sh
Normal file
37
core/scripts/hysteria2/remove_user.sh
Normal 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"
|
||||
4
core/scripts/hysteria2/restart.sh
Normal file
4
core/scripts/hysteria2/restart.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
python3 /etc/hysteria/core/cli.py traffic-status > /dev/null 2>&1
|
||||
systemctl restart hysteria-server.service
|
||||
67
core/scripts/hysteria2/show_user_uri.sh
Normal file
67
core/scripts/hysteria2/show_user_uri.sh
Normal 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"
|
||||
28
core/scripts/hysteria2/uninstall.sh
Normal file
28
core/scripts/hysteria2/uninstall.sh
Normal 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 ""
|
||||
47
core/scripts/hysteria2/update.sh
Normal file
47
core/scripts/hysteria2/update.sh
Normal 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
|
||||
@ -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
4
core/scripts/path.sh
Normal 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"
|
||||
6
core/scripts/tcp-brutal/install.sh
Normal file
6
core/scripts/tcp-brutal/install.sh
Normal 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
22
core/scripts/utils.sh
Normal 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 }')
|
||||
}
|
||||
105
core/scripts/warp/configure.sh
Normal file
105
core/scripts/warp/configure.sh
Normal 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
|
||||
23
core/scripts/warp/install.sh
Normal file
23
core/scripts/warp/install.sh
Normal 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
|
||||
38
core/scripts/warp/uninstall.sh
Normal file
38
core/scripts/warp/uninstall.sh
Normal 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
|
||||
@ -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
10
core/validator.py
Normal 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
|
||||
144
install.sh
144
install.sh
@ -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
698
menu.sh
@ -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
|
||||
|
||||
Reference in New Issue
Block a user