@ -9,7 +9,12 @@
|
||||
```shell
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh)
|
||||
```
|
||||
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دستور نصب نیست.
|
||||
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دوباره دستور نصب نیست.
|
||||
|
||||
### دستور آپدیت:
|
||||
```shell
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh)
|
||||
```
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
|
||||
@ -17,6 +17,10 @@ After installation, simply use the command `hys2` to run the Hysteria2 script.
|
||||
|
||||
There is no need to execute the installation command again.
|
||||
|
||||
### Upgrade command :
|
||||
```shell
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh)
|
||||
```
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
|
||||
51
core/cli.py
51
core/cli.py
@ -12,7 +12,7 @@ import validator
|
||||
|
||||
|
||||
SCRIPT_DIR = '/etc/hysteria/core/scripts'
|
||||
DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
|
||||
class Command(Enum):
|
||||
@ -30,6 +30,7 @@ class Command(Enum):
|
||||
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')
|
||||
SERVER_INFO = os.path.join(SCRIPT_DIR, 'hysteria2', 'server_info.sh')
|
||||
INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.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')
|
||||
@ -42,11 +43,14 @@ 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:
|
||||
|
||||
# if the command is GET_USER or LIST_USERS we don't print the debug-command and just print the output
|
||||
if DEBUG and not (Command.GET_USER.value in command or Command.LIST_USERS.value in command):
|
||||
print(' '.join(command))
|
||||
|
||||
result = subprocess.check_output(command, shell=False)
|
||||
if DEBUG:
|
||||
print(result.decode().strip())
|
||||
|
||||
print(result.decode().strip())
|
||||
|
||||
|
||||
def generate_password() -> str:
|
||||
@ -174,17 +178,27 @@ def edit_user(username: str, new_username: str, new_traffic_limit: int, new_expi
|
||||
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('show-user-uri')
|
||||
@click.option('--username', '-u', required=True, help='Username for the user to show the URI', type=str)
|
||||
@click.option('--qrcode', '-qr', is_flag=True, help='Generate QR code for the URI')
|
||||
@click.option('--ipv', '-ip', type=click.IntRange(4, 6), default=4, help='IP version (4 or 6)')
|
||||
@click.option('--all', '-a', is_flag=True, help='Show both IPv4 and IPv6 URIs and generate QR codes for both if requested')
|
||||
def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool):
|
||||
command_args = ['bash', Command.SHOW_USER_URI.value, '-u', username]
|
||||
if qrcode:
|
||||
command_args.append('-qr')
|
||||
if all:
|
||||
command_args.append('-a')
|
||||
else:
|
||||
command_args.extend(['-ip', str(ipv)])
|
||||
|
||||
run_cmd(command_args)
|
||||
|
||||
|
||||
@ cli.command('traffic-status')
|
||||
@ -196,6 +210,7 @@ def traffic_status():
|
||||
def list_users():
|
||||
run_cmd(['bash', Command.LIST_USERS.value])
|
||||
|
||||
|
||||
@cli.command('server-info')
|
||||
def server_info():
|
||||
output = run_cmd(['bash', Command.SERVER_INFO.value])
|
||||
@ -233,11 +248,25 @@ def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_a
|
||||
"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,
|
||||
run_cmd(['bash', Command.CONFIGURE_WARP.value,
|
||||
options['all'], options['popular_sites'], options['domestic_sites'], options['block_adult_sites']])
|
||||
|
||||
@cli.command('telegram')
|
||||
@click.option('--action', '-a', required=True, help='Action to perform: start or stop', type=click.Choice(['start', 'stop'], case_sensitive=False))
|
||||
@click.option('--token', '-t', required=False, help='Token for running the telegram bot', type=str)
|
||||
@click.option('--adminid', '-aid', required=False, help='Telegram admins ID for running the telegram bot', type=str)
|
||||
def telegram(action: str, token: str, adminid: str):
|
||||
if action == 'start':
|
||||
if not token or not adminid:
|
||||
print("Error: Both --token and --adminid are required for the start action.")
|
||||
return
|
||||
admin_ids = f'{adminid}'
|
||||
run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'start', token, admin_ids])
|
||||
elif action == 'stop':
|
||||
run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'stop'])
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
# 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
|
||||
# source /etc/hysteria/core/scripts/utils.sh
|
||||
# define_colors
|
||||
|
||||
# Function to add a new user to the configuration
|
||||
add_user() {
|
||||
@ -55,7 +55,7 @@ add_user() {
|
||||
|
||||
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
|
||||
|
||||
echo -e "${green}User $username added successfully.${NC}"
|
||||
echo -e "User $username added successfully."
|
||||
}
|
||||
|
||||
# Call the function with the provided arguments
|
||||
|
||||
@ -1,57 +1,86 @@
|
||||
#!/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>"
|
||||
local username
|
||||
local generate_qrcode=false
|
||||
local ip_version=4
|
||||
local show_all=false
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
-u|--username) username="$2"; shift ;;
|
||||
-qr|--qrcode) generate_qrcode=true ;;
|
||||
-ip) ip_version="$2"; shift ;;
|
||||
-a|--all) show_all=true ;;
|
||||
*) echo "Unknown parameter passed: $1"; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$username" ]; then
|
||||
echo "Usage: $0 -u <username> [-qr] [-ip <4|6>] [-a]"
|
||||
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)
|
||||
generate_uri() {
|
||||
local ip_version=$1
|
||||
local ip=$2
|
||||
if [ "$ip_version" -eq 4 ]; then
|
||||
echo "hy2://$username%3A$authpassword@$ip:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv4"
|
||||
elif [ "$ip_version" -eq 6 ]; then
|
||||
echo "hy2://$username%3A$authpassword@[$ip]:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv6"
|
||||
fi
|
||||
}
|
||||
|
||||
# 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"
|
||||
if [ "$show_all" = true ]; then
|
||||
IP=$(curl -s -4 ip.gs)
|
||||
URI=$(generate_uri 4 "$IP")
|
||||
IP6=$(curl -s -6 ip.gs)
|
||||
URI6=$(generate_uri 6 "$IP6")
|
||||
echo -e "\nIPv4:\n$URI\n"
|
||||
echo -e "\nIPv6:\n$URI6\n"
|
||||
else
|
||||
if [ "$ip_version" -eq 4 ]; then
|
||||
IP=$(curl -s -4 ip.gs)
|
||||
URI=$(generate_uri 4 "$IP")
|
||||
echo -e "\nIPv4:\n$URI\n"
|
||||
elif [ "$ip_version" -eq 6 ]; then
|
||||
IP6=$(curl -s -6 ip.gs)
|
||||
URI6=$(generate_uri 6 "$IP6")
|
||||
echo -e "\nIPv6:\n$URI6\n"
|
||||
else
|
||||
echo "Invalid IP version. Use 4 for IPv4 or 6 for IPv6."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 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
|
||||
if [ "$generate_qrcode" = true ]; then
|
||||
cols=$(tput cols)
|
||||
if [ -n "$URI" ]; then
|
||||
qr1=$(echo -n "$URI" | qrencode -t UTF8 -s 3 -m 2)
|
||||
echo -e "\nIPv4 QR Code:\n"
|
||||
echo "$qr1" | while IFS= read -r line; do
|
||||
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
|
||||
done
|
||||
fi
|
||||
if [ -n "$URI6" ]; then
|
||||
qr2=$(echo -n "$URI6" | qrencode -t UTF8 -s 3 -m 2)
|
||||
echo -e "\nIPv6 QR Code:\n"
|
||||
echo "$qr2" | while IFS= read -r line; do
|
||||
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Invalid username. Please try again."
|
||||
fi
|
||||
@ -63,5 +92,4 @@ show_uri() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Call the function with the provided username argument
|
||||
show_uri "$1"
|
||||
show_uri "$@"
|
||||
|
||||
@ -1,28 +1,37 @@
|
||||
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 "Removing alias 'hys2' from .bashrc..."
|
||||
sed -i '/alias hys2=.*\/etc\/hysteria\/menu.sh/d' ~/.bashrc
|
||||
|
||||
echo "Stopping hysteria-bot.service..."
|
||||
systemctl stop hysteria-bot.service >/dev/null 2>&1
|
||||
echo "Disabling hysteria-bot.service..."
|
||||
systemctl disable hysteria-bot.service >/dev/null 2>&1
|
||||
|
||||
echo "Hysteria2 uninstalled!"
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
89
core/scripts/telegrambot/runbot.sh
Normal file
89
core/scripts/telegrambot/runbot.sh
Normal file
@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
source /etc/hysteria/core/scripts/utils.sh
|
||||
define_colors
|
||||
install_dependencies() {
|
||||
echo "Installing dependencies from /etc/hysteria/requirements.txt..."
|
||||
if ! pip3 install -r /etc/hysteria/requirements.txt > /dev/null 2>&1; then
|
||||
echo "Error: Failed to install dependencies. Please check the requirements file and try again."
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${green}Dependencies installed successfully. ${NC}"
|
||||
}
|
||||
|
||||
update_env_file() {
|
||||
local api_token=$1
|
||||
local admin_user_ids=$2
|
||||
|
||||
cat <<EOL > /etc/hysteria/core/scripts/telegrambot/.env
|
||||
API_TOKEN=$api_token
|
||||
ADMIN_USER_IDS=[$admin_user_ids]
|
||||
EOL
|
||||
}
|
||||
|
||||
create_service_file() {
|
||||
cat <<EOL > /etc/systemd/system/hysteria-bot.service
|
||||
[Unit]
|
||||
Description=Hysteria Telegram Bot
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python3 /etc/hysteria/core/scripts/telegrambot/tbot.py
|
||||
WorkingDirectory=/etc/hysteria/core/scripts/telegrambot
|
||||
Restart=always
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local api_token=$1
|
||||
local admin_user_ids=$2
|
||||
|
||||
if systemctl is-active --quiet hysteria-bot.service; then
|
||||
echo "The hysteria-bot.service is already running."
|
||||
return
|
||||
fi
|
||||
|
||||
install_dependencies
|
||||
update_env_file "$api_token" "$admin_user_ids"
|
||||
create_service_file
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable hysteria-bot.service > /dev/null 2>&1
|
||||
systemctl start hysteria-bot.service > /dev/null 2>&1
|
||||
|
||||
if systemctl is-active --quiet hysteria-bot.service; then
|
||||
echo -e "${green}Hysteria bot setup completed. The service is now running. ${NC}"
|
||||
echo -e "\n\n"
|
||||
else
|
||||
echo "Hysteria bot setup completed. The service failed to start."
|
||||
fi
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
systemctl stop hysteria-bot.service > /dev/null 2>&1
|
||||
systemctl disable hysteria-bot.service > /dev/null 2>&1
|
||||
|
||||
rm -f /etc/hysteria/core/scripts/telegrambot/.env
|
||||
echo -e "\n"
|
||||
|
||||
echo "Hysteria bot service stopped and disabled. .env file removed."
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start_service "$2" "$3"
|
||||
;;
|
||||
stop)
|
||||
stop_service
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop} <API_TOKEN> <ADMIN_USER_IDS>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
define_colors
|
||||
234
core/scripts/telegrambot/tbot.py
Normal file
234
core/scripts/telegrambot/tbot.py
Normal file
@ -0,0 +1,234 @@
|
||||
import telebot
|
||||
import subprocess
|
||||
import qrcode
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from telebot import types
|
||||
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('API_TOKEN')
|
||||
ADMIN_USER_IDS = json.loads(os.getenv('ADMIN_USER_IDS'))
|
||||
CLI_PATH = '/etc/hysteria/core/cli.py'
|
||||
bot = telebot.TeleBot(API_TOKEN)
|
||||
|
||||
def run_cli_command(command):
|
||||
try:
|
||||
result = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
|
||||
return result.decode('utf-8').strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
return f'Error: {e.output.decode("utf-8")}'
|
||||
|
||||
def create_main_markup():
|
||||
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
markup.row('Show User', 'Add User')
|
||||
markup.row('Delete User', 'Server Info')
|
||||
return markup
|
||||
|
||||
def is_admin(user_id):
|
||||
return user_id in ADMIN_USER_IDS
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def send_welcome(message):
|
||||
if is_admin(message.from_user.id):
|
||||
markup = create_main_markup()
|
||||
bot.reply_to(message, "Welcome to the User Management Bot!", reply_markup=markup)
|
||||
else:
|
||||
bot.reply_to(message, "Unauthorized access. You do not have permission to use this bot.")
|
||||
|
||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Add User')
|
||||
def add_user(message):
|
||||
msg = bot.reply_to(message, "Enter username:")
|
||||
bot.register_next_step_handler(msg, process_add_user_step1)
|
||||
|
||||
def process_add_user_step1(message):
|
||||
username = message.text.strip()
|
||||
if username == "":
|
||||
bot.reply_to(message, "Username cannot be empty. Please enter a valid username.")
|
||||
return
|
||||
|
||||
command = f"python3 {CLI_PATH} list-users"
|
||||
result = run_cli_command(command)
|
||||
try:
|
||||
users = json.loads(result)
|
||||
if username in users:
|
||||
bot.reply_to(message, f"Username '{username}' already exists. Please choose a different username.")
|
||||
return
|
||||
except json.JSONDecodeError:
|
||||
bot.reply_to(message, "Error retrieving user list. Please try again later.")
|
||||
return
|
||||
|
||||
msg = bot.reply_to(message, "Enter traffic limit (GB):")
|
||||
bot.register_next_step_handler(msg, process_add_user_step2, username)
|
||||
|
||||
def process_add_user_step2(message, username):
|
||||
try:
|
||||
traffic_limit = int(message.text.strip())
|
||||
msg = bot.reply_to(message, "Enter expiration days:")
|
||||
bot.register_next_step_handler(msg, process_add_user_step3, username, traffic_limit)
|
||||
except ValueError:
|
||||
bot.reply_to(message, "Invalid traffic limit. Please enter a number.")
|
||||
|
||||
def process_add_user_step3(message, username, traffic_limit):
|
||||
try:
|
||||
expiration_days = int(message.text.strip())
|
||||
command = f"python3 {CLI_PATH} add-user -u {username} -t {traffic_limit} -e {expiration_days}"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
except ValueError:
|
||||
bot.reply_to(message, "Invalid expiration days. Please enter a number.")
|
||||
|
||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Show User')
|
||||
def show_user(message):
|
||||
msg = bot.reply_to(message, "Enter username:")
|
||||
bot.register_next_step_handler(msg, process_show_user)
|
||||
|
||||
def process_show_user(message):
|
||||
username = message.text.strip()
|
||||
command = f"python3 {CLI_PATH} get-user -u {username}"
|
||||
result = run_cli_command(command)
|
||||
|
||||
if "Error" in result or "Invalid" in result:
|
||||
bot.reply_to(message, result)
|
||||
else:
|
||||
user_details = json.loads(result)
|
||||
formatted_details = (
|
||||
f"Name: {username}\n"
|
||||
f"Traffic limit: {user_details['max_download_bytes'] / (1024 ** 3):.2f} GB\n"
|
||||
f"Days: {user_details['expiration_days']}\n"
|
||||
f"Account Creation: {user_details['account_creation_date']}\n"
|
||||
f"Blocked: {user_details['blocked']}"
|
||||
)
|
||||
|
||||
qr_command = f"python3 {CLI_PATH} show-user-uri -u {username} -ip 4"
|
||||
qr_result = run_cli_command(qr_command)
|
||||
|
||||
if "Error" in qr_result or "Invalid" in qr_result:
|
||||
bot.reply_to(message, qr_result)
|
||||
return
|
||||
uri_v4 = qr_result.split('\n')[-1].strip()
|
||||
|
||||
qr_v4 = qrcode.make(uri_v4)
|
||||
bio_v4 = io.BytesIO()
|
||||
qr_v4.save(bio_v4, 'PNG')
|
||||
bio_v4.seek(0)
|
||||
|
||||
markup = types.InlineKeyboardMarkup(row_width=2)
|
||||
markup.add(types.InlineKeyboardButton("Edit Username", callback_data=f"edit_username:{username}"),
|
||||
types.InlineKeyboardButton("Edit Traffic Limit", callback_data=f"edit_traffic:{username}"))
|
||||
markup.add(types.InlineKeyboardButton("Edit Expiration Days", callback_data=f"edit_expiration:{username}"),
|
||||
types.InlineKeyboardButton("Renew Password", callback_data=f"renew_password:{username}"))
|
||||
markup.add(types.InlineKeyboardButton("Renew Creation Date", callback_data=f"renew_creation:{username}"),
|
||||
types.InlineKeyboardButton("Block User", callback_data=f"block_user:{username}"))
|
||||
|
||||
bot.send_photo(message.chat.id, bio_v4, caption=f"User Details:\n{formatted_details}\n\nIPv4 URI: {uri_v4}", reply_markup=markup)
|
||||
|
||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Server Info')
|
||||
def server_info(message):
|
||||
command = f"python3 {CLI_PATH} server-info"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
|
||||
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_') or call.data.startswith('renew_') or call.data.startswith('block_'))
|
||||
def handle_edit_callback(call):
|
||||
action, username = call.data.split(':')
|
||||
if action == 'edit_username':
|
||||
msg = bot.send_message(call.message.chat.id, f"Enter new username for {username}:")
|
||||
bot.register_next_step_handler(msg, process_edit_username, username)
|
||||
elif action == 'edit_traffic':
|
||||
msg = bot.send_message(call.message.chat.id, f"Enter new traffic limit (GB) for {username}:")
|
||||
bot.register_next_step_handler(msg, process_edit_traffic, username)
|
||||
elif action == 'edit_expiration':
|
||||
msg = bot.send_message(call.message.chat.id, f"Enter new expiration days for {username}:")
|
||||
bot.register_next_step_handler(msg, process_edit_expiration, username)
|
||||
elif action == 'renew_password':
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} -rp"
|
||||
result = run_cli_command(command)
|
||||
bot.send_message(call.message.chat.id, result)
|
||||
elif action == 'renew_creation':
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} -rc"
|
||||
result = run_cli_command(command)
|
||||
bot.send_message(call.message.chat.id, result)
|
||||
elif action == 'block_user':
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton("True", callback_data=f"confirm_block:{username}:true"),
|
||||
types.InlineKeyboardButton("False", callback_data=f"confirm_block:{username}:false"))
|
||||
bot.send_message(call.message.chat.id, f"Set block status for {username}:", reply_markup=markup)
|
||||
|
||||
@bot.callback_query_handler(func=lambda call: call.data.startswith('confirm_block:'))
|
||||
def handle_block_confirmation(call):
|
||||
_, username, block_status = call.data.split(':')
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} {'-b' if block_status == 'true' else ''}"
|
||||
result = run_cli_command(command)
|
||||
bot.send_message(call.message.chat.id, result)
|
||||
|
||||
def process_edit_username(message, username):
|
||||
new_username = message.text.strip()
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} -nu {new_username}"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
|
||||
def process_edit_traffic(message, username):
|
||||
try:
|
||||
new_traffic_limit = int(message.text.strip())
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} -nt {new_traffic_limit}"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
except ValueError:
|
||||
bot.reply_to(message, "Invalid traffic limit. Please enter a number.")
|
||||
|
||||
def process_edit_expiration(message, username):
|
||||
try:
|
||||
new_expiration_days = int(message.text.strip())
|
||||
command = f"python3 {CLI_PATH} edit-user -u {username} -ne {new_expiration_days}"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
except ValueError:
|
||||
bot.reply_to(message, "Invalid expiration days. Please enter a number.")
|
||||
|
||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Delete User')
|
||||
def delete_user(message):
|
||||
msg = bot.reply_to(message, "Enter username:")
|
||||
bot.register_next_step_handler(msg, process_delete_user)
|
||||
|
||||
def process_delete_user(message):
|
||||
username = message.text.strip()
|
||||
command = f"python3 {CLI_PATH} remove-user -u {username}"
|
||||
result = run_cli_command(command)
|
||||
bot.reply_to(message, result)
|
||||
|
||||
@bot.inline_handler(lambda query: is_admin(query.from_user.id))
|
||||
def handle_inline_query(query):
|
||||
command = f"python3 {CLI_PATH} list-users"
|
||||
result = run_cli_command(command)
|
||||
try:
|
||||
users = json.loads(result)
|
||||
except json.JSONDecodeError:
|
||||
bot.answer_inline_query(query.id, results=[], switch_pm_text="Error retrieving users.", switch_pm_user_id=query.from_user.id)
|
||||
return
|
||||
|
||||
query_text = query.query.lower()
|
||||
results = []
|
||||
for username, details in users.items():
|
||||
if query_text in username.lower():
|
||||
title = f"Username: {username}"
|
||||
description = f"Traffic Limit: {details['max_download_bytes'] / (1024 ** 3):.2f} GB, Expiration Days: {details['expiration_days']}"
|
||||
results.append(types.InlineQueryResultArticle(
|
||||
id=username,
|
||||
title=title,
|
||||
description=description,
|
||||
input_message_content=types.InputTextMessageContent(
|
||||
message_text=f"Name: {username}\n"
|
||||
f"Traffic limit: {details['max_download_bytes'] / (1024 ** 3):.2f} GB\n"
|
||||
f"Days: {details['expiration_days']}\n"
|
||||
f"Account Creation: {details['account_creation_date']}\n"
|
||||
f"Blocked: {details['blocked']}"
|
||||
)
|
||||
))
|
||||
|
||||
bot.answer_inline_query(query.id, results)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bot.polling(none_stop=True)
|
||||
@ -19,4 +19,36 @@ get_system_info() {
|
||||
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 }')
|
||||
}
|
||||
}
|
||||
|
||||
version_greater_equal() {
|
||||
IFS='.' read -r -a local_version_parts <<< "$1"
|
||||
IFS='.' read -r -a latest_version_parts <<< "$2"
|
||||
|
||||
for ((i=0; i<${#local_version_parts[@]}; i++)); do
|
||||
if [[ -z ${latest_version_parts[i]} ]]; then
|
||||
latest_version_parts[i]=0
|
||||
fi
|
||||
|
||||
if ((10#${local_version_parts[i]} > 10#${latest_version_parts[i]})); then
|
||||
return 0
|
||||
elif ((10#${local_version_parts[i]} < 10#${latest_version_parts[i]})); then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_version() {
|
||||
local_version=$(cat /etc/hysteria/VERSION)
|
||||
latest_version=$(curl -s https://raw.githubusercontent.com/ReturnFI/Hysteria2/Dev/VERSION)
|
||||
|
||||
if version_greater_equal "$local_version" "$latest_version"; then
|
||||
echo -e "Panel Version: ${cyan}$local_version${NC}"
|
||||
else
|
||||
echo -e "Panel Version: ${cyan}$local_version${NC}"
|
||||
echo -e "Latest Version: ${cyan}$latest_version${NC}"
|
||||
echo -e "${red}Please update your panel.${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
79
menu.sh
79
menu.sh
@ -28,7 +28,11 @@ hysteria2_add_user_handler() {
|
||||
read -p "Enter the username: " username
|
||||
|
||||
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
|
||||
break
|
||||
if python3 $CLI_PATH get-user --username "$username" > /dev/null 2>&1; then
|
||||
echo -e "${red}Error:${NC} Username already exists. Please choose another username."
|
||||
else
|
||||
break
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
||||
fi
|
||||
@ -150,7 +154,7 @@ hysteria2_get_user_handler() {
|
||||
done
|
||||
|
||||
# Run the command and suppress error output
|
||||
if ! python3 "$CLI_PATH" get-user --username "$username" > /dev/null 2>&1; then
|
||||
if ! python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null; then
|
||||
echo -e "${red}Error:${NC} User '$username' not found."
|
||||
return 1
|
||||
fi
|
||||
@ -196,7 +200,7 @@ hysteria2_show_user_uri_handler() {
|
||||
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
|
||||
fi
|
||||
done
|
||||
python3 $CLI_PATH show-user-uri --username "$username"
|
||||
python3 $CLI_PATH show-user-uri --username "$username" -a -qr
|
||||
}
|
||||
|
||||
hysteria2_change_port_handler() {
|
||||
@ -238,6 +242,56 @@ warp_configure_handler() {
|
||||
fi
|
||||
}
|
||||
|
||||
telegram_bot_handler() {
|
||||
while true; do
|
||||
echo -e "${cyan}1.${NC} Start Telegram bot service"
|
||||
echo -e "${red}2.${NC} Stop Telegram bot service"
|
||||
echo "0. Back"
|
||||
read -p "Choose an option: " option
|
||||
|
||||
case $option in
|
||||
1)
|
||||
if systemctl is-active --quiet hysteria-bot.service; then
|
||||
echo "The hysteria-bot.service is already active."
|
||||
else
|
||||
while true; do
|
||||
read -p "Enter the Telegram bot token: " token
|
||||
if [ -z "$token" ]; then
|
||||
echo "Token cannot be empty. Please try again."
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
while true; do
|
||||
read -p "Enter the admin IDs (comma-separated): " admin_ids
|
||||
if [ -z "$admin_ids" ]; then
|
||||
echo "Admin IDs cannot be empty. Please try again."
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
python3 $CLI_PATH telegram -a start -t "$token" -aid "$admin_ids"
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
if ! systemctl is-active --quiet hysteria-bot.service; then
|
||||
echo "The hysteria-bot.service is already inactive."
|
||||
else
|
||||
python3 $CLI_PATH telegram -a stop
|
||||
fi
|
||||
;;
|
||||
0)
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option. Please try again."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Function to display the main menu
|
||||
display_main_menu() {
|
||||
clear
|
||||
@ -250,6 +304,9 @@ display_main_menu() {
|
||||
|
||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||
|
||||
check_version
|
||||
|
||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||
echo -e "${yellow} ☼ Main Menu ☼ ${NC}"
|
||||
|
||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||
@ -293,7 +350,7 @@ display_hysteria2_menu() {
|
||||
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}[6] ${NC}↝ List Users"
|
||||
echo -e "${cyan}[7] ${NC}↝ Check Traffic Status"
|
||||
echo -e "${cyan}[8] ${NC}↝ Show User URI"
|
||||
|
||||
@ -339,9 +396,10 @@ display_advance_menu() {
|
||||
echo -e "${green}[2] ${NC}↝ Install WARP"
|
||||
echo -e "${cyan}[3] ${NC}↝ Configure WARP"
|
||||
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
|
||||
echo -e "${cyan}[5] ${NC}↝ Change Port Hysteria2"
|
||||
echo -e "${cyan}[6] ${NC}↝ Update Core Hysteria2"
|
||||
echo -e "${red}[7] ${NC}↝ Uninstall Hysteria2"
|
||||
echo -e "${green}[5] ${NC}↝ Telegram Bot"
|
||||
echo -e "${cyan}[6] ${NC}↝ Change Port Hysteria2"
|
||||
echo -e "${cyan}[7] ${NC}↝ Update Core Hysteria2"
|
||||
echo -e "${red}[8] ${NC}↝ Uninstall Hysteria2"
|
||||
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
||||
@ -359,9 +417,10 @@ advance_menu() {
|
||||
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 ;;
|
||||
5) telegram_bot_handler ;;
|
||||
6) hysteria2_change_port_handler ;;
|
||||
7) python3 $CLI_PATH update-hysteria2 ;;
|
||||
8) python3 $CLI_PATH uninstall-hysteria2 ;;
|
||||
0) return ;;
|
||||
*) echo "Invalid option. Please try again." ;;
|
||||
esac
|
||||
|
||||
106
modify.py
106
modify.py
@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
users_file_path = '/etc/hysteria/users/users.json'
|
||||
|
||||
def colors():
|
||||
green='\033[0;32m'
|
||||
cyan='\033[0;36m'
|
||||
red='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
return green, cyan, red, NC
|
||||
|
||||
def load_users():
|
||||
with open(users_file_path, 'r') as file:
|
||||
return json.load(file)
|
||||
|
||||
def save_users(users):
|
||||
with open(users_file_path, 'w') as file:
|
||||
json.dump(users, file, indent=4)
|
||||
|
||||
def generate_password():
|
||||
result = subprocess.run(['pwgen', '-s', '32', '1'], capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
def list_users(users):
|
||||
green, cyan, red, NC = colors()
|
||||
print(f"{green}List of Users{NC}\n")
|
||||
for i, user in enumerate(users.keys(), start=1):
|
||||
print(f"{cyan}{i}. {user}{NC}")
|
||||
|
||||
def edit_user(users):
|
||||
green, cyan, red, NC = colors()
|
||||
list_users(users)
|
||||
try:
|
||||
choice = int(input(f"{green}Enter the number of the user to edit:{NC} "))
|
||||
username = list(users.keys())[choice - 1]
|
||||
except (ValueError, IndexError):
|
||||
print(f"{green}Invalid choice.{NC}")
|
||||
return
|
||||
|
||||
print(f"{green}Editing user: {cyan}{username}{NC}")
|
||||
|
||||
change_password = input(f"Change password? (current: {users[username]['password']}) [y/N]: ").lower() == 'y'
|
||||
if change_password:
|
||||
new_password = generate_password()
|
||||
users[username]['password'] = new_password
|
||||
print(f"{green}New password: {cyan}{new_password}{NC}")
|
||||
|
||||
while True:
|
||||
current_max_download_gb = users[username]['max_download_bytes'] // (1024 ** 3)
|
||||
max_download_gb = input(f"Enter new max download bytes in GB (current: {current_max_download_gb} GB, press Enter to keep current): ")
|
||||
if max_download_gb.strip() == '':
|
||||
break
|
||||
elif max_download_gb.isdigit():
|
||||
users[username]['max_download_bytes'] = int(max_download_gb) * (1024 ** 3)
|
||||
break
|
||||
else:
|
||||
print(f"{red}Invalid input. Please enter a valid number or press Enter to keep current.{NC}")
|
||||
|
||||
while True:
|
||||
expiration_days = input(f"Enter new expiration days (current: {users[username]['expiration_days']}, press Enter to keep current): ")
|
||||
if expiration_days.strip() == '':
|
||||
break
|
||||
elif expiration_days.isdigit():
|
||||
users[username]['expiration_days'] = int(expiration_days)
|
||||
break
|
||||
else:
|
||||
print(f"{red}Invalid input. Please enter a valid number or press Enter to keep current.{NC}")
|
||||
|
||||
blocked = input(f"Blocked? (current: {users[username]['blocked']}) [true/false]: ").lower()
|
||||
if blocked:
|
||||
users[username]['blocked'] = blocked == 'true'
|
||||
|
||||
change_date = input(f"Change account creation date to today? (current: {users[username]['account_creation_date']}) [y/N]: ").lower() == 'y'
|
||||
if change_date:
|
||||
users[username]['account_creation_date'] = datetime.today().strftime('%Y-%m-%d')
|
||||
|
||||
def main():
|
||||
green, cyan, red, NC = colors()
|
||||
|
||||
if not os.path.exists(users_file_path):
|
||||
print(f"{red}File {users_file_path} does not exist.{NC}")
|
||||
return
|
||||
|
||||
users = load_users()
|
||||
|
||||
while True:
|
||||
print(f"{green}1. Edit user{NC}")
|
||||
print(f"{red}2. Exit{NC}")
|
||||
choice = input(f"{NC}Enter your choice: {NC}")
|
||||
|
||||
if choice == "1":
|
||||
edit_user(users)
|
||||
save_users(users)
|
||||
elif choice == "2":
|
||||
print(f"{red}Exiting...{NC}")
|
||||
break
|
||||
else:
|
||||
print(f"{NC}Invalid choice. Please try again.{NC}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
pyTelegramBotAPI
|
||||
qrcode
|
||||
python-dotenv
|
||||
requests
|
||||
53
upgrade.sh
Normal file
53
upgrade.sh
Normal file
@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
|
||||
FILES=(
|
||||
"/etc/hysteria/ca.key"
|
||||
"/etc/hysteria/ca.crt"
|
||||
"/etc/hysteria/users.json"
|
||||
"/etc/hysteria/traffic_data.json"
|
||||
"/etc/hysteria/config.json"
|
||||
"/etc/hysteria/core/scripts/telegrambot/.env"
|
||||
)
|
||||
|
||||
echo "Backing up files to $TEMP_DIR"
|
||||
for FILE in "${FILES[@]}"; do
|
||||
cp "$FILE" "$TEMP_DIR"
|
||||
done
|
||||
|
||||
echo "Removing /etc/hysteria directory"
|
||||
rm -rf /etc/hysteria/
|
||||
|
||||
echo "Cloning Hysteria2 repository"
|
||||
git clone https://github.com/ReturnFI/Hysteria2 /etc/hysteria
|
||||
|
||||
echo "Downloading geosite.dat and geoip.dat"
|
||||
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
|
||||
|
||||
echo "Restoring backup files"
|
||||
for FILE in "${FILES[@]}"; do
|
||||
cp "$TEMP_DIR/$(basename $FILE)" "$FILE"
|
||||
done
|
||||
|
||||
echo "Setting ownership and permissions for ca.key and ca.crt"
|
||||
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
||||
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
|
||||
|
||||
echo "Restarting hysteria-server.service"
|
||||
systemctl restart hysteria-server.service
|
||||
|
||||
echo "Setting execute permissions for user.sh and kick.sh"
|
||||
chmod +x /etc/hysteria/core/scripts/hysteria2/user.sh
|
||||
chmod +x /etc/hysteria/core/scripts/hysteria2/kick.sh
|
||||
|
||||
echo "Checking hysteria-server.service status"
|
||||
if systemctl is-active --quiet hysteria-server.service; then
|
||||
echo "Upgrade completed successfully"
|
||||
else
|
||||
echo "Upgrade failed: hysteria-server.service is not active"
|
||||
fi
|
||||
cd /etc/hysteria
|
||||
chmod +x menu.sh
|
||||
./menu.sh
|
||||
Reference in New Issue
Block a user