- Added `edit_normalsub_subpath` function to `cli_api.py` to
interact with the `normalsub.sh edit_subpath` command, including
input validation for the new subpath.
- Updated the `normal-sub` command in `cli.py`:
- Added 'edit_subpath' as a valid action.
- Introduced a '--subpath' option for specifying the new path.
- Modified the command logic to call the new API function when
the 'edit_subpath' action is selected.
614 lines
24 KiB
Python
614 lines
24 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import typing
|
|
import click
|
|
import cli_api
|
|
import json
|
|
|
|
|
|
def pretty_print(data: typing.Any):
|
|
if isinstance(data, dict) or isinstance(data, list):
|
|
print(json.dumps(data, indent=4))
|
|
return
|
|
|
|
print(data)
|
|
|
|
|
|
@click.group()
|
|
def cli():
|
|
pass
|
|
|
|
# region Hysteria2
|
|
|
|
|
|
@cli.command('install-hysteria2')
|
|
@click.option('--port', '-p', required=True, help='Port for Hysteria2', type=int)
|
|
@click.option('--sni', '-s', required=False, default='bts.com', help='SNI for Hysteria2 (default: bts.com)', type=str)
|
|
def install_hysteria2(port: int, sni: str):
|
|
try:
|
|
cli_api.install_hysteria2(port, sni)
|
|
click.echo(f'Hysteria2 installed successfully on port {port} with SNI {sni}.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('uninstall-hysteria2')
|
|
def uninstall_hysteria2():
|
|
try:
|
|
cli_api.uninstall_hysteria2()
|
|
click.echo('Hysteria2 uninstalled successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('update-hysteria2')
|
|
def update_hysteria2():
|
|
try:
|
|
cli_api.update_hysteria2()
|
|
click.echo('Hysteria2 updated successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('restart-hysteria2')
|
|
def restart_hysteria2():
|
|
try:
|
|
cli_api.restart_hysteria2()
|
|
click.echo('Hysteria2 restarted successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('change-hysteria2-port')
|
|
@click.option('--port', '-p', required=True, help='New port for Hysteria2', type=int)
|
|
def change_hysteria2_port(port: int):
|
|
try:
|
|
cli_api.change_hysteria2_port(port)
|
|
click.echo(f'Hysteria2 port changed to {port} successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('change-hysteria2-sni')
|
|
@click.option('--sni', '-s', required=True, help='New SNI for Hysteria2', type=str)
|
|
def change_hysteria2_sni(sni: str):
|
|
try:
|
|
cli_api.change_hysteria2_sni(sni)
|
|
click.echo(f'Hysteria2 SNI changed to {sni} successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('backup-hysteria')
|
|
def backup_hysteria2():
|
|
try:
|
|
cli_api.backup_hysteria2()
|
|
click.echo('Hysteria configuration backed up successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('restore-hysteria2')
|
|
@click.argument('backup_file_path', type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True))
|
|
def restore_hysteria2(backup_file_path):
|
|
"""Restores Hysteria configuration from a backup ZIP file."""
|
|
try:
|
|
cli_api.restore_hysteria2(backup_file_path)
|
|
click.echo('Hysteria configuration restored successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
# endregion
|
|
|
|
# region User
|
|
|
|
|
|
@cli.command('list-users')
|
|
def list_users():
|
|
try:
|
|
res = cli_api.list_users()
|
|
if res:
|
|
pretty_print(res)
|
|
else:
|
|
click.echo('No users found.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('get-user')
|
|
@click.option('--username', '-u', required=True, help='Username for the user to get', type=str)
|
|
def get_user(username: str):
|
|
try:
|
|
if res := cli_api.get_user(username):
|
|
pretty_print(res)
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('add-user')
|
|
@click.option('--username', '-u', required=True, help='Username for the new user', type=str)
|
|
@click.option('--traffic-limit', '-t', required=True, help='Traffic limit for the new user in GB', type=int)
|
|
@click.option('--expiration-days', '-e', required=True, help='Expiration days for the new user', type=int)
|
|
@click.option('--password', '-p', required=False, help='Password for the user', type=str)
|
|
@click.option('--creation-date', '-c', required=False, help='Creation date for the user (YYYY-MM-DD)', type=str)
|
|
def add_user(username: str, traffic_limit: int, expiration_days: int, password: str, creation_date: str):
|
|
try:
|
|
cli_api.add_user(username, traffic_limit, expiration_days, password, creation_date)
|
|
click.echo(f"User '{username}' added successfully.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('edit-user')
|
|
@click.option('--username', '-u', required=True, help='Username for the user to edit', type=str)
|
|
@click.option('--new-username', '-nu', required=False, help='New username for the user', type=str)
|
|
@click.option('--new-traffic-limit', '-nt', required=False, help='Traffic limit for the new user in GB', type=int)
|
|
@click.option('--new-expiration-days', '-ne', required=False, help='Expiration days for the new user', type=int)
|
|
@click.option('--renew-password', '-rp', is_flag=True, help='Renew password for the user')
|
|
@click.option('--renew-creation-date', '-rc', is_flag=True, help='Renew creation date for the user')
|
|
@click.option('--blocked', '-b', is_flag=True, help='Block the user')
|
|
def edit_user(username: str, new_username: str, new_traffic_limit: int, new_expiration_days: int, renew_password: bool, renew_creation_date: bool, blocked: bool):
|
|
try:
|
|
cli_api.kick_user_by_name(username)
|
|
cli_api.traffic_status(display_output=False)
|
|
cli_api.edit_user(username, new_username, new_traffic_limit, new_expiration_days,
|
|
renew_password, renew_creation_date, blocked)
|
|
click.echo(f"User '{username}' updated successfully.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('reset-user')
|
|
@click.option('--username', '-u', required=True, help='Username for the user to Reset', type=str)
|
|
def reset_user(username: str):
|
|
try:
|
|
cli_api.reset_user(username)
|
|
click.echo(f"User '{username}' reset successfully.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('remove-user')
|
|
@click.option('--username', '-u', required=True, help='Username for the user to remove', type=str)
|
|
def remove_user(username: str):
|
|
try:
|
|
cli_api.kick_user_by_name(username)
|
|
cli_api.traffic_status(display_output=False)
|
|
cli_api.remove_user(username)
|
|
click.echo(f"User '{username}' removed successfully.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('kick-user')
|
|
@click.option('--username', '-u', required=True, help='Username of the user to kick')
|
|
def kick_user(username: str):
|
|
"""Kicks a specific user by username."""
|
|
try:
|
|
cli_api.kick_user_by_name(username)
|
|
# click.echo(f"User '{username}' kicked successfully.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@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')
|
|
@click.option('--singbox', '-s', is_flag=True, help='Generate Singbox sublink if Singbox service is active')
|
|
@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):
|
|
try:
|
|
res = cli_api.show_user_uri(username, qrcode, ipv, all, singbox, normalsub)
|
|
if res:
|
|
click.echo(res)
|
|
else:
|
|
click.echo(f"URI for user '{username}' could not be generated.")
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('show-user-uri-json')
|
|
@click.argument('usernames', nargs=-1, required=True)
|
|
def show_user_uri_json(usernames: list[str]):
|
|
"""
|
|
Displays URI information in JSON format for a list of users.
|
|
"""
|
|
try:
|
|
res = cli_api.show_user_uri_json(usernames)
|
|
if res:
|
|
pretty_print(res)
|
|
else:
|
|
click.echo('No user URIs could be generated.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
# endregion
|
|
|
|
# region Server
|
|
|
|
|
|
@cli.command('traffic-status')
|
|
@click.option('--no-gui', is_flag=True, help='Retrieve traffic data without displaying output and kick expired users')
|
|
def traffic_status(no_gui):
|
|
try:
|
|
cli_api.traffic_status(no_gui=no_gui)
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('server-info')
|
|
def server_info():
|
|
try:
|
|
res = cli_api.server_info()
|
|
if res:
|
|
pretty_print(res)
|
|
else:
|
|
click.echo('Server information not available.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('manage_obfs')
|
|
@click.option('--remove', '-r', is_flag=True, help="Remove 'obfs' from config.json.")
|
|
@click.option('--generate', '-g', is_flag=True, help="Generate new 'obfs' in config.json.")
|
|
def manage_obfs(remove: bool, generate: bool):
|
|
try:
|
|
if not remove and not generate:
|
|
raise click.UsageError('Error: You must use either --remove or --generate')
|
|
if remove and generate:
|
|
raise click.UsageError('Error: You cannot use both --remove and --generate at the same time')
|
|
|
|
if generate:
|
|
cli_api.enable_hysteria2_obfs()
|
|
click.echo('Obfs enabled successfully.')
|
|
elif remove:
|
|
cli_api.disable_hysteria2_obfs()
|
|
click.echo('Obfs disabled successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('ip-address')
|
|
@click.option('--edit', is_flag=True, help='Edit IP addresses manually.')
|
|
@click.option('-4', '--ipv4', type=str, help='Specify the new IPv4 address.')
|
|
@click.option('-6', '--ipv6', type=str, help='Specify the new IPv6 address.')
|
|
def ip_address(edit: bool, ipv4: str, ipv6: str):
|
|
'''
|
|
Manage IP addresses in .configs.env.
|
|
- Use without options to add auto-detected IPs.
|
|
- Use --edit with -4 or -6 to manually update IPs.
|
|
'''
|
|
try:
|
|
if not edit:
|
|
cli_api.add_ip_address()
|
|
click.echo('IP addresses added successfully.')
|
|
return
|
|
|
|
if not ipv4 and not ipv6:
|
|
raise click.UsageError('Error: You must specify either -4 or -6')
|
|
|
|
cli_api.edit_ip_address(ipv4, ipv6)
|
|
click.echo('IP address configuration updated successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('update-geo')
|
|
@click.option('--country', '-c',
|
|
type=click.Choice(['iran', 'china', 'russia'], case_sensitive=False),
|
|
default='iran',
|
|
help='Select country for geo files (default: iran)')
|
|
def update_geo(country: str):
|
|
try:
|
|
cli_api.update_geo(country)
|
|
click.echo(f'Geo files for {country} updated successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('masquerade')
|
|
@click.option('--remove', '-r', is_flag=True, help="Remove 'masquerade' from config.json.")
|
|
@click.option('--enable', '-e', metavar='<domain>', type=str, help="Enable 'masquerade' in config.json with the specified domain.")
|
|
def masquerade(remove: bool, enable: str):
|
|
'''Manage 'masquerade' in Hysteria2 configuration.'''
|
|
try:
|
|
if not remove and not enable:
|
|
raise click.UsageError('Error: You must use either --remove or --enable')
|
|
if remove and enable:
|
|
raise click.UsageError('Error: You cannot use both --remove and --enable at the same time')
|
|
|
|
if enable:
|
|
# NOT SURE THIS IS NEEDED
|
|
# if not enable.startswith('http://') and not enable.startswith('https://'):
|
|
# enable = 'https://' + enable
|
|
cli_api.enable_hysteria2_masquerade(enable)
|
|
click.echo('Masquerade enabled successfully.')
|
|
elif remove:
|
|
cli_api.disable_hysteria2_masquerade()
|
|
click.echo('Masquerade disabled successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
# endregion
|
|
|
|
# region Advanced Menu
|
|
|
|
|
|
@cli.command('install-tcp-brutal')
|
|
def install_tcp_brutal():
|
|
try:
|
|
cli_api.install_tcp_brutal()
|
|
click.echo('TCP Brutal installed successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('install-warp')
|
|
def install_warp():
|
|
try:
|
|
cli_api.install_warp()
|
|
click.echo('WARP installed successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('uninstall-warp')
|
|
def uninstall_warp():
|
|
try:
|
|
cli_api.uninstall_warp()
|
|
click.echo('WARP uninstalled successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('configure-warp')
|
|
@click.option('--all', '-a', is_flag=True, help='Use WARP for all connections')
|
|
@click.option('--popular-sites', '-p', is_flag=True, help='Use WARP for popular sites like Google, OpenAI, etc')
|
|
@click.option('--domestic-sites', '-d', is_flag=True, help='Use WARP for Iran domestic sites')
|
|
@click.option('--block-adult-sites', '-x', is_flag=True, help='Block adult content (porn)')
|
|
def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_adult_sites: bool):
|
|
try:
|
|
cli_api.configure_warp(all, popular_sites, domestic_sites, block_adult_sites)
|
|
click.echo('WARP configured successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('warp-status')
|
|
def warp_status():
|
|
try:
|
|
res = cli_api.warp_status()
|
|
if res:
|
|
pretty_print(res)
|
|
else:
|
|
click.echo('WARP status not available.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@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):
|
|
try:
|
|
if action == 'start':
|
|
if not token or not adminid:
|
|
raise click.UsageError('Error: Both --token and --adminid are required for the start action.')
|
|
cli_api.start_telegram_bot(token, adminid)
|
|
click.echo(f'Telegram bot started successfully.')
|
|
elif action == 'stop':
|
|
cli_api.stop_telegram_bot()
|
|
click.echo(f'Telegram bot stopped successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@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):
|
|
try:
|
|
if action == 'start':
|
|
if not domain or not port:
|
|
raise click.UsageError('Error: Both --domain and --port are required for the start action.')
|
|
cli_api.start_singbox(domain, port)
|
|
click.echo(f'Singbox started successfully.')
|
|
elif action == 'stop':
|
|
cli_api.stop_singbox()
|
|
click.echo(f'Singbox stopped successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('normal-sub')
|
|
@click.option('--action', '-a', required=True,
|
|
type=click.Choice(['start', 'stop', 'edit_subpath'], case_sensitive=False),
|
|
help='Action to perform: start, stop, or edit_subpath')
|
|
@click.option('--domain', '-d', required=False, help='Domain name for SSL (for start action)', type=str)
|
|
@click.option('--port', '-p', required=False, help='Port number for NormalSub service (for start action)', type=int)
|
|
@click.option('--subpath', '-sp', required=False, help='New subpath (alphanumeric, for edit_subpath action)', type=str)
|
|
def normalsub(action: str, domain: str, port: int, subpath: str):
|
|
try:
|
|
if action == 'start':
|
|
if not domain or not port:
|
|
raise click.UsageError('Error: Both --domain and --port are required for the start action.')
|
|
cli_api.start_normalsub(domain, port)
|
|
click.echo(f'NormalSub started successfully.')
|
|
elif action == 'stop':
|
|
cli_api.stop_normalsub()
|
|
click.echo(f'NormalSub stopped successfully.')
|
|
elif action == 'edit_subpath':
|
|
if not subpath:
|
|
raise click.UsageError('Error: --subpath is required for the edit_subpath action.')
|
|
cli_api.edit_normalsub_subpath(subpath)
|
|
click.echo(f'NormalSub subpath updated to {subpath} successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('webpanel')
|
|
@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 WebPanel service', type=int)
|
|
@click.option('--admin-username', '-au', required=False, help='Admin username for WebPanel', type=str)
|
|
@click.option('--admin-password', '-ap', required=False, help='Admin password for WebPanel', type=str)
|
|
@click.option('--expiration-minutes', '-e', required=False, help='Expiration minutes for WebPanel session', type=int, default=20)
|
|
@click.option('--debug', '-g', is_flag=True, help='Enable debug mode for WebPanel', default=False)
|
|
@click.option('--decoy-path', '-dp', required=False, type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True), help='Optional path to serve as a decoy site (only for start action)') # Add decoy_path option
|
|
def webpanel(action: str, domain: str, port: int, admin_username: str, admin_password: str, expiration_minutes: int, debug: bool, decoy_path: str | None): # Add decoy_path parameter
|
|
"""Manages the Hysteria Web Panel service."""
|
|
try:
|
|
if action == 'start':
|
|
if not domain or not port or not admin_username or not admin_password:
|
|
raise click.UsageError('Error: the --domain, --port, --admin-username, and --admin-password are required for the start action.')
|
|
|
|
cli_api.start_webpanel(domain, port, admin_username, admin_password, expiration_minutes, debug, decoy_path)
|
|
|
|
services_status = cli_api.get_services_status()
|
|
if not services_status:
|
|
raise click.Abort('Error: WebPanel services status not available.')
|
|
if not services_status.get('hysteria-webpanel.service'):
|
|
raise click.Abort('Error: hysteria-webpanel.service service is not running.')
|
|
if not services_status.get('hysteria-caddy.service'):
|
|
raise click.Abort('Error: hysteria-caddy.service service is not running.')
|
|
|
|
url = cli_api.get_webpanel_url()
|
|
click.echo(f'Hysteria web panel is now running. The service is accessible on: {url}')
|
|
if decoy_path:
|
|
click.echo(f'Decoy site configured using path: {decoy_path}')
|
|
elif action == 'stop':
|
|
if decoy_path:
|
|
click.echo('Warning: --decoy-path option is ignored for the stop action.', err=True)
|
|
cli_api.stop_webpanel()
|
|
click.echo(f'WebPanel stopped successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('setup-webpanel-decoy')
|
|
@click.option('--domain', '-d', required=True, help='Domain name associated with the web panel', type=str)
|
|
@click.option('--decoy-path', '-dp', required=True, type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True), help='Path to the directory containing the decoy website files (e.g., /var/www/html)')
|
|
def setup_webpanel_decoy(domain: str, decoy_path: str):
|
|
"""Sets up or updates the decoy site for the running Web Panel."""
|
|
try:
|
|
cli_api.setup_webpanel_decoy(domain, decoy_path)
|
|
click.echo(f'Web Panel decoy site configured successfully for domain {domain} using path {decoy_path}.')
|
|
click.echo('Note: Caddy service was restarted.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('stop-webpanel-decoy')
|
|
def stop_webpanel_decoy():
|
|
"""Stops the decoy site functionality for the Web Panel."""
|
|
try:
|
|
cli_api.stop_webpanel_decoy()
|
|
click.echo(f'Web Panel decoy site stopped and configuration removed successfully.')
|
|
click.echo('Note: Caddy service was restarted.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('get-webpanel-url')
|
|
def get_web_panel_url():
|
|
try:
|
|
url = cli_api.get_webpanel_url()
|
|
click.echo(f'Hysteria web panel is now running. The service is accessible on: {url}')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('get-webpanel-api-token')
|
|
def get_web_panel_api_token():
|
|
try:
|
|
token = cli_api.get_webpanel_api_token()
|
|
click.echo(f'WebPanel API token: {token}')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('get-webpanel-services-status')
|
|
def get_web_panel_services_status():
|
|
try:
|
|
if services_status := cli_api.get_services_status():
|
|
webpanel_status = services_status.get('hysteria-webpanel.service', False)
|
|
caddy_status = services_status.get('hysteria-caddy.service', False)
|
|
print(f"hysteria-webpanel.service: {'Active' if webpanel_status else 'Inactive'}")
|
|
print(f"hysteria-caddy.service: {'Active' if caddy_status else 'Inactive'}")
|
|
else:
|
|
click.echo('Error: Services status not available.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('get-services-status')
|
|
def get_services_status():
|
|
try:
|
|
if services_status := cli_api.get_services_status():
|
|
for service, status in services_status.items():
|
|
click.echo(f"{service}: {'Active' if status else 'Inactive'}")
|
|
else:
|
|
click.echo('Error: Services status not available.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
|
|
@cli.command('show-version')
|
|
def show_version():
|
|
"""Displays the currently installed version of the panel."""
|
|
try:
|
|
if version_info := cli_api.show_version():
|
|
click.echo(version_info)
|
|
else:
|
|
click.echo("Error retrieving version")
|
|
except Exception as e:
|
|
click.echo(f"An unexpected error occurred: {e}", err=True)
|
|
|
|
|
|
@cli.command('check-version')
|
|
def check_version():
|
|
"""Checks if the current version is up-to-date and displays changelog if not."""
|
|
try:
|
|
if version_info := cli_api.check_version():
|
|
click.echo(version_info)
|
|
else:
|
|
click.echo("Error retrieving version")
|
|
except Exception as e:
|
|
click.echo(f"An unexpected error occurred: {e}", err=True)
|
|
|
|
@cli.command('start-ip-limit')
|
|
def start_ip_limit():
|
|
"""Starts the IP limiter service."""
|
|
try:
|
|
cli_api.start_ip_limiter()
|
|
click.echo('IP Limiter service started successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('stop-ip-limit')
|
|
def stop_ip_limit():
|
|
"""Stops the IP limiter service."""
|
|
try:
|
|
cli_api.stop_ip_limiter()
|
|
click.echo('IP Limiter service stopped successfully.')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
@cli.command('config-ip-limit')
|
|
@click.option('--block-duration', '-bd', type=int, help='New block duration in seconds')
|
|
@click.option('--max-ips', '-mi', type=int, help='New maximum IPs per user')
|
|
def config_ip_limit(block_duration: int, max_ips: int):
|
|
"""Configures the IP limiter service parameters."""
|
|
try:
|
|
cli_api.config_ip_limiter(block_duration, max_ips)
|
|
click.echo('IP Limiter configuration updated successfully.')
|
|
if block_duration is not None:
|
|
click.echo(f' Block Duration: {block_duration} seconds')
|
|
if max_ips is not None:
|
|
click.echo(f' Max IPs per user: {max_ips}')
|
|
except Exception as e:
|
|
click.echo(f'{e}', err=True)
|
|
|
|
# endregion
|
|
|
|
|
|
if __name__ == '__main__':
|
|
cli()
|