From 6c5486eafc37699327eef6c4ed7a45111289b6e3 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 17 May 2025 23:20:04 +0330 Subject: [PATCH 1/9] feat: Add function to edit Normalsub subpath This commit introduces a new 'edit_subpath' command to the normalsub.sh script. This allows users to change the SUBPATH variable in the .env file and automatically restarts the hysteria-normal-sub service to apply the changes. --- core/scripts/normalsub/normalsub.sh | 58 +++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/core/scripts/normalsub/normalsub.sh b/core/scripts/normalsub/normalsub.sh index a02b6ae..ceae80f 100644 --- a/core/scripts/normalsub/normalsub.sh +++ b/core/scripts/normalsub/normalsub.sh @@ -70,7 +70,6 @@ start_service() { systemctl daemon-reload systemctl enable hysteria-normal-sub.service > /dev/null 2>&1 systemctl start hysteria-normal-sub.service > /dev/null 2>&1 - # systemctl restart caddy.service > /dev/null 2>&1 # We stopped caddy service just after its installation systemctl daemon-reload > /dev/null 2>&1 if systemctl is-active --quiet hysteria-normal-sub.service; then @@ -85,12 +84,12 @@ stop_service() { source /etc/hysteria/core/scripts/normalsub/.env fi - # if [ -n "$HYSTERIA_DOMAIN" ]; then - # echo -e "${yellow}Deleting SSL certificate for domain: $HYSTERIA_DOMAIN...${NC}" - # certbot delete --cert-name "$HYSTERIA_DOMAIN" --non-interactive > /dev/null 2>&1 - # else - # echo -e "${red}HYSTERIA_DOMAIN not found in .env. Skipping certificate deletion.${NC}" - # fi + if [ -n "$HYSTERIA_DOMAIN" ]; then + echo -e "${yellow}Deleting SSL certificate for domain: $HYSTERIA_DOMAIN...${NC}" + certbot delete --cert-name "$HYSTERIA_DOMAIN" --non-interactive > /dev/null 2>&1 + else + echo -e "${red}HYSTERIA_DOMAIN not found in .env. Skipping certificate deletion.${NC}" + fi systemctl stop hysteria-normal-sub.service > /dev/null 2>&1 systemctl disable hysteria-normal-sub.service > /dev/null 2>&1 @@ -101,6 +100,38 @@ stop_service() { echo -e "${yellow}normalsub service stopped and disabled. .env file removed.${NC}" } +edit_subpath() { + local new_path="$1" + local env_file="/etc/hysteria/core/scripts/normalsub/.env" + + if [[ ! "$new_path" =~ ^[a-zA-Z0-9]+$ ]]; then + echo -e "${red}Error: New subpath must contain only alphanumeric characters (a-z, A-Z, 0-9) and cannot be empty.${NC}" + exit 1 + fi + + if [ ! -f "$env_file" ]; then + echo -e "${red}Error: .env file ($env_file) not found. Please run the start command first.${NC}" + exit 1 + fi + + if grep -q "^SUBPATH=" "$env_file"; then + sed -i "s|^SUBPATH=.*|SUBPATH=$new_path|" "$env_file" + else + echo "SUBPATH=$new_path" >> "$env_file" + fi + echo -e "${green}SUBPATH updated to $new_path in $env_file.${NC}" + + echo -e "${yellow}Restarting hysteria-normal-sub service...${NC}" + systemctl daemon-reload + systemctl restart hysteria-normal-sub.service + + if systemctl is-active --quiet hysteria-normal-sub.service; then + echo -e "${green}hysteria-normal-sub service restarted successfully.${NC}" + else + echo -e "${red}Error: hysteria-normal-sub service failed to restart. Please check logs.${NC}" + fi +} + case "$1" in start) if [ -z "$2" ] || [ -z "$3" ]; then @@ -112,10 +143,15 @@ case "$1" in stop) stop_service ;; + edit_subpath) + if [ -z "$2" ]; then + echo -e "${red}Usage: $0 edit_subpath ${NC}" + exit 1 + fi + edit_subpath "$2" + ;; *) - echo -e "${red}Usage: $0 {start|stop} ${NC}" + echo -e "${red}Usage: $0 {start | stop | edit_subpath } ${NC}" exit 1 ;; -esac - -define_colors +esac \ No newline at end of file From edcc4e32e278974656b1b1ad6ca4c6473d0de678 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 17 May 2025 23:24:43 +0330 Subject: [PATCH 2/9] feat: Add CLI command to edit NormalSub subpath - 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. --- core/cli.py | 16 ++++++++++++---- core/cli_api.py | 8 ++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/cli.py b/core/cli.py index 8c684f6..6672e5b 100644 --- a/core/cli.py +++ b/core/cli.py @@ -421,10 +421,13 @@ def singbox(action: str, domain: str, port: int): @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): +@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: @@ -434,6 +437,11 @@ def normalsub(action: str, domain: str, port: int): 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) diff --git a/core/cli_api.py b/core/cli_api.py index 4bbc44e..0ac9348 100644 --- a/core/cli_api.py +++ b/core/cli_api.py @@ -510,6 +510,14 @@ def start_normalsub(domain: str, port: int): raise InvalidInputError('Error: Both --domain and --port are required for the start action.') run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'start', domain, str(port)]) +def edit_normalsub_subpath(new_subpath: str): + '''Edits the subpath for NormalSub service.''' + if not new_subpath: + raise InvalidInputError('Error: New subpath cannot be empty.') + if not new_subpath.isalnum(): + raise InvalidInputError('Error: New subpath must contain only alphanumeric characters (a-z, A-Z, 0-9).') + + run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'edit_subpath', new_subpath]) def stop_normalsub(): '''Stops NormalSub.''' From d86087cf745863275e61f2ef26541408919bca91 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 17 May 2025 23:27:17 +0330 Subject: [PATCH 3/9] feat: Add API endpoint to edit NormalSub subpath This commit introduces a new PUT endpoint `/api/v1/config/normalsub/edit_subpath` to allow modification of the NormalSub service's subpath. - Added `EditSubPathInputBody` Pydantic model for request validation. - Implemented the API endpoint in `normalsub.py` router, calling the corresponding `cli_api.edit_normalsub_subpath` function. - Includes error handling for validation and general exceptions. --- .../routers/api/v1/config/normalsub.py | 27 +++++++++++++++++-- .../routers/api/v1/schema/config/normalsub.py | 9 +++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/core/scripts/webpanel/routers/api/v1/config/normalsub.py b/core/scripts/webpanel/routers/api/v1/config/normalsub.py index 93e933f..3a97165 100644 --- a/core/scripts/webpanel/routers/api/v1/config/normalsub.py +++ b/core/scripts/webpanel/routers/api/v1/config/normalsub.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, HTTPException from ..schema.response import DetailResponse -from ..schema.config.normalsub import StartInputBody +from ..schema.config.normalsub import StartInputBody, EditSubPathInputBody import cli_api router = APIRouter() @@ -51,4 +51,27 @@ async def normal_sub_stop_api(): except Exception as e: raise HTTPException(status_code=400, detail=f'Error: {str(e)}') -# TODO: Maybe would be nice to have a status endpoint + +@router.put('/edit_subpath', response_model=DetailResponse, summary='Edit NormalSub Subpath') +async def normal_sub_edit_subpath_api(body: EditSubPathInputBody): + """ + Edits the subpath for the NormalSub service. + + Args: + body (EditSubPathInputBody): The request body containing the new subpath. + + Returns: + DetailResponse: A response object containing a success message indicating + that the NormalSub subpath has been updated successfully. + + Raises: + HTTPException: If there is an error editing the NormalSub subpath, an + HTTPException with status code 400 and error details will be raised. + """ + try: + cli_api.edit_normalsub_subpath(body.subpath) + return DetailResponse(detail=f'Normalsub subpath updated to {body.subpath} successfully.') + except cli_api.InvalidInputError as e: + raise HTTPException(status_code=422, detail=f'Validation Error: {str(e)}') + except Exception as e: + raise HTTPException(status_code=400, detail=f'Error: {str(e)}') \ No newline at end of file diff --git a/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py b/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py index 10933a9..7f791bf 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py +++ b/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py @@ -1,9 +1,8 @@ -from pydantic import BaseModel - -# The StartInputBody is the same as in /hysteria/core/scripts/webpanel/routers/api/v1/schema/config/singbox.py but for /normalsub endpoint -# I'm defining it separately because highly likely it'll be different - +from pydantic import BaseModel, Field class StartInputBody(BaseModel): domain: str port: int + +class EditSubPathInputBody(BaseModel): + subpath: str = Field(..., min_length=1, pattern=r"^[a-zA-Z0-9]+$", description="The new subpath, must be alphanumeric.") \ No newline at end of file From eaef80b80e379a5da70d3f39da6b417bbc45e3ef Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 17 May 2025 23:54:32 +0330 Subject: [PATCH 4/9] feat: Add function to cli_api to get current NormalSub subpath --- core/cli_api.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/cli_api.py b/core/cli_api.py index 0ac9348..ad7a8e2 100644 --- a/core/cli_api.py +++ b/core/cli_api.py @@ -13,6 +13,8 @@ SCRIPT_DIR = '/etc/hysteria/core/scripts' CONFIG_FILE = '/etc/hysteria/config.json' CONFIG_ENV_FILE = '/etc/hysteria/.configs.env' WEBPANEL_ENV_FILE = '/etc/hysteria/core/scripts/webpanel/.env' +NORMALSUB_ENV_FILE = '/etc/hysteria/core/scripts/normalsub/.env' + class Command(Enum): '''Contains path to command's script''' @@ -519,6 +521,18 @@ def edit_normalsub_subpath(new_subpath: str): run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'edit_subpath', new_subpath]) +def get_normalsub_subpath() -> str | None: + '''Retrieves the current SUBPATH for the NormalSub service from its .env file.''' + try: + if not os.path.exists(NORMALSUB_ENV_FILE): + return None + + env_vars = dotenv_values(NORMALSUB_ENV_FILE) + return env_vars.get('SUBPATH') + except Exception as e: + print(f"Error reading NormalSub .env file: {e}") + return None + def stop_normalsub(): '''Stops NormalSub.''' run_cmd(['bash', Command.INSTALL_NORMALSUB.value, 'stop']) From 3cb8022c7ce1e59a4e6f3f1298e447161d1bfe33 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sat, 17 May 2025 23:55:01 +0330 Subject: [PATCH 5/9] feat: Add API endpoint to get current NormalSub subpath - Created `GetSubPathResponse` Pydantic schema for the API response. - Added a new GET endpoint `/api/v1/config/normalsub/subpath` to fetch the current NormalSub subpath using `cli_api.get_normalsub_subpath()`. - Returns the subpath or null if not found/set. --- .../webpanel/routers/api/v1/config/normalsub.py | 15 +++++++++++++-- .../routers/api/v1/schema/config/normalsub.py | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core/scripts/webpanel/routers/api/v1/config/normalsub.py b/core/scripts/webpanel/routers/api/v1/config/normalsub.py index 3a97165..5f26569 100644 --- a/core/scripts/webpanel/routers/api/v1/config/normalsub.py +++ b/core/scripts/webpanel/routers/api/v1/config/normalsub.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, HTTPException from ..schema.response import DetailResponse -from ..schema.config.normalsub import StartInputBody, EditSubPathInputBody +from ..schema.config.normalsub import StartInputBody, EditSubPathInputBody, GetSubPathResponse import cli_api router = APIRouter() @@ -74,4 +74,15 @@ async def normal_sub_edit_subpath_api(body: EditSubPathInputBody): except cli_api.InvalidInputError as e: raise HTTPException(status_code=422, detail=f'Validation Error: {str(e)}') except Exception as e: - raise HTTPException(status_code=400, detail=f'Error: {str(e)}') \ No newline at end of file + raise HTTPException(status_code=400, detail=f'Error: {str(e)}') + +@router.get('/subpath', response_model=GetSubPathResponse, summary='Get Current NormalSub Subpath') +async def normal_sub_get_subpath_api(): + """ + Retrieves the current subpath for the NormalSub service. + """ + try: + current_subpath = cli_api.get_normalsub_subpath() + return GetSubPathResponse(subpath=current_subpath) + except Exception as e: + raise HTTPException(status_code=500, detail=f'Error retrieving subpath: {str(e)}') \ No newline at end of file diff --git a/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py b/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py index 7f791bf..7f2af1f 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py +++ b/core/scripts/webpanel/routers/api/v1/schema/config/normalsub.py @@ -1,8 +1,12 @@ from pydantic import BaseModel, Field +from typing import Optional class StartInputBody(BaseModel): domain: str port: int class EditSubPathInputBody(BaseModel): - subpath: str = Field(..., min_length=1, pattern=r"^[a-zA-Z0-9]+$", description="The new subpath, must be alphanumeric.") \ No newline at end of file + subpath: str = Field(..., min_length=1, pattern=r"^[a-zA-Z0-9]+$", description="The new subpath, must be alphanumeric.") + +class GetSubPathResponse(BaseModel): + subpath: Optional[str] = Field(None, description="The current NormalSub subpath, or null if not set/found.") \ No newline at end of file From ec6610571d5f4e60a4f6559c1e592a0dd57441a8 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Sun, 18 May 2025 11:25:29 +0330 Subject: [PATCH 6/9] feat: Add NormalSub subpath configuration to settings UI Adds a 'Configure' tab to the Normal Subscriptions section in Settings. This tab allows users to view and edit the subpath for the NormalSub service. - Modified settings.html: - Added new tab link and pane for NormalSub subpath configuration. - Tab is only visible and accessible if NormalSub service is active. - Extended JavaScript: - `sendRequest` function now accepts an optional `postSuccessCallback`. - Added `isValidSubPath` for client-side validation. - `updateServiceUI` now manages visibility of the 'Configure' tab and fetches current subpath. - Implemented `fetchNormalSubPath` to get subpath via API. - Implemented `editNormalSubPath` to save subpath via API. - Integrated new input into `validateForm` and added real-time validation. - Updated `updateDecoyStatusUI` and its calls from `setupDecoy`/`stopDecoy` to use the new `postSuccessCallback` pattern for consistency. - Standardized form IDs (e.g., `telegram_form`, `port_form`) for better targeting in `validateForm` and `updateServiceUI`. --- core/scripts/webpanel/templates/settings.html | 268 ++++++++++++------ 1 file changed, 188 insertions(+), 80 deletions(-) diff --git a/core/scripts/webpanel/templates/settings.html b/core/scripts/webpanel/templates/settings.html index 88097bd..5f0d733 100644 --- a/core/scripts/webpanel/templates/settings.html +++ b/core/scripts/webpanel/templates/settings.html @@ -56,7 +56,7 @@ aria-controls='ip-limit' aria-selected='false'> IP Limit -