19
core/cli.py
19
core/cli.py
@ -35,6 +35,7 @@ class Command(Enum):
|
|||||||
BACKUP_HYSTERIA = os.path.join(SCRIPT_DIR, 'hysteria2', 'backup.sh')
|
BACKUP_HYSTERIA = os.path.join(SCRIPT_DIR, 'hysteria2', 'backup.sh')
|
||||||
INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.sh')
|
INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.sh')
|
||||||
INSTALL_SINGBOX = os.path.join(SCRIPT_DIR, 'singbox', 'singbox_shell.sh')
|
INSTALL_SINGBOX = os.path.join(SCRIPT_DIR, 'singbox', 'singbox_shell.sh')
|
||||||
|
INSTALL_NORMALSUB = os.path.join(SCRIPT_DIR, 'normalsub', 'normalsub.sh')
|
||||||
INSTALL_TCP_BRUTAL = os.path.join(SCRIPT_DIR, 'tcp-brutal', 'install.sh')
|
INSTALL_TCP_BRUTAL = os.path.join(SCRIPT_DIR, 'tcp-brutal', 'install.sh')
|
||||||
INSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'install.sh')
|
INSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'install.sh')
|
||||||
UNINSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'uninstall.sh')
|
UNINSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'uninstall.sh')
|
||||||
@ -207,7 +208,8 @@ def remove_user(username: str):
|
|||||||
@click.option('--ipv', '-ip', type=click.IntRange(4, 6), default=4, help='IP version (4 or 6)')
|
@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')
|
@click.option('--all', '-a', is_flag=True, help='Show both IPv4 and IPv6 URIs and generate QR codes for both if requested')
|
||||||
@click.option('--singbox', '-s', is_flag=True, help='Generate Singbox sublink if Singbox service is active')
|
@click.option('--singbox', '-s', is_flag=True, help='Generate Singbox sublink if Singbox service is active')
|
||||||
def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool, singbox: bool):
|
@click.option('--normalsub', '-n', is_flag=True, help='Generate Normal sublink if normalsub service is active')
|
||||||
|
def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool, singbox: bool, normalsub: bool):
|
||||||
command_args = ['bash', Command.SHOW_USER_URI.value, '-u', username]
|
command_args = ['bash', Command.SHOW_USER_URI.value, '-u', username]
|
||||||
if qrcode:
|
if qrcode:
|
||||||
command_args.append('-qr')
|
command_args.append('-qr')
|
||||||
@ -217,6 +219,8 @@ def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool, singbox: boo
|
|||||||
command_args.extend(['-ip', str(ipv)])
|
command_args.extend(['-ip', str(ipv)])
|
||||||
if singbox:
|
if singbox:
|
||||||
command_args.append('-s')
|
command_args.append('-s')
|
||||||
|
if normalsub:
|
||||||
|
command_args.append('-n')
|
||||||
|
|
||||||
run_cmd(command_args)
|
run_cmd(command_args)
|
||||||
|
|
||||||
@ -325,6 +329,19 @@ def singbox(action: str, domain: str, port: int):
|
|||||||
elif action == 'stop':
|
elif action == 'stop':
|
||||||
run_cmd(['bash', Command.INSTALL_SINGBOX.value, 'stop'])
|
run_cmd(['bash', Command.INSTALL_SINGBOX.value, 'stop'])
|
||||||
|
|
||||||
|
@cli.command('normal-sub')
|
||||||
|
@click.option('--action', '-a', required=True, help='Action to perform: start or stop', type=click.Choice(['start', 'stop'], case_sensitive=False))
|
||||||
|
@click.option('--domain', '-d', required=False, help='Domain name for SSL', type=str)
|
||||||
|
@click.option('--port', '-p', required=False, help='Port number for NormalSub service', type=int)
|
||||||
|
def normalsub(action: str, domain: str, port: int):
|
||||||
|
if action == 'start':
|
||||||
|
if not domain or not port:
|
||||||
|
click.echo("Error: Both --domain and --port are required for the start action.")
|
||||||
|
return
|
||||||
|
run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'start', domain, str(port)])
|
||||||
|
elif action == 'stop':
|
||||||
|
run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'stop'])
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,18 @@ get_singbox_domain_and_port() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_normalsub_domain_and_port() {
|
||||||
|
if [ -f "$NORMALSUB_ENV" ]; then
|
||||||
|
local domain port
|
||||||
|
domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2)
|
||||||
|
port=$(grep -E '^HYSTERIA_PORT=' "$NORMALSUB_ENV" | cut -d'=' -f2)
|
||||||
|
echo "$domain" "$port"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
show_uri() {
|
show_uri() {
|
||||||
if [ -f "$USERS_FILE" ]; then
|
if [ -f "$USERS_FILE" ]; then
|
||||||
if systemctl is-active --quiet hysteria-server.service; then
|
if systemctl is-active --quiet hysteria-server.service; then
|
||||||
@ -22,6 +34,7 @@ show_uri() {
|
|||||||
local ip_version=4
|
local ip_version=4
|
||||||
local show_all=false
|
local show_all=false
|
||||||
local generate_singbox=false
|
local generate_singbox=false
|
||||||
|
local generate_normalsub=false
|
||||||
|
|
||||||
load_hysteria2_env
|
load_hysteria2_env
|
||||||
|
|
||||||
@ -32,6 +45,7 @@ show_uri() {
|
|||||||
-ip) ip_version="$2"; shift ;;
|
-ip) ip_version="$2"; shift ;;
|
||||||
-a|--all) show_all=true ;;
|
-a|--all) show_all=true ;;
|
||||||
-s|--singbox) generate_singbox=true ;;
|
-s|--singbox) generate_singbox=true ;;
|
||||||
|
-n|--normalsub) generate_normalsub=true ;;
|
||||||
*) echo "Unknown parameter passed: $1"; exit 1 ;;
|
*) echo "Unknown parameter passed: $1"; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
@ -104,6 +118,12 @@ show_uri() {
|
|||||||
echo -e "\nSingbox Sublink:\nhttps://$domain:$port/sub/singbox/$username/$ip_version#$username\n"
|
echo -e "\nSingbox Sublink:\nhttps://$domain:$port/sub/singbox/$username/$ip_version#$username\n"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
if [ "$generate_normalsub" = true ] && systemctl is-active --quiet normalsub.service; then
|
||||||
|
read -r domain port < <(get_normalsub_domain_and_port)
|
||||||
|
if [ -n "$domain" ] && [ -n "$port" ]; then
|
||||||
|
echo -e "\nNormal-SUB Sublink:\nhttps://$domain:$port/sub/normal/$username#Hysteria2\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Invalid username. Please try again."
|
echo "Invalid username. Please try again."
|
||||||
fi
|
fi
|
||||||
|
|||||||
96
core/scripts/normalsub/normalsub.py
Normal file
96
core/scripts/normalsub/normalsub.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import os
|
||||||
|
import ssl
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp.web_middlewares import middleware
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
CERTFILE = os.getenv('HYSTERIA_CERTFILE')
|
||||||
|
KEYFILE = os.getenv('HYSTERIA_KEYFILE')
|
||||||
|
PORT = int(os.getenv('HYSTERIA_PORT', '3325'))
|
||||||
|
|
||||||
|
RATE_LIMIT = 100
|
||||||
|
RATE_LIMIT_WINDOW = 60
|
||||||
|
|
||||||
|
rate_limit_store = {}
|
||||||
|
|
||||||
|
@middleware
|
||||||
|
async def rate_limit_middleware(request, handler):
|
||||||
|
client_ip = request.headers.get('X-Forwarded-For', request.remote)
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
if client_ip in rate_limit_store:
|
||||||
|
requests, last_request_time = rate_limit_store[client_ip]
|
||||||
|
if current_time - last_request_time < RATE_LIMIT_WINDOW:
|
||||||
|
if requests >= RATE_LIMIT:
|
||||||
|
return web.Response(status=429, text="Rate limit exceeded.")
|
||||||
|
if current_time - last_request_time >= RATE_LIMIT_WINDOW:
|
||||||
|
rate_limit_store[client_ip] = (1, current_time)
|
||||||
|
else:
|
||||||
|
rate_limit_store[client_ip] = (requests + 1, last_request_time)
|
||||||
|
else:
|
||||||
|
rate_limit_store[client_ip] = (1, current_time)
|
||||||
|
|
||||||
|
return await handler(request)
|
||||||
|
|
||||||
|
def sanitize_input(value, pattern):
|
||||||
|
if not re.match(pattern, value):
|
||||||
|
raise ValueError(f"Invalid value: {value}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
async def handle(request):
|
||||||
|
try:
|
||||||
|
username = sanitize_input(request.match_info.get('username', ''), r'^[a-zA-Z0-9_-]+$')
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
return web.Response(status=400, text="Error: Missing 'username' parameter.")
|
||||||
|
|
||||||
|
uri_output = get_user_uri(username)
|
||||||
|
return web.Response(text=uri_output, content_type='text/plain')
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
return web.Response(status=400, text=f"Error: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Internal Server Error: {str(e)}")
|
||||||
|
return web.Response(status=500, text="Error: Internal server error.")
|
||||||
|
|
||||||
|
def get_user_uri(username):
|
||||||
|
try:
|
||||||
|
command = [
|
||||||
|
'python3',
|
||||||
|
'/etc/hysteria/core/cli.py',
|
||||||
|
'show-user-uri',
|
||||||
|
'-u', username,
|
||||||
|
'-a'
|
||||||
|
]
|
||||||
|
safe_command = [shlex.quote(arg) for arg in command]
|
||||||
|
output = subprocess.check_output(safe_command).decode().strip()
|
||||||
|
output = re.sub(r'IPv4:\s*', '', output)
|
||||||
|
output = re.sub(r'IPv6:\s*', '', output)
|
||||||
|
|
||||||
|
return output
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
raise RuntimeError("Failed to get URI.")
|
||||||
|
|
||||||
|
async def handle_404(request):
|
||||||
|
print(f"404 Not Found: {request.path}")
|
||||||
|
return web.Response(status=404, text="Not Found")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = web.Application(middlewares=[rate_limit_middleware])
|
||||||
|
|
||||||
|
app.add_routes([web.get('/sub/normal/{username}', handle)])
|
||||||
|
app.router.add_route('*', '/sub/normal/{tail:.*}', handle_404)
|
||||||
|
|
||||||
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
ssl_context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE)
|
||||||
|
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||||
|
ssl_context.set_ciphers('AES256+EECDH:AES256+EDH')
|
||||||
|
|
||||||
|
web.run_app(app, port=PORT, ssl_context=ssl_context)
|
||||||
109
core/scripts/normalsub/normalsub.sh
Normal file
109
core/scripts/normalsub/normalsub.sh
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
source /etc/hysteria/core/scripts/utils.sh
|
||||||
|
define_colors
|
||||||
|
|
||||||
|
install_dependencies() {
|
||||||
|
echo "Installing necessary dependencies..."
|
||||||
|
apt-get install certbot -y > /dev/null 2>&1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${red}Error: Failed to install certbot. ${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${green}Certbot installed successfully. ${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_env_file() {
|
||||||
|
local domain=$1
|
||||||
|
local port=$2
|
||||||
|
local cert_dir="/etc/letsencrypt/live/$domain"
|
||||||
|
|
||||||
|
cat <<EOL > /etc/hysteria/core/scripts/normalsub/.env
|
||||||
|
HYSTERIA_DOMAIN=$domain
|
||||||
|
HYSTERIA_PORT=$port
|
||||||
|
HYSTERIA_CERTFILE=$cert_dir/fullchain.pem
|
||||||
|
HYSTERIA_KEYFILE=$cert_dir/privkey.pem
|
||||||
|
EOL
|
||||||
|
}
|
||||||
|
|
||||||
|
create_service_file() {
|
||||||
|
cat <<EOL > /etc/systemd/system/normalsub.service
|
||||||
|
[Unit]
|
||||||
|
Description=normalsub Python Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/bash -c 'source /etc/hysteria/hysteria2_venv/bin/activate && /etc/hysteria/hysteria2_venv/bin/python /etc/hysteria/core/scripts/normalsub/normalsub.py'
|
||||||
|
WorkingDirectory=/etc/hysteria/core/scripts/normalsub
|
||||||
|
EnvironmentFile=/etc/hysteria/core/scripts/normalsub/.env
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOL
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
local domain=$1
|
||||||
|
local port=$2
|
||||||
|
|
||||||
|
if systemctl is-active --quiet normalsub.service; then
|
||||||
|
echo "The normalsub.service is already running."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_dependencies
|
||||||
|
|
||||||
|
echo "Generating SSL certificates for $domain..."
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d "$domain"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${red}Error: Failed to generate SSL certificates. ${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
update_env_file "$domain" "$port"
|
||||||
|
create_service_file
|
||||||
|
chown -R hysteria:hysteria "/etc/letsencrypt/live/$domain"
|
||||||
|
chown -R hysteria:hysteria /etc/hysteria/core/scripts/normalsub
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable normalsub.service > /dev/null 2>&1
|
||||||
|
systemctl start normalsub.service > /dev/null 2>&1
|
||||||
|
systemctl daemon-reload > /dev/null 2>&1
|
||||||
|
|
||||||
|
if systemctl is-active --quiet normalsub.service; then
|
||||||
|
echo -e "${green}normalsub service setup completed. The service is now running on port $port. ${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${red}normalsub setup completed. The service failed to start. ${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
systemctl stop normalsub.service > /dev/null 2>&1
|
||||||
|
systemctl disable normalsub.service > /dev/null 2>&1
|
||||||
|
systemctl daemon-reload > /dev/null 2>&1
|
||||||
|
|
||||||
|
rm -f /etc/hysteria/core/scripts/normalsub/.env
|
||||||
|
echo -e "\n"
|
||||||
|
|
||||||
|
echo -e "${yellow}normalsub service stopped and disabled. .env file removed. ${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
if [ -z "$2" ] || [ -z "$3" ]; then
|
||||||
|
echo -e "${red}Usage: $0 start <DOMAIN> <PORT> ${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
start_service "$2" "$3"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_service
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Usage: $0 {start|stop} <DOMAIN> <PORT> ${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
define_colors
|
||||||
@ -5,4 +5,5 @@ CONFIG_FILE="/etc/hysteria/config.json"
|
|||||||
CONFIG_ENV="/etc/hysteria/.configs.env"
|
CONFIG_ENV="/etc/hysteria/.configs.env"
|
||||||
TELEGRAM_ENV="/etc/hysteria/core/scripts/telegrambot/.env"
|
TELEGRAM_ENV="/etc/hysteria/core/scripts/telegrambot/.env"
|
||||||
SINGBOX_ENV="/etc/hysteria/core/scripts/singbox/.env"
|
SINGBOX_ENV="/etc/hysteria/core/scripts/singbox/.env"
|
||||||
|
NORMALSUB_ENV="/etc/hysteria/core/scripts/normalsub/.env"
|
||||||
ONLINE_API_URL="http://127.0.0.1:25413/online"
|
ONLINE_API_URL="http://127.0.0.1:25413/online"
|
||||||
|
|||||||
@ -90,7 +90,7 @@ def process_add_user_step3(message, username, traffic_limit):
|
|||||||
lower_username = username.lower()
|
lower_username = username.lower()
|
||||||
command = f"python3 {CLI_PATH} add-user -u {username} -t {traffic_limit} -e {expiration_days}"
|
command = f"python3 {CLI_PATH} add-user -u {username} -t {traffic_limit} -e {expiration_days}"
|
||||||
result = run_cli_command(command)
|
result = run_cli_command(command)
|
||||||
|
bot.send_chat_action(message.chat.id, 'typing')
|
||||||
qr_command = f"python3 {CLI_PATH} show-user-uri -u {lower_username} -ip 4"
|
qr_command = f"python3 {CLI_PATH} show-user-uri -u {lower_username} -ip 4"
|
||||||
qr_result = run_cli_command(qr_command).replace("IPv4:\n", "").strip()
|
qr_result = run_cli_command(qr_command).replace("IPv4:\n", "").strip()
|
||||||
|
|
||||||
@ -115,6 +115,7 @@ def show_user(message):
|
|||||||
|
|
||||||
def process_show_user(message):
|
def process_show_user(message):
|
||||||
username = message.text.strip().lower()
|
username = message.text.strip().lower()
|
||||||
|
bot.send_chat_action(message.chat.id, 'typing')
|
||||||
command = f"python3 {CLI_PATH} list-users"
|
command = f"python3 {CLI_PATH} list-users"
|
||||||
result = run_cli_command(command)
|
result = run_cli_command(command)
|
||||||
|
|
||||||
@ -167,17 +168,31 @@ def process_show_user(message):
|
|||||||
f"{traffic_message}"
|
f"{traffic_message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
combined_command = f"python3 {CLI_PATH} show-user-uri -u {actual_username} -ip 4 -s"
|
combined_command = f"python3 {CLI_PATH} show-user-uri -u {actual_username} -ip 4 -s -n"
|
||||||
combined_result = run_cli_command(combined_command)
|
combined_result = run_cli_command(combined_command)
|
||||||
|
|
||||||
if "Error" in combined_result or "Invalid" in combined_result:
|
if "Error" in combined_result or "Invalid" in combined_result:
|
||||||
bot.reply_to(message, combined_result)
|
bot.reply_to(message, combined_result)
|
||||||
return
|
return
|
||||||
|
|
||||||
result_lines = combined_result.split('\n')
|
result_lines = combined_result.strip().split('\n')
|
||||||
uri_v4 = result_lines[1].strip()
|
|
||||||
|
|
||||||
singbox_sublink = result_lines[-1].strip() if "https://" in result_lines[-1] else None
|
uri_v4 = ""
|
||||||
|
singbox_sublink = ""
|
||||||
|
normal_sub_sublink = ""
|
||||||
|
|
||||||
|
for line in result_lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("hy2://"):
|
||||||
|
uri_v4 = line
|
||||||
|
elif line.startswith("Singbox Sublink:"):
|
||||||
|
singbox_sublink = result_lines[result_lines.index(line) + 1].strip()
|
||||||
|
elif line.startswith("Normal-SUB Sublink:"):
|
||||||
|
normal_sub_sublink = result_lines[result_lines.index(line) + 1].strip()
|
||||||
|
|
||||||
|
if not uri_v4:
|
||||||
|
bot.reply_to(message, "No valid URI found.")
|
||||||
|
return
|
||||||
|
|
||||||
qr_v4 = qrcode.make(uri_v4)
|
qr_v4 = qrcode.make(uri_v4)
|
||||||
bio_v4 = io.BytesIO()
|
bio_v4 = io.BytesIO()
|
||||||
@ -196,7 +211,9 @@ def process_show_user(message):
|
|||||||
|
|
||||||
caption = f"{formatted_details}\n\n**IPv4 URI:**\n\n`{uri_v4}`"
|
caption = f"{formatted_details}\n\n**IPv4 URI:**\n\n`{uri_v4}`"
|
||||||
if singbox_sublink:
|
if singbox_sublink:
|
||||||
caption += f"\n\n\n**SingBox SUB:**\n{singbox_sublink}"
|
caption += f"\n\n**SingBox SUB:**\n{singbox_sublink}"
|
||||||
|
if normal_sub_sublink:
|
||||||
|
caption += f"\n\n**Normal SUB:**\n{normal_sub_sublink}"
|
||||||
|
|
||||||
bot.send_photo(
|
bot.send_photo(
|
||||||
message.chat.id,
|
message.chat.id,
|
||||||
@ -206,10 +223,12 @@ def process_show_user(message):
|
|||||||
parse_mode="Markdown"
|
parse_mode="Markdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Server Info')
|
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Server Info')
|
||||||
def server_info(message):
|
def server_info(message):
|
||||||
command = f"python3 {CLI_PATH} server-info"
|
command = f"python3 {CLI_PATH} server-info"
|
||||||
result = run_cli_command(command)
|
result = run_cli_command(command)
|
||||||
|
bot.send_chat_action(message.chat.id, 'typing')
|
||||||
bot.reply_to(message, result)
|
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_') or call.data.startswith('reset_') or call.data.startswith('ipv6_'))
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_') or call.data.startswith('renew_') or call.data.startswith('block_') or call.data.startswith('reset_') or call.data.startswith('ipv6_'))
|
||||||
@ -306,13 +325,16 @@ def process_delete_user(message):
|
|||||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Backup Server')
|
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Backup Server')
|
||||||
def backup_server(message):
|
def backup_server(message):
|
||||||
bot.reply_to(message, "Starting backup. This may take a few moments...")
|
bot.reply_to(message, "Starting backup. This may take a few moments...")
|
||||||
|
bot.send_chat_action(message.chat.id, 'typing')
|
||||||
|
|
||||||
backup_command = f"python3 {CLI_PATH} backup-hysteria"
|
backup_command = f"python3 {CLI_PATH} backup-hysteria"
|
||||||
result = run_cli_command(backup_command)
|
result = run_cli_command(backup_command)
|
||||||
|
|
||||||
if "Error" in result:
|
if "Error" in result:
|
||||||
bot.reply_to(message, f"Backup failed: {result}")
|
bot.reply_to(message, f"Backup failed: {result}")
|
||||||
return
|
else:
|
||||||
|
bot.reply_to(message, "Backup completed successfully!")
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
files = [f for f in os.listdir(BACKUP_DIRECTORY) if f.endswith('.zip')]
|
files = [f for f in os.listdir(BACKUP_DIRECTORY) if f.endswith('.zip')]
|
||||||
|
|||||||
70
menu.sh
70
menu.sh
@ -413,6 +413,58 @@ singbox_handler() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalsub_handler() {
|
||||||
|
while true; do
|
||||||
|
echo -e "${cyan}1.${NC} Start Normal-Sub service"
|
||||||
|
echo -e "${red}2.${NC} Stop Normal-Sub service"
|
||||||
|
echo "0. Back"
|
||||||
|
read -p "Choose an option: " option
|
||||||
|
|
||||||
|
case $option in
|
||||||
|
1)
|
||||||
|
if systemctl is-active --quiet normalsub.service; then
|
||||||
|
echo "The normalsub.service is already active."
|
||||||
|
else
|
||||||
|
while true; do
|
||||||
|
read -e -p "Enter the domain name for the SSL certificate: " domain
|
||||||
|
if [ -z "$domain" ]; then
|
||||||
|
echo "Domain name cannot be empty. Please try again."
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -e -p "Enter the port number for the service: " port
|
||||||
|
if [ -z "$port" ]; then
|
||||||
|
echo "Port number cannot be empty. Please try again."
|
||||||
|
elif ! [[ "$port" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "Port must be a number. Please try again."
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 $CLI_PATH normal-sub -a start -d "$domain" -p "$port"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
if ! systemctl is-active --quiet normalsub.service; then
|
||||||
|
echo "The normalsub.service is already inactive."
|
||||||
|
else
|
||||||
|
python3 $CLI_PATH normal-sub -a stop
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
0)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid option. Please try again."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Function to display the main menu
|
# Function to display the main menu
|
||||||
display_main_menu() {
|
display_main_menu() {
|
||||||
clear
|
clear
|
||||||
@ -523,10 +575,11 @@ display_advance_menu() {
|
|||||||
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
|
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
|
||||||
echo -e "${green}[5] ${NC}↝ Telegram Bot"
|
echo -e "${green}[5] ${NC}↝ Telegram Bot"
|
||||||
echo -e "${green}[6] ${NC}↝ SingBox SubLink"
|
echo -e "${green}[6] ${NC}↝ SingBox SubLink"
|
||||||
echo -e "${cyan}[7] ${NC}↝ Change Port Hysteria2"
|
echo -e "${green}[7] ${NC}↝ Normal-SUB SubLink"
|
||||||
echo -e "${cyan}[8] ${NC}↝ Change SNI Hysteria2"
|
echo -e "${cyan}[8] ${NC}↝ Change Port Hysteria2"
|
||||||
echo -e "${cyan}[9] ${NC}↝ Update Core Hysteria2"
|
echo -e "${cyan}[9] ${NC}↝ Change SNI Hysteria2"
|
||||||
echo -e "${red}[10] ${NC}↝ Uninstall Hysteria2"
|
echo -e "${cyan}[10] ${NC}↝ Update Core Hysteria2"
|
||||||
|
echo -e "${red}[11] ${NC}↝ Uninstall Hysteria2"
|
||||||
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
||||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||||
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
||||||
@ -546,10 +599,11 @@ advance_menu() {
|
|||||||
4) python3 $CLI_PATH uninstall-warp ;;
|
4) python3 $CLI_PATH uninstall-warp ;;
|
||||||
5) telegram_bot_handler ;;
|
5) telegram_bot_handler ;;
|
||||||
6) singbox_handler ;;
|
6) singbox_handler ;;
|
||||||
7) hysteria2_change_port_handler ;;
|
7) normalsub_handler ;;
|
||||||
8) hysteria2_change_sni_handler ;;
|
8) hysteria2_change_port_handler ;;
|
||||||
9) python3 $CLI_PATH update-hysteria2 ;;
|
9) hysteria2_change_sni_handler ;;
|
||||||
10) python3 $CLI_PATH uninstall-hysteria2 ;;
|
10) python3 $CLI_PATH update-hysteria2 ;;
|
||||||
|
11) python3 $CLI_PATH uninstall-hysteria2 ;;
|
||||||
0) return ;;
|
0) return ;;
|
||||||
*) echo "Invalid option. Please try again." ;;
|
*) echo "Invalid option. Please try again." ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -16,6 +16,7 @@ FILES=(
|
|||||||
"/etc/hysteria/config.json"
|
"/etc/hysteria/config.json"
|
||||||
"/etc/hysteria/core/scripts/telegrambot/.env"
|
"/etc/hysteria/core/scripts/telegrambot/.env"
|
||||||
"/etc/hysteria/core/scripts/singbox/.env"
|
"/etc/hysteria/core/scripts/singbox/.env"
|
||||||
|
"/etc/hysteria/core/scripts/normalsub/.env"
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Backing up files to $TEMP_DIR"
|
echo "Backing up files to $TEMP_DIR"
|
||||||
|
|||||||
Reference in New Issue
Block a user