Merge pull request #2 from ReturnFI/beta

Refactor CLI
This commit is contained in:
Whispering Wind
2024-08-02 20:40:47 +03:30
committed by GitHub
27 changed files with 1323 additions and 632 deletions

View File

@ -9,7 +9,9 @@
```shell ```shell
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh) bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh)
``` ```
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دستور نصب نیست.
<br />
<p align="center"> <p align="center">
<img src="https://github.com/ReturnFI/Hysteria2/assets/151555003/b1c7ab9f-7887-46fd-8e13-a7bfe9bf5990" width="500" height="250"> <img src="https://github.com/ReturnFI/Hysteria2/assets/151555003/b1c7ab9f-7887-46fd-8e13-a7bfe9bf5990" width="500" height="250">
<p/> <p/>

View File

@ -11,9 +11,14 @@ This shell script provides a menu-driven interface to manage Hysteria2 server op
### Install command : ### Install command :
```shell ```shell
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh) bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/install.sh)
``` ```
After installation, simply use the command `hys2` to run the Hysteria2 script.
There is no need to execute the installation command again.
<br />
<p align="center"> <p align="center">
<img src="https://github.com/ReturnFI/Hysteria2/assets/151555003/b1c7ab9f-7887-46fd-8e13-a7bfe9bf5990" width="500" height="250"> <img src="https://github.com/ReturnFI/Hysteria2/assets/151555003/b1c7ab9f-7887-46fd-8e13-a7bfe9bf5990" width="500" height="250">
<p/> <p/>

View File

@ -14,14 +14,14 @@
}, },
"auth": { "auth": {
"type": "command", "type": "command",
"command": "/etc/hysteria/users/user.sh" "command": "/etc/hysteria/core/scripts/hysteria2/user.sh"
}, },
"quic": { "quic": {
"initStreamReceiveWindow": 8388608, "initStreamReceiveWindow": 8388608,
"maxStreamReceiveWindow": 8388608, "maxStreamReceiveWindow": 8388608,
"initConnReceiveWindow": 20971520, "initConnReceiveWindow": 20971520,
"maxConnReceiveWindow": 20971520, "maxConnReceiveWindow": 20971520,
"maxIdleTimeout": "60s", "maxIdleTimeout": "20s",
"maxIncomingStreams": 60, "maxIncomingStreams": 60,
"disablePathMTUDiscovery": false "disablePathMTUDiscovery": false
}, },
@ -31,7 +31,7 @@
}, },
"ignoreClientBandwidth": false, "ignoreClientBandwidth": false,
"disableUDP": false, "disableUDP": false,
"udpIdleTimeout": "60s", "udpIdleTimeout": "20s",
"resolver": { "resolver": {
"type": "tls", "type": "tls",
"tcp": { "tcp": {

239
core/cli.py Normal file
View File

@ -0,0 +1,239 @@
#!/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,
'true' if blocked 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):
options = {
"all": all,
"popular_sites": popular_sites,
"domestic_sites": domestic_sites,
"block_adult_sites": block_adult_sites
}
options = {k: 'true' if v else 'false' for k, v in options.items()}
run_cmd(['bash', Command.CONFIGURE_WARP.value,
options['all'], options['popular_sites'], options['domestic_sites'], options['block_adult_sites']])
# 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-zA-Z0-9]+$ ]]; then
echo -e "${red}Error:${NC} Username can only contain 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,184 @@
#!/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-zA-Z0-9]+$ ]]; then
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
exit 1
fi
fi
# Validate traffic limit
if [ -n "$new_traffic_limit" ]; 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" ] && [ "$new_blocked" != "y" ] && [ "$new_blocked" != "n" ]; then
echo -e "${red}Error:${NC} Blocked status must be 'true', 'false', 'y', or 'n'."
exit 1
fi
fi
}
# Convert 'y'/'n' to 'true'/'false'
convert_blocked_status() {
local status=$1
case "$status" in
y|Y) echo "true" ;;
n|N) echo "false" ;;
true|false) echo "$status" ;;
*) echo "false" ;; # Default to false if something unexpected
esac
}
# 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"
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."
# Debugging output
echo "Updating user:"
echo "Username: $new_username"
echo "Password: $new_password"
echo "Max Download Bytes: $new_max_download_bytes"
echo "Expiration Days: $new_expiration_days"
echo "Creation Date: $new_account_creation_date"
echo "Blocked: $new_blocked"
# Update user fields, only if new values are provided
jq --arg old_username "$old_username" \
--arg new_username "$new_username" \
--arg password "${new_password:-null}" \
--argjson max_download_bytes "${new_max_download_bytes:-null}" \
--argjson expiration_days "${new_expiration_days:-null}" \
--arg account_creation_date "${new_creation_date:-null}" \
--argjson blocked "$(convert_blocked_status "${new_blocked:-false}")" \
'
.[$new_username] = .[$old_username] |
del(.[$old_username]) |
.[$new_username] |= (
.password = ($password // .password) |
.max_download_bytes = ($max_download_bytes // .max_download_bytes) |
.expiration_days = ($expiration_days // .expiration_days) |
.account_creation_date = ($account_creation_date // .account_creation_date) |
.blocked = $blocked
)' "$USERS_FILE" > tmp.$$.json && mv tmp.$$.json "$USERS_FILE"
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}
# Convert traffic limit to bytes if provided, otherwise keep existing
if [ -n "$new_traffic_limit" ]; then
new_traffic_limit=$(echo "$new_traffic_limit * 1073741824" | bc)
else
new_traffic_limit=$traffic_limit
fi
new_expiration_days=${new_expiration_days:-$expiration_days}
new_creation_date=${new_creation_date:-$creation_date}
new_blocked=$(convert_blocked_status "${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 -e "${cyan}Hysteria2${green} has been successfully installed."
else
echo -e "${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 #!/bin/bash
USERS_FILE="/etc/hysteria/users/users.json" source /etc/hysteria/core/scripts/path.sh
TRAFFIC_FILE="/etc/hysteria/traffic_data.json"
CONFIG_FILE="/etc/hysteria/config.json"
kick_user() { kick_user() {
local username=$1 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" AUTH="$2"
TX="$3" TX="$3"
USERS_FILE="/etc/hysteria/users/users.json" # Source the path.sh script to load variables
TRAFFIC_FILE="/etc/hysteria/traffic_data.json" source /etc/hysteria/core/scripts/path.sh
CONFIG_FILE="/etc/hysteria/config.json"
# Extract username and password from AUTH
IFS=':' read -r USERNAME PASSWORD <<< "$AUTH" IFS=':' read -r USERNAME PASSWORD <<< "$AUTH"
# Retrieve stored user data
STORED_PASSWORD=$(jq -r --arg user "$USERNAME" '.[$user].password' "$USERS_FILE") 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") 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") 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") ACCOUNT_CREATION_DATE=$(jq -r --arg user "$USERNAME" '.[$user].account_creation_date' "$USERS_FILE")
BLOCKED=$(jq -r --arg user "$USERNAME" '.[$user].blocked' "$USERS_FILE") BLOCKED=$(jq -r --arg user "$USERNAME" '.[$user].blocked' "$USERS_FILE")
# Check if the user is blocked
if [ "$BLOCKED" == "true" ]; then if [ "$BLOCKED" == "true" ]; then
exit 1 exit 1
fi fi
# Check if the provided password matches the stored password
if [ "$STORED_PASSWORD" != "$PASSWORD" ]; then if [ "$STORED_PASSWORD" != "$PASSWORD" ]; then
exit 1 exit 1
fi fi
# Check if the user's account has expired
CURRENT_DATE=$(date +%s) CURRENT_DATE=$(date +%s)
EXPIRATION_DATE=$(date -d "$ACCOUNT_CREATION_DATE + $EXPIRATION_DAYS days" +%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 exit 1
fi fi
# Check if the user's download limit has been exceeded
CURRENT_DOWNLOAD_BYTES=$(jq -r --arg user "$USERNAME" '.[$user].download_bytes' "$TRAFFIC_FILE") CURRENT_DOWNLOAD_BYTES=$(jq -r --arg user "$USERNAME" '.[$user].download_bytes' "$TRAFFIC_FILE")
if [ "$CURRENT_DOWNLOAD_BYTES" -ge "$MAX_DOWNLOAD_BYTES" ]; then if [ "$CURRENT_DOWNLOAD_BYTES" -ge "$MAX_DOWNLOAD_BYTES" ]; then
@ -43,5 +48,6 @@ if [ "$CURRENT_DOWNLOAD_BYTES" -ge "$MAX_DOWNLOAD_BYTES" ]; then
exit 1 exit 1
fi fi
# If all checks pass, print the username and exit successfully
echo "$USERNAME" 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,56 @@
#!/bin/bash
source /etc/hysteria/core/scripts/path.sh
warp_configure_handler() {
local all=$1
local popular_sites=$2
local domestic_sites=$3
local block_adult_sites=$4
if [ "$all" == "true" ]; then
if [ "$(jq -r 'if .acl.inline | index("warps(all)") then "WARP active" else "Direct" end' "$CONFIG_FILE")" == "WARP active" ]; then
jq 'del(.acl.inline[] | select(. == "warps(all)"))' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "Traffic configuration changed to Direct."
else
jq '.acl.inline += ["warps(all)"]' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "Traffic configuration changed to WARP."
fi
fi
if [ "$popular_sites" == "true" ]; then
if [ "$(jq -r 'if (.acl.inline | index("warps(geoip:google)")) or (.acl.inline | index("warps(geosite:google)")) or (.acl.inline | index("warps(geosite:netflix)")) or (.acl.inline | index("warps(geosite:spotify)")) or (.acl.inline | index("warps(geosite:openai)")) or (.acl.inline | index("warps(geoip:openai)")) then "WARP active" else "Direct" end' "$CONFIG_FILE")" == "WARP active" ]; then
jq 'del(.acl.inline[] | select(. == "warps(geoip:google)" or . == "warps(geosite:google)" or . == "warps(geosite:netflix)" or . == "warps(geosite:spotify)" or . == "warps(geosite:openai)" or . == "warps(geoip:openai)"))' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "WARP configuration for Google, OpenAI, etc. removed."
else
jq '.acl.inline += ["warps(geoip:google)", "warps(geosite:google)", "warps(geosite:netflix)", "warps(geosite:spotify)", "warps(geosite:openai)", "warps(geoip:openai)"]' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "WARP configured for Google, OpenAI, etc."
fi
fi
if [ "$domestic_sites" == "true" ]; then
if [ "$(jq -r 'if (.acl.inline | index("warps(geosite:ir)")) and (.acl.inline | index("warps(geoip:ir)")) then "Use WARP" else "Reject" end' "$CONFIG_FILE")" == "Use WARP" ]; then
jq '(.acl.inline[] | select(. == "warps(geosite:ir)")) = "reject(geosite:ir)" | (.acl.inline[] | select(. == "warps(geoip:ir)")) = "reject(geoip:ir)"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "Configuration changed to Reject for geosite:ir and geoip:ir."
else
jq '(.acl.inline[] | select(. == "reject(geosite:ir)")) = "warps(geosite:ir)" | (.acl.inline[] | select(. == "reject(geoip:ir)")) = "warps(geoip:ir)"' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "Configuration changed to Use WARP for geosite:ir and geoip:ir."
fi
fi
if [ "$block_adult_sites" == "true" ]; then
if [ "$(jq -r 'if .acl.inline | index("reject(geosite:category-porn)") then "Blocked" else "Not blocked" end' "$CONFIG_FILE")" == "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
fi
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
}
warp_configure_handler "$1" "$2" "$3" "$4"

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 json
import os 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(): def traffic_status():
green = '\033[0;32m' green = '\033[0;32m'
cyan = '\033[0;36m' cyan = '\033[0;36m'
NC = '\033[0m' NC = '\033[0m'
try: 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: 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 return
if not secret: if not secret:
@ -19,7 +25,7 @@ def traffic_status():
return return
try: 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: except subprocess.CalledProcessError as e:
print(f"Error: Failed to fetch traffic data. Details: {e}") print(f"Error: Failed to fetch traffic data. Details: {e}")
return return
@ -29,7 +35,7 @@ def traffic_status():
return return
try: 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: except subprocess.CalledProcessError as e:
print(f"Error: Failed to fetch online status data. Details: {e}") print(f"Error: Failed to fetch online status data. Details: {e}")
return return
@ -55,9 +61,9 @@ def traffic_status():
} }
existing_data = {} existing_data = {}
if os.path.exists('/etc/hysteria/traffic_data.json'): if os.path.exists(TRAFFIC_FILE):
try: 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) existing_data = json.load(json_file)
except json.JSONDecodeError: except json.JSONDecodeError:
print("Error: Failed to parse existing traffic data JSON file.") print("Error: Failed to parse existing traffic data JSON file.")
@ -71,7 +77,7 @@ def traffic_status():
else: else:
existing_data[user] = data 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) json.dump(existing_data, json_file, indent=4)
display_traffic_data(existing_data, green, cyan, NC) display_traffic_data(existing_data, green, cyan, NC)
@ -108,5 +114,3 @@ def format_bytes(bytes):
return f"{bytes / 1073741824:.2f}GB" return f"{bytes / 1073741824:.2f}GB"
else: else:
return f"{bytes / 1099511627776:.2f}TB" 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,26 @@
#!/bin/bash # Ensure necessary packages are installed
define_colors() { # Check if the script is being run by the root user
green='\033[0;32m' if [ "$(id -u)" -ne 0 ]; then
cyan='\033[0;36m' echo "This script must be run as root."
red='\033[0;31m' exit 1
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
fi fi
# Get the default network interface clear
networkdef=$(ip route | grep "^default" | awk '{print $5}') 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..."
# Step 10: Customize the config.json file sleep 3
echo "Customizing config.json..." echo
jq --arg port "$port" \ apt update && apt upgrade -y && apt install jq qrencode curl pwgen uuid-runtime python3 python3-pip git -y
--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."
fi fi
# Step 15: wget Traffic/user/kick script git clone -b beta https://github.com/ReturnFI/Hysteria2 /etc/hysteria
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 # Add alias 'hys2' for Hysteria2
chmod +x /etc/hysteria/users/kick.sh if ! grep -q "alias hys2='/etc/hysteria/menu.sh'" ~/.bashrc; then
# Add the commands to the crontab echo "alias hys2='/etc/hysteria/menu.sh'" >> ~/.bashrc
(crontab -l ; echo "*/1 * * * * python3 /etc/hysteria/traffic.py >/dev/null 2>&1") | crontab - source ~/.bashrc
(crontab -l ; echo "*/1 * * * * /etc/hysteria/users/kick.sh >/dev/null 2>&1") | crontab - fi
cd /etc/hysteria
chmod +x menu.sh
./menu.sh

726
menu.sh
View File

@ -1,109 +1,16 @@
#!/bin/bash #!/bin/bash
# Function to define colors source /etc/hysteria/core/scripts/utils.sh
define_colors() { source /etc/hysteria/core/scripts/path.sh
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
}
# Ensure necessary packages are installed # OPTION HANDLERS (ONLY NEEDED ONE)
clear hysteria2_install_handler() {
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 if systemctl is-active --quiet hysteria-server.service; then
echo -e "${red}Error:${NC} Hysteria2 is already installed and running." echo "The hysteria-server.service is currently active."
echo
echo "If you need to update the core, please use the 'Update Core' option." echo "If you need to update the core, please use the 'Update Core' option."
else return
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 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() {
while true; do while true; do
read -p "Enter the new port number you want to use: " port read -p "Enter the new port number you want to use: " port
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
@ -113,384 +20,224 @@ change_port() {
fi fi
done done
if [ -f "/etc/hysteria/config.json" ]; then python3 $CLI_PATH install-hysteria2 --port "$port"
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
} }
# Function to show URI if Hysteria2 is installed and active hysteria2_add_user_handler() {
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() {
while true; do while true; do
read -p "Enter the username: " username read -p "Enter the username: " username
if [[ "$username" =~ ^[a-z0-9]+$ ]]; then if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
break break
else 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 letters and numbers."
fi fi
done done
read -p "Enter the traffic limit (in GB): " traffic_gb read -p "Enter the traffic limit (in GB): " traffic_limit_GB
# Convert GB to bytes (1 GB = 1073741824 bytes)
traffic=$((traffic_gb * 1073741824))
read -p "Enter the expiration days: " expiration_days read -p "Enter the expiration days: " expiration_days
password=$(pwgen -s 32 1) password=$(pwgen -s 32 1)
creation_date=$(date +%Y-%m-%d) creation_date=$(date +%Y-%m-%d)
if [ ! -f "/etc/hysteria/users/users.json" ]; then python3 $CLI_PATH add-user --username "$username" --traffic-limit "$traffic_limit_GB" --expiration-days "$expiration_days" --password "$password" --creation-date "$creation_date"
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"
} }
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 while true; do
remove_user() { read -p "$prompt_message" input
if [ -f "/etc/hysteria/users/users.json" ]; then if [[ -z "$input" ]]; then
# Extract current users from the users.json file input="$default_value"
users=$(jq -r 'keys[]' /etc/hysteria/users/users.json) fi
if [[ "$input" =~ $validation_regex ]]; then
if [ -z "$users" ]; then eval "$input_variable_name='$input'"
echo "No users found." break
return else
fi echo -e "${red}Error:${NC} Invalid input. Please try again."
fi
# Display current users with numbering
echo "Current users:"
echo "-----------------"
i=1
for user in $users; do
echo "$i. $user"
((i++))
done 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-zA-Z0-9]+$' '' username
if ! [[ "$selected_number" =~ ^[0-9]+$ ]]; then # Check if user exists
echo "${red}Error:${NC} Invalid input. Please enter a number." if ! python3 $CLI_PATH get-user --username "$username" > /dev/null 2>&1; then
return 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-zA-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
# Determine if we need to renew 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
# Determine if we need to renew 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
# Determine if user should be blocked
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
# Construct the arguments for the edit-user command
args=()
if [[ -n "$new_username" ]]; then args+=("--new-username" "$new_username"); fi
if [[ -n "$new_traffic_limit_GB" ]]; then args+=("--new-traffic-limit" "$new_traffic_limit_GB"); fi
if [[ -n "$new_expiration_days" ]]; then args+=("--new-expiration-days" "$new_expiration_days"); fi
if [[ "$renew_password" == "true" ]]; then args+=("--renew-password"); fi
if [[ "$renew_creation_date" == "true" ]]; then args+=("--renew-creation-date"); fi
if [[ "$blocked" == "true" ]]; then args+=("--blocked"); fi
# Call the edit-user script with the constructed arguments
python3 $CLI_PATH edit-user --username "$username" "${args[@]}"
}
hysteria2_remove_user_handler() {
while true; do
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
fi fi
done
python3 $CLI_PATH remove-user --username "$username"
}
if [ "$selected_number" -lt 1 ] || [ "$selected_number" -gt "$i" ]; then hysteria2_get_user_handler() {
echo "${red}Error:${NC} Invalid selection. Please enter a number within the range." while true; do
return read -p "Enter the username: " username
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
fi fi
done
selected_user=$(echo "$users" | sed -n "${selected_number}p") # Run the command and suppress error output
if ! python3 "$CLI_PATH" get-user --username "$username" > /dev/null 2>&1; then
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 echo -e "${red}Error:${NC} User '$username' not found."
return 1
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."
fi 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-zA-Z0-9]+$ ]]; then
break
else
echo -e "${red}Error:${NC} Username can only contain 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() {
local service_name="wg-quick@wgcf.service"
if systemctl is-active --quiet "$service_name"; then
echo "Configure WARP Options:"
echo "1. Use WARP for all traffic"
echo "2. Use WARP for popular sites"
echo "3. Use WARP for domestic sites"
echo "4. Block adult content"
echo "0. Cancel"
read -p "Select an option: " option
case $option in
1) python3 $CLI_PATH configure-warp --all ;;
2) python3 $CLI_PATH configure-warp --popular-sites ;;
3) python3 $CLI_PATH configure-warp --domestic-sites ;;
4) python3 $CLI_PATH configure-warp --block-adult-sites ;;
0) echo "WARP configuration canceled." ;;
*) echo "Invalid option. Please try again." ;;
esac
else
# Notify user if the service is not active
echo "$service_name is not active. Please start the service before configuring WARP."
fi
}
# Function to display the main menu # Function to display the main menu
display_main_menu() { display_main_menu() {
clear clear
@ -518,7 +265,6 @@ main_menu() {
clear clear
local choice local choice
while true; do while true; do
define_colors
get_system_info get_system_info
display_main_menu display_main_menu
read -r choice read -r choice
@ -544,10 +290,12 @@ display_hysteria2_menu() {
echo -e "${green}[1] ${NC}↝ Install and Configure Hysteria2" echo -e "${green}[1] ${NC}↝ Install and Configure Hysteria2"
echo -e "${cyan}[2] ${NC}↝ Add User" echo -e "${cyan}[2] ${NC}↝ Add User"
echo -e "${cyan}[3] ${NC}Modify User" echo -e "${cyan}[3] ${NC}Edit User"
echo -e "${cyan}[4] ${NC}Show URI" echo -e "${cyan}[4] ${NC}Remove User"
echo -e "${cyan}[5] ${NC}Check Traffic Status" echo -e "${cyan}[5] ${NC}Get User"
echo -e "${cyan}[6] ${NC}Remove 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" echo -e "${red}[0] ${NC}↝ Back to Main Menu"
@ -561,41 +309,18 @@ hysteria2_menu() {
clear clear
local choice local choice
while true; do while true; do
define_colors
get_system_info get_system_info
display_hysteria2_menu display_hysteria2_menu
read -r choice read -r choice
case $choice in case $choice in
1) install_and_configure ;; 1) hysteria2_install_handler ;;
2) add_user ;; 2) hysteria2_add_user_handler ;;
3) modify_users ;; 3) hysteria2_edit_user ;;
4) show_uri ;; 4) hysteria2_remove_user_handler ;;
5) traffic_status ;; 5) hysteria2_get_user_handler ;;
6) remove_user ;; 6) hysteria2_list_users_handler ;;
0) return ;; 7) python3 $CLI_PATH traffic-status ;;
*) echo "Invalid option. Please try again." ;; 8) hysteria2_show_user_uri_handler ;;
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 ;;
0) return ;; 0) return ;;
*) echo "Invalid option. Please try again." ;; *) echo "Invalid option. Please try again." ;;
esac esac
@ -622,10 +347,29 @@ display_advance_menu() {
echo -ne "${yellow}➜ Enter your option: ${NC}" echo -ne "${yellow}➜ Enter your option: ${NC}"
} }
# Main function to run the script # Function to handle Advance menu options
main() { advance_menu() {
main_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
} }
# Run the main function # Main function to run the script
main define_colors
main_menu