diff --git a/core/scripts/hysteria2/show_user_uri.sh b/core/scripts/hysteria2/show_user_uri.sh index a4c9685..c86dc50 100644 --- a/core/scripts/hysteria2/show_user_uri.sh +++ b/core/scripts/hysteria2/show_user_uri.sh @@ -19,7 +19,8 @@ get_normalsub_domain_and_port() { local domain port domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2) port=$(grep -E '^HYSTERIA_PORT=' "$NORMALSUB_ENV" | cut -d'=' -f2) - echo "$domain" "$port" + subpath=$(grep -E '^SUBPATH=' "$NORMALSUB_ENV" | cut -d'=' -f2) + echo "$domain" "$port" "$subpath" else echo "" fi @@ -145,9 +146,9 @@ show_uri() { fi fi if [ "$generate_normalsub" = true ] && systemctl is-active --quiet hysteria-normal-sub.service; then - read -r domain port < <(get_normalsub_domain_and_port) + read -r domain port subpath < <(get_normalsub_domain_and_port) if [ -n "$domain" ] && [ -n "$port" ]; then - echo -e "\nNormal-SUB Sublink:\nhttps://$domain:$port/sub/normal/$username#Hysteria2\n" + echo -e "\nNormal-SUB Sublink:\nhttps://$domain:$port/$subpath/sub/normal/$username#Hysteria2\n" fi fi else diff --git a/core/scripts/normalsub/normalsub.py b/core/scripts/normalsub/normalsub.py index a69bc80..c127270 100644 --- a/core/scripts/normalsub/normalsub.py +++ b/core/scripts/normalsub/normalsub.py @@ -13,7 +13,7 @@ from io import BytesIO from aiohttp import web from aiohttp.web_middlewares import middleware -from urllib.parse import unquote, parse_qs, urlparse +from urllib.parse import unquote, parse_qs, urlparse, urljoin from dotenv import load_dotenv import qrcode from jinja2 import Environment, FileSystemLoader @@ -36,6 +36,7 @@ class AppConfig: rate_limit_window: int sni: str template_dir: str + subpath: str class RateLimiter: @@ -178,6 +179,11 @@ class Utils: size /= 1024 return f"{size:.2f} PB" + @staticmethod + def build_url(base: str, path: str) -> str: + """Constructs a URL, handling potential double slashes correctly.""" + return urljoin(base, path) + class HysteriaCLI: """Interface for Hysteria CLI commands""" @@ -426,8 +432,9 @@ class HysteriaServer: self.template_renderer = TemplateRenderer(self.config.template_dir, self.config) self.app = web.Application(middlewares=[self._rate_limit_middleware]) - self.app.add_routes([web.get('/sub/normal/{username}', self.handle)]) - self.app.router.add_route('*', '/sub/normal/{tail:.*}', self.handle_404) + self.app.add_routes([web.get(Utils.build_url('/{subpath}/sub/normal/', '{username}'), self.handle)]) + self.app.router.add_route('*', '/{subpath:[^{}]+}/{tail:.*}', self.handle_404) + self.app.router.add_route('*', '/{tail:.*}', self.handle_404) def _load_config(self) -> AppConfig: """Loads application configuration from environment variables""" @@ -435,6 +442,7 @@ class HysteriaServer: cert_file = os.getenv('HYSTERIA_CERTFILE') key_file = os.getenv('HYSTERIA_KEYFILE') port = int(os.getenv('HYSTERIA_PORT', '3326')) + subpath = os.getenv('SUBPATH', '').strip().strip("/") sni_file = '/etc/hysteria/.configs.env' singbox_template_path = '/etc/hysteria/core/scripts/normalsub/singbox.json' hysteria_cli_path = '/etc/hysteria/core/cli.py' @@ -455,7 +463,8 @@ class HysteriaServer: rate_limit=rate_limit, rate_limit_window=rate_limit_window, sni=sni, - template_dir=template_dir + template_dir=template_dir, + subpath=subpath ) def _load_sni_from_env(self, sni_file: str) -> str: @@ -483,10 +492,10 @@ class HysteriaServer: async def handle(self, request: web.Request) -> web.Response: """Main request handler""" try: + # No need to extract subpath here; aiohttp handles it in the route username = Utils.sanitize_input(request.match_info.get('username', ''), r'^[a-zA-Z0-9_-]+$') if not username: return web.Response(status=400, text="Error: Missing 'username' parameter.") - user_agent = request.headers.get('User-Agent', '').lower() if any(browser in user_agent for browser in ['chrome', 'firefox', 'safari', 'edge', 'opera']): @@ -527,11 +536,12 @@ class HysteriaServer: return web.Response(text=subscription, content_type='text/plain') async def _get_template_context(self, username: str) -> TemplateContext: - """Generates the context for HTML template rendering""" + """Generates the context for HTML template rendering, incorporating subpath""" user_info = self.hysteria_cli.get_user_info(username) ipv4_uri, ipv6_uri = self.hysteria_cli.get_uris(username) - sub_link = f"https://{self.config.domain}:{self.config.port}/sub/normal/{username}" + base_url = f"https://{self.config.domain}:{self.config.port}" + sub_link = Utils.build_url(base_url, f"/{self.config.subpath}/sub/normal/{username}") ipv4_qrcode = Utils.generate_qrcode_base64(ipv4_uri) ipv6_qrcode = Utils.generate_qrcode_base64(ipv6_uri) sublink_qrcode = Utils.generate_qrcode_base64(sub_link) @@ -565,4 +575,4 @@ class HysteriaServer: if __name__ == '__main__': server = HysteriaServer() - server.run() + server.run() \ No newline at end of file diff --git a/core/scripts/normalsub/normalsub.sh b/core/scripts/normalsub/normalsub.sh index e0c447f..2adbafe 100644 --- a/core/scripts/normalsub/normalsub.sh +++ b/core/scripts/normalsub/normalsub.sh @@ -22,6 +22,7 @@ HYSTERIA_DOMAIN=$domain HYSTERIA_PORT=$port HYSTERIA_CERTFILE=$cert_dir/fullchain.pem HYSTERIA_KEYFILE=$cert_dir/privkey.pem +SUBPATH=$(pwgen -s 32 1) EOL }