196
Hysteria2_CLI_Usage.md
Normal file
196
Hysteria2_CLI_Usage.md
Normal file
@ -0,0 +1,196 @@
|
||||
# Hysteria2 CLI Tool
|
||||
|
||||
## Overview
|
||||
|
||||
The Hysteria2 CLI Tool is a command-line utility for managing various aspects of Hysteria2 and related services on your system. It allows you to install, configure, and manage Hysteria2, as well as other services like Telegram bot, Singbox SubLink, TCP Brutal, and WARP.
|
||||
|
||||
## Commands:
|
||||
|
||||
### Hysteria2 Management
|
||||
|
||||
- **Install Hysteria2**
|
||||
|
||||
```sh
|
||||
cli.py install-hysteria2 --port <port>
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--port, -p`: New port for Hysteria2 (required)
|
||||
|
||||
- **Uninstall Hysteria2**
|
||||
|
||||
```sh
|
||||
cli.py uninstall-hysteria2
|
||||
```
|
||||
|
||||
- **Update Hysteria2**
|
||||
|
||||
```sh
|
||||
cli.py update-hysteria2
|
||||
```
|
||||
|
||||
- **Restart Hysteria2**
|
||||
|
||||
```sh
|
||||
cli.py restart-hysteria2
|
||||
```
|
||||
|
||||
- **Change Hysteria2 Port**
|
||||
|
||||
```sh
|
||||
cli.py change-hysteria2-port --port <port>
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--port, -p`: New port for Hysteria2 (required)
|
||||
|
||||
### User Management
|
||||
|
||||
- **Get User Information**
|
||||
|
||||
```sh
|
||||
cli.py get-user --username <username> [--no-traffic]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username for the user (required)
|
||||
- `--no-traffic, -t`: Do not display traffic information (optional)
|
||||
|
||||
- **Add User**
|
||||
|
||||
```sh
|
||||
cli.py add-user --username <username> --traffic-limit <limit> --expiration-days <days> [--password <password>] [--creation-date <date>]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username for the new user (required)
|
||||
- `--traffic-limit, -t`: Traffic limit in GB (required)
|
||||
- `--expiration-days, -e`: Expiration days (required)
|
||||
- `--password, -p`: Password (optional; will be generated if not provided)
|
||||
- `--creation-date, -c`: Creation date (optional; defaults to current date)
|
||||
|
||||
- **Edit User**
|
||||
|
||||
```sh
|
||||
cli.py edit-user --username <username> [--new-username <new-username>] [--new-traffic-limit <limit>] [--new-expiration-days <days>] [--renew-password] [--renew-creation-date] [--blocked]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username to edit (required)
|
||||
- `--new-username, -nu`: New username (optional)
|
||||
- `--new-traffic-limit, -nt`: New traffic limit in GB (optional)
|
||||
- `--new-expiration-days, -ne`: New expiration days (optional)
|
||||
- `--renew-password, -rp`: Renew password (optional)
|
||||
- `--renew-creation-date, -rc`: Renew creation date (optional)
|
||||
- `--blocked, -b`: Block the user (optional)
|
||||
|
||||
- **Reset User**
|
||||
|
||||
```sh
|
||||
cli.py reset-user --username <username>
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username to reset (required)
|
||||
|
||||
- **Remove User**
|
||||
|
||||
```sh
|
||||
cli.py remove-user --username <username>
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username to remove (required)
|
||||
|
||||
- **Show User URI**
|
||||
|
||||
```sh
|
||||
cli.py show-user-uri --username <username> [--qrcode] [--ipv <4|6>] [--all] [--singbox]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--username, -u`: Username for the user (required)
|
||||
- `--qrcode, -qr`: Generate QR code for the URI (optional)
|
||||
- `--ipv, -ip`: IP version (4 or 6; default is 4)
|
||||
- `--all, -a`: Show both IPv4 and IPv6 URIs (optional)
|
||||
- `--singbox, -s`: Generate Singbox sublink if Singbox service is active (optional)
|
||||
|
||||
- **List Users**
|
||||
|
||||
```sh
|
||||
cli.py list-users
|
||||
```
|
||||
|
||||
- **Server Info**
|
||||
|
||||
```sh
|
||||
cli.py server-info
|
||||
```
|
||||
|
||||
### Advanced Commands
|
||||
|
||||
- **Install TCP Brutal**
|
||||
|
||||
```sh
|
||||
cli.py install-tcp-brutal
|
||||
```
|
||||
|
||||
- **Install WARP**
|
||||
|
||||
```sh
|
||||
cli.py install-warp
|
||||
```
|
||||
|
||||
- **Uninstall WARP**
|
||||
|
||||
```sh
|
||||
cli.py uninstall-warp
|
||||
```
|
||||
|
||||
- **Configure WARP**
|
||||
|
||||
```sh
|
||||
cli.py configure-warp [--all] [--popular-sites] [--domestic-sites] [--block-adult-sites]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--all, -a`: Use WARP for all connections (optional)
|
||||
- `--popular-sites, -p`: Use WARP for popular sites (optional)
|
||||
- `--domestic-sites, -d`: Use WARP for domestic sites (optional)
|
||||
- `--block-adult-sites, -x`: Block adult content (optional)
|
||||
|
||||
- **Telegram Bot Management**
|
||||
|
||||
```sh
|
||||
cli.py telegram --action <start|stop> [--token <token>] [--adminid <adminid>]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--action, -a`: Action to perform: `start` or `stop` (required)
|
||||
- `--token, -t`: Token for the bot (required for `start`)
|
||||
- `--adminid, -aid`: Telegram admin IDs (required for `start`)
|
||||
|
||||
- **Singbox SubLink**
|
||||
|
||||
```sh
|
||||
cli.py singbox --action <start|stop> [--domain <domain>] [--port <port>]
|
||||
```
|
||||
|
||||
*Options:*
|
||||
- `--action, -a`: Action to perform: `start` or `stop` (required)
|
||||
- `--domain, -d`: Domain name for SSL (required for `start`)
|
||||
- `--port, -p`: Port number for Singbox service (required for `start`)
|
||||
|
||||
## Debugging
|
||||
|
||||
To enable debugging, set the `DEBUG` variable to `True` in the script:
|
||||
|
||||
```python
|
||||
DEBUG = True
|
||||
```
|
||||
|
||||
This will print the commands being executed.
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to contribute by creating issues or submitting pull requests on the [GitHub repository](https://github.com/ReturnFI/Hysteria2).
|
||||
85
README-fa.md
85
README-fa.md
@ -1,17 +1,20 @@
|
||||
### اسکریپت مدیریتی Hysteria2
|
||||
<div dir="rtl">
|
||||
|
||||
این اسکریپت شل یک رابط کاربری مبتنی بر منو فراهم میکند برای مدیریت Hysteria2 میباشد.
|
||||
# اسکریپت مدیریت Hysteria2
|
||||
|
||||
این اسکریپت شامل گزینههای نصب، پیکربندی، به روزرسانی و حذف Hysteria2 است، همچنین مدیریت کاربران، پورتها، وضعیت ترافیک و ادغام با ابزارهای دیگر مانند TCP Brutal و WARP.
|
||||
این اسکریپت یک رابط کاربری جامع مبتنی بر منو را برای مدیریت سرور Hysteria2، حسابهای کاربری و خدمات مختلف فراهم میکند.
|
||||
|
||||
این اسکریپت از نصب، مدیریت کاربران، نظارت بر ترافیک و ادغام با ابزارهای اضافی مانند وارپ ، ساب لینک سینگ باکس و ربات تلگرام پشتیبانی میکند.
|
||||
|
||||
### دستور نصب:
|
||||
|
||||
```shell
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh)
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/install.sh)
|
||||
```
|
||||
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دوباره دستور نصب نیست.
|
||||
پس از نصب، فقط از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید.
|
||||
|
||||
### دستور آپدیت:
|
||||
نیازی به اجرای مجدد دستور نصب نیست.
|
||||
|
||||
### دستور ارتقاء:
|
||||
```shell
|
||||
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh)
|
||||
```
|
||||
@ -21,33 +24,63 @@ bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh
|
||||
<img src="https://github.com/ReturnFI/Hysteria2/assets/151555003/b1c7ab9f-7887-46fd-8e13-a7bfe9bf5990" width="500" height="250">
|
||||
<p/>
|
||||
|
||||
## ویژگیها Hysteria2:
|
||||
## ویژگیها:
|
||||
|
||||
- نصب و پیکربندی: نصب و پیکربندی سرور Hysteria2.
|
||||
- افزودن کاربر: ایجاد کاربر جدید برای دسترسی به Hysteria2.
|
||||
- نمایش URI: نمایش URI اتصال و کد QR برای کاربران موجود.
|
||||
- بررسی وضعیت ترافیک: نظارت بر اطلاعات ترافیک به صورت زمان واقعی برای هر کاربر.
|
||||
- حذف کاربر: حذف یک کاربر از پیکربندی Hysteria2.
|
||||
- تغییر پورت: تغییر پورت گوش دادن برای سرور Hysteria2.
|
||||
- به روزرسانی هسته: به روزرسانی Hysteria2 به آخرین نسخه موجود.
|
||||
- حذف نصب Hysteria2: حذف سرور Hysteria2 و پیکربندی آن.
|
||||
**نصب و پیکربندی Hysteria2:**
|
||||
|
||||
- نصب و پیکربندی Hysteria2 روی سرور خود.
|
||||
- مدیریت حسابهای کاربری (افزودن، ویرایش، بازنشانی، حذف، لیست).
|
||||
- نظارت بر ترافیک و نمایش URIهای کاربر.
|
||||
|
||||
**گزینههای پیشرفته:**
|
||||
|
||||
- نصب و مدیریت خدمات اضافی مانند WARP و TCP Brutal.
|
||||
- شروع/توقف خدمات Singbox SubLink و ربات تلگرام.
|
||||
- تغییر شماره پورت برای Hysteria2.
|
||||
- بهروزرسانی یا حذف نصب Hysteria2.
|
||||
|
||||
**منوهای تعاملی:**
|
||||
|
||||
- رابط کاربری مبتنی بر منو برای راحتی در جابجایی و مدیریت.
|
||||
- اعتبارسنجی برای ورودیهای کاربر و بررسیهای سیستمی برای جلوگیری از پیکربندیهای نادرست.
|
||||
|
||||
## منوی اصلی:
|
||||
|
||||
### پیشرفته: (ویژگیهای اختیاری)
|
||||
- نصب TCP Brutal: این اسکریپت میتواند به صورت اختیاری TCP Brutal را نصب کند، یک ابزار طراحی شده برای بهبود عملکرد در شبکههای شلوغ.
|
||||
- نصب WARP: ادغام WARP از Cloudflare برای افزودن یک لایه اضافی از رمزنگاری به اتصالات Hysteria2 شما، که باعث حفاظت بیشتر فعالیتهای آنلاین شما میشود.
|
||||
- پیکربندی WARP: مدیریت ادغام WARP با Hysteria2 برای مسیریابی ترافیک.
|
||||
- **نصب و پیکربندی Hysteria2:** پیکربندی Hysteria2 با تنظیمات مورد نظر.
|
||||
- **افزودن کاربر:** افزودن کاربر جدید با محدودیتهای ترافیکی و روزهای انقضا.
|
||||
- **ویرایش کاربر:** تغییر جزئیات کاربر مانند نام کاربری، محدودیت ترافیک، روزهای انقضا، رمز عبور و غیره.
|
||||
- **بازنشانی کاربر:** بازنشانی آمار کاربر.
|
||||
- **حذف کاربر:** حذف کاربر از سیستم.
|
||||
- **دریافت کاربر:** بازیابی اطلاعات دقیق کاربر خاص.
|
||||
- **لیست کاربران:** نمایش لیستی از تمام کاربران.
|
||||
- **بررسی وضعیت ترافیک:** مشاهده وضعیت فعلی ترافیک.
|
||||
- **نمایش URI کاربر:** تولید و نمایش URI برای یک کاربر.
|
||||
|
||||
## پیشنیازها
|
||||
اطمینان حاصل کنید که بستههای زیر نصب شده باشند:
|
||||
## منوی پیشرفته:
|
||||
|
||||
- توزیع مبتنی بر Linux مانند Ubuntu (بر روی Ubuntu تست شده است)
|
||||
- **نصب TCP Brutal:** نصب سرویس TCP Brutal.
|
||||
- **نصب WARP:** نصب سرویس WARP از Cloudflare.
|
||||
- **پیکربندی WARP:** پیکربندی WARP برای مسیرهای ترافیکی مختلف.
|
||||
- **حذف WARP:** حذف WARP از سیستم.
|
||||
- **ربات تلگرام:** شروع یا توقف سرویس ربات تلگرام.
|
||||
- **ساب لینک سینگباکس:** شروع یا توقف سرویس Singbox.
|
||||
- **تغییر پورت Hysteria2:** تغییر پورتی که Hysteria2 به آن گوش میدهد.
|
||||
- **بهروزرسانی Hysteria2:** بهروزرسانی Hysteria2 به نسخه جدیدترین.
|
||||
- **حذف نصب Hysteria2:** حذف Hysteria2 و پیکربندیهای آن.
|
||||
|
||||
## پیشنیازها:
|
||||
اطمینان حاصل کنید که بستههای زیر نصب شدهاند:
|
||||
|
||||
- توزیع لینوکس مبتنی بر اوبونتو (تست شده بر روی اوبونتو)
|
||||
- jq
|
||||
- qrencode
|
||||
- curl
|
||||
- pwgen
|
||||
- uuid-runtime
|
||||
|
||||
در صورت عدم وجود هر کدام از این بستهها، اسکریپت به صورت خودکار سعی میکند آنها را نصب کند.
|
||||
اگر هر یک از این موارد وجود نداشته باشد، اسکریپت سعی خواهد کرد آنها را به طور خودکار نصب کند.
|
||||
|
||||
## سلب مسئولیت:
|
||||
این اسکریپت تنها برای اهداف آموزشی فراهم شده است. توسعهدهندگان هیچگونه مسئولیتی در قبال هرگونه سوءاستفاده یا عواقب ناشی از استفاده از آن ندارند. لطفاً پیش از استقرار در محیط تولید، پیامدهای استفاده از Hysteria2 و ابزارهای مرتبط را درک کنید.
|
||||
|
||||
## ملاحظه:
|
||||
|
||||
این اسکریپت تنها برای مقاصد آموزشی ارائه شده است. توسعهدهندگان هیچ مسئولیتی در قبال سوءاستفاده یا پیامدهای ناشی از استفاده از آن ندارند. لطفاً قبل از استقرار در محیط تولید، مطمئن شوید که پیامدهای استفاده از Hysteria2 و ابزارهای مرتبط را درک کردهاید.
|
||||
|
||||
70
README.md
70
README.md
@ -7,7 +7,8 @@
|
||||
|
||||
# Hysteria2 Management Shell Script
|
||||
|
||||
This shell script provides a menu-driven interface to manage Hysteria2 server operations. It includes options to install, configure, update, and uninstall Hysteria2, as well as manage users, ports, traffic status, and integrate with other tools like TCP Brutal and WARP.
|
||||
This bash script provides a comprehensive menu-driven interface to manage the Hysteria2 server, user accounts, and various services. It supports installation, user management, traffic monitoring, and integration with additional tools like WARP, Singbox SubLink, and a Telegram bot.
|
||||
|
||||
|
||||
### Install command :
|
||||
```shell
|
||||
@ -28,25 +29,50 @@ bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh
|
||||
<p/>
|
||||
|
||||
|
||||
## Features
|
||||
## Features :
|
||||
|
||||
### Hysteria2:
|
||||
- Install and Configure: Installs and configures Hysteria2 server.
|
||||
- Add User: Creates a new user for Hysteria2 access.
|
||||
- Show URI: Displays the connection URI and QR code for existing users.
|
||||
- Check Traffic Status: Monitors real-time traffic information for each user.
|
||||
- Remove User: Deletes a user from the Hysteria2 configuration.
|
||||
- Change Port: Modifies the listening port for the Hysteria2 server.
|
||||
- Update Core: Updates Hysteria2 to the latest available version.
|
||||
- Uninstall Hysteria2: Removes Hysteria2 server and its configuration.
|
||||
- **Hysteria2 Installation & Configuration:**
|
||||
- Install and configure Hysteria2 on your server.
|
||||
- Manage user accounts (add, edit, reset, remove, list).
|
||||
- Monitor traffic and display user URIs.
|
||||
|
||||
### Advance: (Optional features)
|
||||
- **Advanced Options:**
|
||||
- Install and manage additional services like WARP and TCP Brutal.
|
||||
- Start/Stop Singbox SubLink and Telegram bot services.
|
||||
- Change the port number for Hysteria2.
|
||||
- Update or uninstall Hysteria2.
|
||||
|
||||
- Install TCP Brutal: The script can optionally install TCP Brutal, a tool designed to improve performance on congested networks.
|
||||
- Install WARP: Integrate WARP from Cloudflare to add an extra layer of encryption to your Hysteria2 connections, further protecting your online activity.
|
||||
- Configure WARP: Manages WARP integration with Hysteria2 for traffic routing.
|
||||
- **Interactive Menus:**
|
||||
- User-friendly menu-driven interface for easier navigation and management.
|
||||
- Validations for user inputs and system checks to prevent misconfigurations.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
## Main Menu :
|
||||
|
||||
- **Hysteria2 Menu:**
|
||||
- **Install and Configure Hysteria2:** Set up Hysteria2 with your desired configuration.
|
||||
- **Add User:** Add a new user with traffic limits and expiration days.
|
||||
- **Edit User:** Modify user details like username, traffic limit, expiration days, password, etc.
|
||||
- **Reset User:** Reset user statistics.
|
||||
- **Remove User:** Remove a user from the system.
|
||||
- **Get User:** Retrieve detailed information of a specific user.
|
||||
- **List Users:** Display a list of all users.
|
||||
- **Check Traffic Status:** View the current traffic status.
|
||||
- **Show User URI:** Generate and display the URI for a user.
|
||||
|
||||
- **Advance Menu:**
|
||||
- **Install TCP Brutal:** Install the TCP Brutal service.
|
||||
- **Install WARP:** Install Cloudflare's WARP service.
|
||||
- **Configure WARP:** Configure WARP for different traffic routes.
|
||||
- **Uninstall WARP:** Remove WARP from the system.
|
||||
- **Telegram Bot:** Start or stop the Telegram bot service.
|
||||
- **Singbox SubLink:** Start or stop the Singbox service.
|
||||
- **Change Port Hysteria2:** Change the port on which Hysteria2 listens.
|
||||
- **Update Core Hysteria2:** Update Hysteria2 to the latest version.
|
||||
- **Uninstall Hysteria2:** Remove Hysteria2 and its configuration.
|
||||
|
||||
|
||||
## Prerequisites :
|
||||
Ensure the following packages are installed:
|
||||
|
||||
- Ubuntu-based Linux distribution (tested on Ubuntu)
|
||||
@ -57,8 +83,16 @@ Ensure the following packages are installed:
|
||||
- uuid-runtime
|
||||
|
||||
If any of these are missing, the script will attempt to install them automatically.
|
||||
|
||||
## Disclaimer:
|
||||
|
||||
## Contributing :
|
||||
|
||||
Contributions are welcome!
|
||||
|
||||
Feel free to contribute by creating issues or submitting pull requests
|
||||
|
||||
Please fork the repository and submit a pull request with your improvements.
|
||||
|
||||
## Disclaimer :
|
||||
|
||||
This script is provided for educational purposes only. The developer are not responsible for any misuse or consequences arising from its use. Please ensure you understand the implications of using Hysteria2 and related tools before deployment in a production environment.
|
||||
|
||||
|
||||
31
core/cli.py
31
core/cli.py
@ -32,6 +32,7 @@ class Command(Enum):
|
||||
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_SINGBOX = os.path.join(SCRIPT_DIR, 'singbox', 'singbox_shell.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')
|
||||
@ -97,12 +98,15 @@ def restart_hysteria2():
|
||||
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])
|
||||
|
||||
@click.option('--no-traffic', '-t', is_flag=True, help='Do not display traffic information')
|
||||
def get_user(username: str, no_traffic: bool):
|
||||
cmd = ['bash', Command.GET_USER.value, '-u', str(username)]
|
||||
if no_traffic:
|
||||
cmd.append('-t')
|
||||
|
||||
run_cmd(cmd)
|
||||
|
||||
@cli.command('add-user')
|
||||
@click.option('--username', '-u', required=True, help='Username for the new user', type=str)
|
||||
@ -193,7 +197,8 @@ def remove_user(username: 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):
|
||||
@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):
|
||||
command_args = ['bash', Command.SHOW_USER_URI.value, '-u', username]
|
||||
if qrcode:
|
||||
command_args.append('-qr')
|
||||
@ -201,10 +206,11 @@ def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool):
|
||||
command_args.append('-a')
|
||||
else:
|
||||
command_args.extend(['-ip', str(ipv)])
|
||||
if singbox:
|
||||
command_args.append('-s')
|
||||
|
||||
run_cmd(command_args)
|
||||
|
||||
|
||||
@ cli.command('traffic-status')
|
||||
def traffic_status():
|
||||
traffic.traffic_status()
|
||||
@ -271,6 +277,19 @@ def telegram(action: str, token: str, adminid: str):
|
||||
elif action == 'stop':
|
||||
run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'stop'])
|
||||
|
||||
@cli.command('singbox')
|
||||
@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 Singbox service', type=int)
|
||||
def singbox(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_SINGBOX.value, 'start', domain, str(port)])
|
||||
elif action == 'stop':
|
||||
run_cmd(['bash', Command.INSTALL_SINGBOX.value, 'stop'])
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ convert_blocked_status() {
|
||||
# Function to get user info
|
||||
get_user_info() {
|
||||
local username=$1
|
||||
python3 $CLI_PATH get-user -u "$username"
|
||||
python3 $CLI_PATH get-user -u "$username" -t
|
||||
}
|
||||
|
||||
# Function to update user info in JSON
|
||||
|
||||
@ -2,31 +2,55 @@
|
||||
|
||||
source /etc/hysteria/core/scripts/path.sh
|
||||
|
||||
SHOW_TRAFFIC=true
|
||||
|
||||
# Check if a username is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <username>"
|
||||
while getopts ":u:t" opt; do
|
||||
case ${opt} in
|
||||
u )
|
||||
USERNAME=$OPTARG
|
||||
;;
|
||||
t )
|
||||
SHOW_TRAFFIC=false
|
||||
;;
|
||||
\? )
|
||||
echo "Usage: $0 -u <username> [-t]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$USERNAME" ]; then
|
||||
echo "Usage: $0 -u <username> [-t]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USERNAME=$1
|
||||
|
||||
# Check if users.json file exists
|
||||
if [ ! -f "$USERS_FILE" ]; then
|
||||
echo "users.json file not found!"
|
||||
echo "users.json file not found at $USERS_FILE!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract user info using jq
|
||||
USER_INFO=$(jq -r --arg username "$USERNAME" '.[$username] // empty' $USERS_FILE)
|
||||
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."
|
||||
echo "User '$USERNAME' not found in $USERS_FILE."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Print user info
|
||||
echo "$USER_INFO" | jq .
|
||||
|
||||
if [ "$SHOW_TRAFFIC" = true ]; then
|
||||
if [ ! -f "$TRAFFIC_FILE" ]; then
|
||||
echo "No traffic data file found at $TRAFFIC_FILE. User might not have connected yet."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TRAFFIC_INFO=$(jq -r --arg username "$USERNAME" '.[$username] // empty' "$TRAFFIC_FILE")
|
||||
|
||||
if [ -z "$TRAFFIC_INFO" ]; then
|
||||
echo "No traffic data found for user '$USERNAME' in $TRAFFIC_FILE. User might not have connected yet."
|
||||
else
|
||||
echo "$TRAFFIC_INFO" | jq .
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@ -2,6 +2,17 @@
|
||||
|
||||
source /etc/hysteria/core/scripts/path.sh
|
||||
|
||||
get_singbox_domain_and_port() {
|
||||
if [ -f "$SINGBOX_ENV" ]; then
|
||||
local domain port
|
||||
domain=$(grep -E '^HYSTERIA_DOMAIN=' "$SINGBOX_ENV" | cut -d'=' -f2)
|
||||
port=$(grep -E '^HYSTERIA_PORT=' "$SINGBOX_ENV" | cut -d'=' -f2)
|
||||
echo "$domain" "$port"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
show_uri() {
|
||||
if [ -f "$USERS_FILE" ]; then
|
||||
if systemctl is-active --quiet hysteria-server.service; then
|
||||
@ -9,6 +20,7 @@ show_uri() {
|
||||
local generate_qrcode=false
|
||||
local ip_version=4
|
||||
local show_all=false
|
||||
local generate_singbox=false
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
@ -16,13 +28,14 @@ show_uri() {
|
||||
-qr|--qrcode) generate_qrcode=true ;;
|
||||
-ip) ip_version="$2"; shift ;;
|
||||
-a|--all) show_all=true ;;
|
||||
-s|--singbox) generate_singbox=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]"
|
||||
echo "Usage: $0 -u <username> [-qr] [-ip <4|6>] [-a] [-s]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -81,6 +94,13 @@ show_uri() {
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$generate_singbox" = true ] && systemctl is-active --quiet singbox.service; then
|
||||
read -r domain port < <(get_singbox_domain_and_port)
|
||||
if [ -n "$domain" ] && [ -n "$port" ]; then
|
||||
echo -e "\nSingbox Sublink:\nhttps://$domain:$port/sub/singbox/$username/$ip_version#$username\n"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Invalid username. Please try again."
|
||||
fi
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
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"
|
||||
CONFIG_FILE="/etc/hysteria/config.json"
|
||||
TELEGRAM_ENV="/etc/hysteria/core/scripts/telegrambot/.env"
|
||||
SINGBOX_ENV="/etc/hysteria/core/scripts/singbox/.env"
|
||||
|
||||
217
core/scripts/singbox/singbox.json
Normal file
217
core/scripts/singbox/singbox.json
Normal file
@ -0,0 +1,217 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "warn",
|
||||
"output": "box.log",
|
||||
"timestamp": true
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "dns-remote",
|
||||
"address": "udp://1.1.1.1",
|
||||
"address_resolver": "dns-direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-direct",
|
||||
"address": "1.1.1.1",
|
||||
"address_resolver": "dns-local",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-local",
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-block",
|
||||
"address": "rcode://success"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"domain": "cp.cloudflare.com",
|
||||
"server": "dns-remote",
|
||||
"rewrite_ttl": 3000
|
||||
}
|
||||
],
|
||||
"independent_cache": true
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"mtu": 9000,
|
||||
"inet4_address": "172.19.0.1/28",
|
||||
"auto_route": true,
|
||||
"strict_route": true,
|
||||
"endpoint_independent_nat": true,
|
||||
"stack": "mixed",
|
||||
"sniff": true,
|
||||
"sniff_override_destination": true
|
||||
},
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 2334,
|
||||
"sniff": true,
|
||||
"sniff_override_destination": true
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "dns-in",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 6450
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "select",
|
||||
"outbounds": [
|
||||
"auto",
|
||||
"{username}-Hysteria2"
|
||||
],
|
||||
"default": "auto"
|
||||
},
|
||||
{
|
||||
"type": "urltest",
|
||||
"tag": "auto",
|
||||
"outbounds": [
|
||||
"{username}-Hysteria2"
|
||||
],
|
||||
"url": "http://connectivitycheck.gstatic.com/generate_204",
|
||||
"interval": "10m0s",
|
||||
"idle_timeout": "1h40m0s"
|
||||
},
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "{username}-Hysteria2",
|
||||
"server": "{ip}",
|
||||
"server_port": "{port}",
|
||||
"obfs": {
|
||||
"type": "salamander",
|
||||
"password": "{obfs_password}"
|
||||
},
|
||||
"password": "{username}:{password}",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "bts.com",
|
||||
"insecure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "bypass"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-ir",
|
||||
"geosite-ir"
|
||||
],
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"inbound": "dns-in",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "select"
|
||||
}
|
||||
],
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-ir",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-category-ads-all",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-category-ads-all.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-malware",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-malware.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-phishing",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-phishing.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-cryptominers",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-cryptominers.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-ir",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-malware",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-malware.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-phishing",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-phishing.srs",
|
||||
"download_detour": "direct",
|
||||
"update_interval": "72h0m0s"
|
||||
}
|
||||
],
|
||||
"final": "select",
|
||||
"auto_detect_interface": true,
|
||||
"override_android_vpn": true
|
||||
}
|
||||
}
|
||||
|
||||
169
core/scripts/singbox/singbox.py
Normal file
169
core/scripts/singbox/singbox.py
Normal file
@ -0,0 +1,169 @@
|
||||
import os
|
||||
import ssl
|
||||
import json
|
||||
import subprocess
|
||||
from aiohttp import web
|
||||
from aiohttp.web_middlewares import middleware
|
||||
from urllib.parse import unquote, parse_qs
|
||||
import re
|
||||
import time
|
||||
import shlex
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Environment variables
|
||||
DOMAIN = os.getenv('HYSTERIA_DOMAIN')
|
||||
CERTFILE = os.getenv('HYSTERIA_CERTFILE')
|
||||
KEYFILE = os.getenv('HYSTERIA_KEYFILE')
|
||||
PORT = int(os.getenv('HYSTERIA_PORT', '3324'))
|
||||
|
||||
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 shlex.quote(value)
|
||||
|
||||
async def handle(request):
|
||||
try:
|
||||
username = sanitize_input(request.match_info.get('username', ''), r'^[a-zA-Z0-9_-]+$')
|
||||
ip_version = sanitize_input(request.match_info.get('ip_version', ''), r'^[46]$')
|
||||
fragment = request.query.get('fragment', '')
|
||||
|
||||
if not username:
|
||||
return web.Response(status=400, text="Error: Missing 'username' parameter.")
|
||||
|
||||
if not ip_version:
|
||||
return web.Response(status=400, text="Error: Missing 'ip' parameter.")
|
||||
|
||||
if ip_version not in ['4', '6']:
|
||||
return web.Response(status=400, text="Error: Invalid 'ip' parameter. Must be '4' or '6'.")
|
||||
|
||||
config = generate_singbox_config(username, ip_version, fragment)
|
||||
config_json = json.dumps(config, indent=4, sort_keys=True)
|
||||
|
||||
return web.Response(text=config_json, content_type='application/json')
|
||||
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 generate_singbox_config(username, ip_version, fragment):
|
||||
try:
|
||||
username = sanitize_input(username, r'^[a-zA-Z0-9_-]+$')
|
||||
ip_version = sanitize_input(ip_version, r'^[46]$')
|
||||
|
||||
command = [
|
||||
'python3',
|
||||
'/etc/hysteria/core/cli.py',
|
||||
'show-user-uri',
|
||||
'-u', username,
|
||||
'-ip', ip_version
|
||||
]
|
||||
|
||||
uri = subprocess.check_output(command).decode().strip()
|
||||
except subprocess.CalledProcessError:
|
||||
raise RuntimeError("Failed to get URI.")
|
||||
|
||||
if ip_version == '4':
|
||||
components = extract_uri_components(uri, 'IPv4:')
|
||||
else:
|
||||
components = extract_uri_components(uri, 'IPv6:')
|
||||
|
||||
config = load_singbox_template()
|
||||
hysteria_tag = f"{username}-Hysteria2"
|
||||
config['outbounds'][2]['tag'] = hysteria_tag
|
||||
config['outbounds'][2]['server'] = components['ip']
|
||||
config['outbounds'][2]['server_port'] = int(components['port'])
|
||||
config['outbounds'][2]['obfs']['password'] = components['obfs_password']
|
||||
config['outbounds'][2]['password'] = f"{username}:{components['password']}"
|
||||
|
||||
if fragment:
|
||||
config['outbounds'][2]['tls']['server_name'] = fragment
|
||||
|
||||
config['outbounds'][0]['outbounds'] = ["auto", hysteria_tag]
|
||||
config['outbounds'][1]['outbounds'] = [hysteria_tag]
|
||||
|
||||
return config
|
||||
|
||||
def extract_uri_components(uri, prefix):
|
||||
if uri.startswith(prefix):
|
||||
uri = uri[len(prefix):].strip()
|
||||
|
||||
decoded_uri = unquote(uri)
|
||||
pattern = re.compile(
|
||||
r'^hy2://([^:]+):([^@]+)@(\[?[^\]]+?\]?):(\d+)\?([^#]+)(?:#([^/]+))?$'
|
||||
)
|
||||
match = pattern.match(decoded_uri)
|
||||
|
||||
if not match:
|
||||
raise ValueError("Could not parse URI.")
|
||||
|
||||
username = match.group(1)
|
||||
password = match.group(2)
|
||||
ip = match.group(3)
|
||||
port = match.group(4)
|
||||
query_params = match.group(5)
|
||||
fragment = match.group(6)
|
||||
|
||||
if ip.startswith('[') and ip.endswith(']'):
|
||||
ip = ip[1:-1]
|
||||
|
||||
params = parse_qs(query_params)
|
||||
obfs_password = params.get('obfs-password', [''])[0]
|
||||
|
||||
return {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'ip': ip,
|
||||
'port': port,
|
||||
'obfs_password': obfs_password,
|
||||
}
|
||||
|
||||
def load_singbox_template():
|
||||
try:
|
||||
with open('/etc/hysteria/core/scripts/singbox/singbox.json', 'r') as f:
|
||||
return json.load(f)
|
||||
except IOError:
|
||||
raise RuntimeError("Failed to load template.")
|
||||
|
||||
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/singbox/{username}/{ip_version}', handle)])
|
||||
app.router.add_route('*', '/sub/singbox/{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)
|
||||
119
core/scripts/singbox/singbox_shell.sh
Normal file
119
core/scripts/singbox/singbox_shell.sh
Normal file
@ -0,0 +1,119 @@
|
||||
#!/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}"
|
||||
|
||||
if [ -f /etc/hysteria/requirements.txt ]; then
|
||||
pip install -r /etc/hysteria/requirements.txt > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${red}Error: Failed to install Python dependencies. ${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${green}Python dependencies installed successfully. ${NC}"
|
||||
else
|
||||
echo -e "${red}Error: /etc/hysteria/requirements.txt not found. ${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_env_file() {
|
||||
local domain=$1
|
||||
local port=$2
|
||||
local cert_dir="/etc/letsencrypt/live/$domain"
|
||||
|
||||
cat <<EOL > /etc/hysteria/core/scripts/singbox/.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/singbox.service
|
||||
[Unit]
|
||||
Description=Singbox Python Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python3 /etc/hysteria/core/scripts/singbox/singbox.py
|
||||
WorkingDirectory=/etc/hysteria/core/scripts/singbox
|
||||
EnvironmentFile=/etc/hysteria/core/scripts/singbox/.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 singbox.service; then
|
||||
echo "The singbox.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/singbox
|
||||
systemctl daemon-reload
|
||||
systemctl enable singbox.service > /dev/null 2>&1
|
||||
systemctl start singbox.service > /dev/null 2>&1
|
||||
|
||||
if systemctl is-active --quiet singbox.service; then
|
||||
echo -e "${green}Singbox service setup completed. The service is now running on port $port. ${NC}"
|
||||
else
|
||||
echo -e "${red}Singbox setup completed. The service failed to start. ${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
systemctl stop singbox.service > /dev/null 2>&1
|
||||
systemctl disable singbox.service > /dev/null 2>&1
|
||||
|
||||
rm -f /etc/hysteria/core/scripts/singbox/.env
|
||||
echo -e "\n"
|
||||
|
||||
echo -e "${yellow}Singbox 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
|
||||
@ -4,6 +4,7 @@ import qrcode
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from dotenv import load_dotenv
|
||||
from telebot import types
|
||||
|
||||
@ -23,7 +24,7 @@ def run_cli_command(command):
|
||||
|
||||
def create_main_markup():
|
||||
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
markup.row('Show User', 'Add User')
|
||||
markup.row('Add User', 'Show User')
|
||||
markup.row('Delete User', 'Server Info')
|
||||
return markup
|
||||
|
||||
@ -51,14 +52,19 @@ def process_add_user_step1(message):
|
||||
|
||||
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
|
||||
if "No such file or directory" in result or result.strip() == "":
|
||||
bot.reply_to(message, "User list file does not exist. Adding the first user.")
|
||||
else:
|
||||
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)
|
||||
@ -88,50 +94,82 @@ def show_user(message):
|
||||
def process_show_user(message):
|
||||
username = message.text.strip()
|
||||
command = f"python3 {CLI_PATH} get-user -u {username}"
|
||||
result = run_cli_command(command)
|
||||
user_result = run_cli_command(command)
|
||||
|
||||
user_json_match = re.search(r'(\{.*?\})\n?(\{.*?\})?', user_result, re.DOTALL)
|
||||
|
||||
if not user_json_match:
|
||||
bot.reply_to(message, "Failed to parse user details. The command output format may be incorrect.")
|
||||
return
|
||||
|
||||
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']}"
|
||||
)
|
||||
user_json = user_json_match.group(1)
|
||||
traffic_data_section = user_json_match.group(2)
|
||||
|
||||
qr_command = f"python3 {CLI_PATH} show-user-uri -u {username} -ip 4"
|
||||
qr_result = run_cli_command(qr_command)
|
||||
try:
|
||||
user_details = json.loads(user_json)
|
||||
|
||||
if traffic_data_section:
|
||||
traffic_data = json.loads(traffic_data_section)
|
||||
traffic_message = (
|
||||
f"**Traffic Data:**\n"
|
||||
f"Upload: {traffic_data.get('upload_bytes', 0) / (1024 ** 2):.2f} MB\n"
|
||||
f"Download: {traffic_data.get('download_bytes', 0) / (1024 ** 2):.2f} MB\n"
|
||||
f"Status: {traffic_data.get('status', 'Unknown')}"
|
||||
)
|
||||
else:
|
||||
traffic_message = "**Traffic Data:**\nNo traffic data available. The user might not have connected yet."
|
||||
except json.JSONDecodeError:
|
||||
bot.reply_to(message, "Failed to parse JSON data. The command output may be malformed.")
|
||||
return
|
||||
|
||||
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()
|
||||
formatted_details = (
|
||||
f"**User Details:**\n\n"
|
||||
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']}\n\n"
|
||||
f"{traffic_message}"
|
||||
)
|
||||
|
||||
qr_v4 = qrcode.make(uri_v4)
|
||||
bio_v4 = io.BytesIO()
|
||||
qr_v4.save(bio_v4, 'PNG')
|
||||
bio_v4.seek(0)
|
||||
combined_command = f"python3 {CLI_PATH} show-user-uri -u {username} -ip 4 -s"
|
||||
combined_result = run_cli_command(combined_command)
|
||||
|
||||
markup = types.InlineKeyboardMarkup(row_width=3)
|
||||
markup.add(types.InlineKeyboardButton("Reset User", callback_data=f"reset_user:{username}"),
|
||||
types.InlineKeyboardButton("IPv6-URI", callback_data=f"ipv6_uri:{username}"))
|
||||
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}"))
|
||||
if "Error" in combined_result or "Invalid" in combined_result:
|
||||
bot.reply_to(message, combined_result)
|
||||
return
|
||||
|
||||
bot.send_photo(
|
||||
message.chat.id,
|
||||
bio_v4,
|
||||
caption=f"**User Details:**\n\n{formatted_details}\n\n**IPv4 URI:**\n\n`{uri_v4}`",
|
||||
reply_markup=markup,
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
result_lines = combined_result.split('\n')
|
||||
uri_v4 = result_lines[1].strip()
|
||||
|
||||
singbox_sublink = result_lines[-1].strip() if "https://" in result_lines[-1] else None
|
||||
|
||||
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=3)
|
||||
markup.add(types.InlineKeyboardButton("Reset User", callback_data=f"reset_user:{username}"),
|
||||
types.InlineKeyboardButton("IPv6-URI", callback_data=f"ipv6_uri:{username}"))
|
||||
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}"))
|
||||
|
||||
caption = f"{formatted_details}\n\n**IPv4 URI:**\n\n`{uri_v4}`"
|
||||
if singbox_sublink:
|
||||
caption += f"\n\n\n**SingBox SUB:**\n{singbox_sublink}"
|
||||
|
||||
bot.send_photo(
|
||||
message.chat.id,
|
||||
bio_v4,
|
||||
caption=caption,
|
||||
reply_markup=markup,
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Server Info')
|
||||
def server_info(message):
|
||||
|
||||
@ -13,7 +13,7 @@ if ! command -v jq &> /dev/null || ! command -v git &> /dev/null || ! command -v
|
||||
apt update && apt upgrade -y && apt install jq qrencode curl pwgen uuid-runtime python3 python3-pip git -y
|
||||
fi
|
||||
|
||||
git clone https://github.com/ReturnFI/Hysteria2 /etc/hysteria
|
||||
git clone -b Dev https://github.com/ReturnFI/Hysteria2 /etc/hysteria
|
||||
|
||||
# Add alias 'hys2' for Hysteria2
|
||||
if ! grep -q "alias hys2='/etc/hysteria/menu.sh'" ~/.bashrc; then
|
||||
|
||||
66
menu.sh
66
menu.sh
@ -304,6 +304,58 @@ telegram_bot_handler() {
|
||||
done
|
||||
}
|
||||
|
||||
singbox_handler() {
|
||||
while true; do
|
||||
echo -e "${cyan}1.${NC} Start Singbox service"
|
||||
echo -e "${red}2.${NC} Stop Singbox service"
|
||||
echo "0. Back"
|
||||
read -p "Choose an option: " option
|
||||
|
||||
case $option in
|
||||
1)
|
||||
if systemctl is-active --quiet singbox.service; then
|
||||
echo "The singbox.service is already active."
|
||||
else
|
||||
while true; do
|
||||
read -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 -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 singbox -a start -d "$domain" -p "$port"
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
if ! systemctl is-active --quiet singbox.service; then
|
||||
echo "The singbox.service is already inactive."
|
||||
else
|
||||
python3 $CLI_PATH singbox -a stop
|
||||
fi
|
||||
;;
|
||||
0)
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option. Please try again."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Function to display the main menu
|
||||
display_main_menu() {
|
||||
clear
|
||||
@ -411,9 +463,10 @@ display_advance_menu() {
|
||||
echo -e "${cyan}[3] ${NC}↝ Configure WARP"
|
||||
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
|
||||
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 "${green}[6] ${NC}↝ SingBox SubLink"
|
||||
echo -e "${cyan}[7] ${NC}↝ Change Port Hysteria2"
|
||||
echo -e "${cyan}[8] ${NC}↝ Update Core Hysteria2"
|
||||
echo -e "${red}[9] ${NC}↝ Uninstall Hysteria2"
|
||||
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
|
||||
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
|
||||
echo -ne "${yellow}➜ Enter your option: ${NC}"
|
||||
@ -432,9 +485,10 @@ advance_menu() {
|
||||
3) warp_configure_handler ;;
|
||||
4) python3 $CLI_PATH uninstall-warp ;;
|
||||
5) telegram_bot_handler ;;
|
||||
6) hysteria2_change_port_handler ;;
|
||||
7) python3 $CLI_PATH update-hysteria2 ;;
|
||||
8) python3 $CLI_PATH uninstall-hysteria2 ;;
|
||||
6) singbox_handler ;;
|
||||
7) hysteria2_change_port_handler ;;
|
||||
8) python3 $CLI_PATH update-hysteria2 ;;
|
||||
9) python3 $CLI_PATH uninstall-hysteria2 ;;
|
||||
0) return ;;
|
||||
*) echo "Invalid option. Please try again." ;;
|
||||
esac
|
||||
|
||||
@ -2,3 +2,4 @@ pyTelegramBotAPI
|
||||
qrcode
|
||||
python-dotenv
|
||||
requests
|
||||
aiohttp
|
||||
|
||||
@ -9,6 +9,7 @@ FILES=(
|
||||
"/etc/hysteria/traffic_data.json"
|
||||
"/etc/hysteria/config.json"
|
||||
"/etc/hysteria/core/scripts/telegrambot/.env"
|
||||
"/etc/hysteria/core/scripts/singbox/.env"
|
||||
)
|
||||
|
||||
echo "Backing up files to $TEMP_DIR"
|
||||
@ -35,9 +36,10 @@ 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 and bot service"
|
||||
echo "Restarting hysteria services"
|
||||
systemctl restart hysteria-server.service
|
||||
systemctl restart hysteria-bot.service
|
||||
systemctl restart singbox.service
|
||||
|
||||
echo "Setting execute permissions for user.sh and kick.sh"
|
||||
chmod +x /etc/hysteria/core/scripts/hysteria2/user.sh
|
||||
|
||||
Reference in New Issue
Block a user