From 1e0c60c112b4c4f2b99f3238155ba3f99ae6d37b Mon Sep 17 00:00:00 2001 From: ReturnFI <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:07:59 +0000 Subject: [PATCH] feat: Add web panel configuration management functions --- core/cli.py | 45 +++++- core/cli_api.py | 55 ++++++++ core/scripts/webpanel/webpanel_shell.sh | 177 ++++++++++++++++++++---- menu.sh | 56 +++++++- 4 files changed, 305 insertions(+), 28 deletions(-) diff --git a/core/cli.py b/core/cli.py index 247e785..a62cf39 100644 --- a/core/cli.py +++ b/core/cli.py @@ -719,6 +719,49 @@ def get_web_panel_services_status(): except Exception as e: click.echo(f'{e}', err=True) +@cli.command('change-webpanel-exp') +@click.option('--minutes', '-m', required=True, help='New session expiration time in minutes', type=int) +def change_webpanel_exp(minutes: int): + """Changes the session expiration time for the WebPanel.""" + try: + cli_api.change_webpanel_expiration(minutes) + click.echo(f'WebPanel session expiration successfully updated to {minutes} minutes.') + click.echo('WebPanel service has been restarted.') + except Exception as e: + click.echo(f'{e}', err=True) + + +@cli.command('change-webpanel-root') +@click.option('--path', '-p', required=False, help='New root path. If not provided, a random one will be generated.', type=str) +def change_webpanel_root(path: str | None): + """Changes the root path for the WebPanel.""" + try: + cli_api.change_webpanel_root_path(path) + click.echo(f'WebPanel root path updated successfully.') + new_url = cli_api.get_webpanel_url() + click.echo(f'New URL is accessible on: {new_url}') + click.echo('WebPanel and Caddy services have been restarted.') + except Exception as e: + click.echo(f'{e}', err=True) + + +@cli.command('change-webpanel-domain-port') +@click.option('--domain', '-d', required=False, help='New domain for WebPanel', type=str) +@click.option('--port', '-p', required=False, help='New port for WebPanel', type=int) +def change_webpanel_domain_port(domain: str | None, port: int | None): + """Changes the domain and/or port for the WebPanel.""" + try: + if not domain and not port: + raise click.UsageError('Error: You must provide either --domain or --port, or both.') + + cli_api.change_webpanel_domain_port(domain, port) + click.echo(f'WebPanel domain/port configuration updated successfully.') + new_url = cli_api.get_webpanel_url() + click.echo(f'New URL is accessible on: {new_url}') + click.echo('Caddy service has been restarted.') + except Exception as e: + click.echo(f'{e}', err=True) + @cli.command('get-services-status') def get_services_status(): @@ -801,4 +844,4 @@ def config_ip_limit(block_duration: int, max_ips: int): if __name__ == '__main__': - cli() + cli() \ No newline at end of file diff --git a/core/cli_api.py b/core/cli_api.py index 822828d..edc382e 100644 --- a/core/cli_api.py +++ b/core/cli_api.py @@ -733,6 +733,31 @@ def get_webpanel_api_token() -> str | None: '''Gets the API token of WebPanel.''' return run_cmd(['bash', Command.SHELL_WEBPANEL.value, 'api-token']) +def get_webpanel_env_config() -> dict[str, Any]: + '''Retrieves the current configuration for the WebPanel service from its .env file.''' + try: + if not os.path.exists(WEBPANEL_ENV_FILE): + return {} + + env_vars = dotenv_values(WEBPANEL_ENV_FILE) + config = {} + + config['DOMAIN'] = env_vars.get('DOMAIN') + config['ROOT_PATH'] = env_vars.get('ROOT_PATH') + + port_val = env_vars.get('PORT') + if port_val and port_val.isdigit(): + config['PORT'] = int(port_val) + + exp_val = env_vars.get('EXPIRATION_MINUTES') + if exp_val and exp_val.isdigit(): + config['EXPIRATION_MINUTES'] = int(exp_val) + + return config + except Exception as e: + print(f"Error reading WebPanel .env file: {e}") + return {} + def reset_webpanel_credentials(new_username: str | None = None, new_password: str | None = None): '''Resets the WebPanel admin username and/or password.''' if not new_username and not new_password: @@ -746,6 +771,36 @@ def reset_webpanel_credentials(new_username: str | None = None, new_password: st run_cmd(cmd_args) +def change_webpanel_expiration(expiration_minutes: int): + '''Changes the session expiration time for the WebPanel.''' + if not expiration_minutes: + raise InvalidInputError('Error: Expiration minutes must be provided.') + run_cmd( + ['bash', Command.SHELL_WEBPANEL.value, 'changeexp', str(expiration_minutes)] + ) + + +def change_webpanel_root_path(root_path: str | None = None): + '''Changes the root path for the WebPanel. A new random path is generated if not provided.''' + cmd_args = ['bash', Command.SHELL_WEBPANEL.value, 'changeroot'] + if root_path: + cmd_args.append(root_path) + run_cmd(cmd_args) + + +def change_webpanel_domain_port(domain: str | None = None, port: int | None = None): + '''Changes the domain and/or port for the WebPanel.''' + if not domain and not port: + raise InvalidInputError('Error: At least a new domain or new port must be provided.') + + cmd_args = ['bash', Command.SHELL_WEBPANEL.value, 'changedomain'] + if domain: + cmd_args.extend(['-d', domain]) + if port: + cmd_args.extend(['-p', str(port)]) + + run_cmd(cmd_args) + def get_services_status() -> dict[str, bool] | None: '''Gets the status of all project services.''' if res := run_cmd(['bash', Command.SERVICES_STATUS.value]): diff --git a/core/scripts/webpanel/webpanel_shell.sh b/core/scripts/webpanel/webpanel_shell.sh index dbc2ee8..012ed83 100644 --- a/core/scripts/webpanel/webpanel_shell.sh +++ b/core/scripts/webpanel/webpanel_shell.sh @@ -99,22 +99,14 @@ EOL # Listen for incoming requests on the specified domain and port $DOMAIN:$PORT { - # Define a route to handle all requests starting with ROOT_PATH('/$ROOT_PATH/') route /$ROOT_PATH/* { - # We don't strip the ROOT_PATH('/$ROOT_PATH/') from the request - # uri strip_prefix /$ROOT_PATH - - # We are proxying all requests under the ROOT_PATH to FastAPI at 127.0.0.1:28260 - # FastAPI handles these requests because we set the 'root_path' parameter in the FastAPI instance. reverse_proxy http://127.0.0.1:28260 } - # Any request that doesn't start with the ROOT_PATH('/$ROOT_PATH/') will be blocked and no response will be sent to the client @blocked { not path /$ROOT_PATH/* } - # Abort the request, effectively dropping the connection without a response for invalid paths abort @blocked } EOL @@ -302,30 +294,20 @@ stop_decoy_site() { cat < "$CADDY_CONFIG_FILE" # Global configuration { - # Disable admin panel of the Caddy admin off - # Disable automatic HTTP to HTTPS redirects so the Caddy won't listen on port 80 (We need this port for other parts of the project) auto_https disable_redirects } # Listen for incoming requests on the specified domain and port $DOMAIN:$PORT { - # Define a route to handle all requests starting with ROOT_PATH('/$ROOT_PATH/') route /$ROOT_PATH/* { - # We don't strip the ROOT_PATH('/$ROOT_PATH/') from the request - # uri strip_prefix /$ROOT_PATH - - # We are proxying all requests under the ROOT_PATH to FastAPI at 127.0.0.1:28260 - # FastAPI handles these requests because we set the 'root_path' parameter in the FastAPI instance. reverse_proxy http://127.0.0.1:28260 } - # Any request that doesn't start with the ROOT_PATH('/$ROOT_PATH/') will be blocked and no response will be sent to the client @blocked { not path /$ROOT_PATH/* } - # Abort the request, effectively dropping the connection without a response for invalid paths abort @blocked } EOL @@ -399,6 +381,136 @@ reset_credentials() { fi } +change_expiration() { + local new_expiration=$1 + + if [ -z "$new_expiration" ]; then + echo -e "${red}Usage: $0 changeexp ${NC}" + exit 1 + fi + + if [ ! -f "$WEBPANEL_ENV_FILE" ]; then + echo -e "${red}Error: Web panel .env file not found. Is the web panel configured?${NC}" + exit 1 + fi + + echo "Updating session expiration to: $new_expiration minutes" + if sudo sed -i "s|^EXPIRATION_MINUTES=.*|EXPIRATION_MINUTES=$new_expiration|" "$WEBPANEL_ENV_FILE"; then + echo "Restarting web panel service to apply changes..." + if systemctl restart hysteria-webpanel.service; then + echo -e "${green}Web panel session expiration updated successfully.${NC}" + else + echo -e "${red}Failed to restart hysteria-webpanel service. Please restart it manually.${NC}" + fi + else + echo -e "${red}Failed to update expiration in $WEBPANEL_ENV_FILE${NC}" + exit 1 + fi +} + +change_root_path() { + local new_root_path=$1 + + if [ ! -f "$WEBPANEL_ENV_FILE" ]; then + echo -e "${red}Error: Web panel .env file not found. Is the web panel configured?${NC}" + exit 1 + fi + + if [ -z "$new_root_path" ]; then + echo "Generating a new random root path..." + new_root_path=$(openssl rand -hex 16) + fi + + echo "Updating root path to: $new_root_path" + if sudo sed -i "s|^ROOT_PATH=.*|ROOT_PATH=$new_root_path|" "$WEBPANEL_ENV_FILE"; then + echo "Updating Caddy configuration..." + update_caddy_file + if [ $? -ne 0 ]; then + echo -e "${red}Error: Failed to update the Caddyfile.${NC}" + exit 1 + fi + + echo "Restarting services to apply changes..." + if systemctl restart hysteria-webpanel.service && systemctl restart hysteria-caddy.service; then + echo -e "${green}Web panel root path updated successfully.${NC}" + echo -n "New URL: " + show_webpanel_url + else + echo -e "${red}Failed to restart services. Please restart them manually.${NC}" + fi + else + echo -e "${red}Failed to update root path in $WEBPANEL_ENV_FILE${NC}" + exit 1 + fi +} + +change_port_domain() { + local new_domain="" + local new_port="" + local changes_made=false + + if [ ! -f "$WEBPANEL_ENV_FILE" ]; then + echo -e "${red}Error: Web panel .env file not found. Is the web panel configured?${NC}" + exit 1 + fi + + OPTIND=1 + while getopts ":d:p:" opt; do + case $opt in + d) new_domain="$OPTARG" ;; + p) new_port="$OPTARG" ;; + \?) echo -e "${red}Invalid option: -$OPTARG${NC}" >&2; exit 1 ;; + :) echo -e "${red}Option -$OPTARG requires an argument.${NC}" >&2; exit 1 ;; + esac + done + + if [ -z "$new_domain" ] && [ -z "$new_port" ]; then + echo -e "${red}Error: At least one option (-d or -p ) must be provided.${NC}" + echo -e "${yellow}Usage: $0 changedomain [-d new_domain] [-p new_port]${NC}" + exit 1 + fi + + if [ -n "$new_domain" ]; then + echo "Updating domain to: $new_domain" + if sudo sed -i "s|^DOMAIN=.*|DOMAIN=$new_domain|" "$WEBPANEL_ENV_FILE"; then + changes_made=true + else + echo -e "${red}Failed to update domain in $WEBPANEL_ENV_FILE${NC}" + exit 1 + fi + fi + + if [ -n "$new_port" ]; then + echo "Updating port to: $new_port" + if sudo sed -i "s|^PORT=.*|PORT=$new_port|" "$WEBPANEL_ENV_FILE"; then + changes_made=true + else + echo -e "${red}Failed to update port in $WEBPANEL_ENV_FILE${NC}" + exit 1 + fi + fi + + if [ "$changes_made" = true ]; then + echo "Updating Caddy configuration..." + update_caddy_file + if [ $? -ne 0 ]; then + echo -e "${red}Error: Failed to update the Caddyfile.${NC}" + exit 1 + fi + + echo "Restarting Caddy service to apply changes..." + if systemctl restart hysteria-caddy.service; then + echo -e "${green}Web panel domain/port updated successfully.${NC}" + echo -n "New URL: " + show_webpanel_url + else + echo -e "${red}Failed to restart Caddy. Please restart it manually.${NC}" + fi + else + echo -e "${yellow}No changes were made.${NC}" + fi +} + show_webpanel_url() { source /etc/hysteria/core/scripts/webpanel/.env local webpanel_url="https://$DOMAIN:$PORT/$ROOT_PATH/" @@ -412,18 +524,18 @@ show_webpanel_api_token() { stop_service() { echo "Stopping Caddy..." - systemctl disable hysteria-caddy.service - systemctl stop hysteria-caddy.service + systemctl disable hysteria-caddy.service > /dev/null 2>&1 + systemctl stop hysteria-caddy.service > /dev/null 2>&1 echo "Caddy stopped." echo "Stopping Hysteria web panel..." - systemctl disable hysteria-webpanel.service - systemctl stop hysteria-webpanel.service + systemctl disable hysteria-webpanel.service > /dev/null 2>&1 + systemctl stop hysteria-webpanel.service > /dev/null 2>&1 echo "Hysteria web panel stopped." systemctl daemon-reload - rm /etc/hysteria/core/scripts/webpanel/.env - rm "$CADDY_CONFIG_FILE" + rm -f /etc/hysteria/core/scripts/webpanel/.env + rm -f "$CADDY_CONFIG_FILE" } case "$1" in @@ -451,6 +563,16 @@ case "$1" in shift reset_credentials "$@" ;; + changeexp) + change_expiration "$2" + ;; + changeroot) + change_root_path "$2" + ;; + changedomain) + shift + change_port_domain "$@" + ;; url) show_webpanel_url ;; @@ -458,12 +580,15 @@ case "$1" in show_webpanel_api_token ;; *) - echo -e "${red}Usage: $0 {start|stop|decoy|stopdecoy|url|api-token} [options]${NC}" + echo -e "${red}Usage: $0 {start|stop|decoy|stopdecoy|resetcreds|changeexp|changeroot|changedomain|url|api-token} [options]${NC}" echo -e "${yellow}start [ADMIN_USERNAME] [ADMIN_PASSWORD] [EXPIRATION_MINUTES] [DEBUG] [DECOY_PATH]${NC}" echo -e "${yellow}stop${NC}" echo -e "${yellow}decoy ${NC}" echo -e "${yellow}stopdecoy${NC}" - echo -e "${yellow} resetcreds [-u new_username] [-p new_password]${NC}" + echo -e "${yellow}resetcreds [-u new_username] [-p new_password]${NC}" + echo -e "${yellow}changeexp ${NC}" + echo -e "${yellow}changeroot [NEW_ROOT_PATH] # Generates random if not provided${NC}" + echo -e "${yellow}changedomain [-d new_domain] [-p new_port]${NC}" echo -e "${yellow}url${NC}" echo -e "${yellow}api-token${NC}" exit 1 diff --git a/menu.sh b/menu.sh index 3e37b7d..f677eed 100644 --- a/menu.sh +++ b/menu.sh @@ -688,6 +688,9 @@ webpanel_handler() { echo -e "${cyan}3.${NC} Get WebPanel URL" echo -e "${cyan}4.${NC} Show API Token" echo -e "${yellow}5.${NC} Reset WebPanel Credentials" + echo -e "${yellow}6.${NC} Change Domain/Port" + echo -e "${yellow}7.${NC} Change Root Path" + echo -e "${yellow}8.${NC} Change Session Expiration" echo "0. Back" read -p "Choose an option: " option @@ -784,6 +787,57 @@ webpanel_handler() { fi fi ;; + 6) + if ! systemctl is-active --quiet hysteria-webpanel.service; then + echo -e "${red}WebPanel service is not running. Cannot perform this action.${NC}" + else + read -e -p "Enter new domain (leave blank to keep current): " new_domain + read -e -p "Enter new port (leave blank to keep current): " new_port + + if [ -z "$new_domain" ] && [ -z "$new_port" ]; then + echo -e "${yellow}No changes specified. Aborting.${NC}" + else + local cmd_args=() + if [ -n "$new_domain" ]; then + cmd_args+=("--domain" "$new_domain") + fi + if [ -n "$new_port" ]; then + cmd_args+=("--port" "$new_port") + fi + echo "Attempting to change domain/port..." + python3 "$CLI_PATH" change-webpanel-domain-port "${cmd_args[@]}" + fi + fi + ;; + 7) + if ! systemctl is-active --quiet hysteria-webpanel.service; then + echo -e "${red}WebPanel service is not running. Cannot perform this action.${NC}" + else + read -e -p "Enter new root path (leave blank for random): " new_root_path + local cmd_args=() + if [ -n "$new_root_path" ]; then + cmd_args+=("--path" "$new_root_path") + fi + echo "Attempting to change root path..." + python3 "$CLI_PATH" change-webpanel-root "${cmd_args[@]}" + fi + ;; + 8) + if ! systemctl is-active --quiet hysteria-webpanel.service; then + echo -e "${red}WebPanel service is not running. Cannot perform this action.${NC}" + else + while true; do + read -e -p "Enter new session expiration in minutes: " new_minutes + if [[ "$new_minutes" =~ ^[0-9]+$ ]]; then + break + else + echo -e "${red}Error:${NC} Please enter a valid number." + fi + done + echo "Attempting to change session expiration..." + python3 "$CLI_PATH" change-webpanel-exp --minutes "$new_minutes" + fi + ;; 0) break ;; @@ -1162,4 +1216,4 @@ advance_menu() { } # Main function to run the script define_colors -main_menu +main_menu \ No newline at end of file