feat: Implement subpath support for normal subscriptions
This commit is contained in:
@ -19,7 +19,8 @@ get_normalsub_domain_and_port() {
|
|||||||
local domain port
|
local domain port
|
||||||
domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2)
|
domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2)
|
||||||
port=$(grep -E '^HYSTERIA_PORT=' "$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
|
else
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
@ -145,9 +146,9 @@ show_uri() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ "$generate_normalsub" = true ] && systemctl is-active --quiet hysteria-normal-sub.service; then
|
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
|
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
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from io import BytesIO
|
|||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.web_middlewares import middleware
|
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
|
from dotenv import load_dotenv
|
||||||
import qrcode
|
import qrcode
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
@ -36,6 +36,7 @@ class AppConfig:
|
|||||||
rate_limit_window: int
|
rate_limit_window: int
|
||||||
sni: str
|
sni: str
|
||||||
template_dir: str
|
template_dir: str
|
||||||
|
subpath: str
|
||||||
|
|
||||||
|
|
||||||
class RateLimiter:
|
class RateLimiter:
|
||||||
@ -178,6 +179,11 @@ class Utils:
|
|||||||
size /= 1024
|
size /= 1024
|
||||||
return f"{size:.2f} PB"
|
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:
|
class HysteriaCLI:
|
||||||
"""Interface for Hysteria CLI commands"""
|
"""Interface for Hysteria CLI commands"""
|
||||||
@ -426,8 +432,9 @@ class HysteriaServer:
|
|||||||
self.template_renderer = TemplateRenderer(self.config.template_dir, self.config)
|
self.template_renderer = TemplateRenderer(self.config.template_dir, self.config)
|
||||||
|
|
||||||
self.app = web.Application(middlewares=[self._rate_limit_middleware])
|
self.app = web.Application(middlewares=[self._rate_limit_middleware])
|
||||||
self.app.add_routes([web.get('/sub/normal/{username}', self.handle)])
|
self.app.add_routes([web.get(Utils.build_url('/{subpath}/sub/normal/', '{username}'), self.handle)])
|
||||||
self.app.router.add_route('*', '/sub/normal/{tail:.*}', self.handle_404)
|
self.app.router.add_route('*', '/{subpath:[^{}]+}/{tail:.*}', self.handle_404)
|
||||||
|
self.app.router.add_route('*', '/{tail:.*}', self.handle_404)
|
||||||
|
|
||||||
def _load_config(self) -> AppConfig:
|
def _load_config(self) -> AppConfig:
|
||||||
"""Loads application configuration from environment variables"""
|
"""Loads application configuration from environment variables"""
|
||||||
@ -435,6 +442,7 @@ class HysteriaServer:
|
|||||||
cert_file = os.getenv('HYSTERIA_CERTFILE')
|
cert_file = os.getenv('HYSTERIA_CERTFILE')
|
||||||
key_file = os.getenv('HYSTERIA_KEYFILE')
|
key_file = os.getenv('HYSTERIA_KEYFILE')
|
||||||
port = int(os.getenv('HYSTERIA_PORT', '3326'))
|
port = int(os.getenv('HYSTERIA_PORT', '3326'))
|
||||||
|
subpath = os.getenv('SUBPATH', '').strip().strip("/")
|
||||||
sni_file = '/etc/hysteria/.configs.env'
|
sni_file = '/etc/hysteria/.configs.env'
|
||||||
singbox_template_path = '/etc/hysteria/core/scripts/normalsub/singbox.json'
|
singbox_template_path = '/etc/hysteria/core/scripts/normalsub/singbox.json'
|
||||||
hysteria_cli_path = '/etc/hysteria/core/cli.py'
|
hysteria_cli_path = '/etc/hysteria/core/cli.py'
|
||||||
@ -455,7 +463,8 @@ class HysteriaServer:
|
|||||||
rate_limit=rate_limit,
|
rate_limit=rate_limit,
|
||||||
rate_limit_window=rate_limit_window,
|
rate_limit_window=rate_limit_window,
|
||||||
sni=sni,
|
sni=sni,
|
||||||
template_dir=template_dir
|
template_dir=template_dir,
|
||||||
|
subpath=subpath
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load_sni_from_env(self, sni_file: str) -> str:
|
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:
|
async def handle(self, request: web.Request) -> web.Response:
|
||||||
"""Main request handler"""
|
"""Main request handler"""
|
||||||
try:
|
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_-]+$')
|
username = Utils.sanitize_input(request.match_info.get('username', ''), r'^[a-zA-Z0-9_-]+$')
|
||||||
if not username:
|
if not username:
|
||||||
return web.Response(status=400, text="Error: Missing 'username' parameter.")
|
return web.Response(status=400, text="Error: Missing 'username' parameter.")
|
||||||
|
|
||||||
user_agent = request.headers.get('User-Agent', '').lower()
|
user_agent = request.headers.get('User-Agent', '').lower()
|
||||||
|
|
||||||
if any(browser in user_agent for browser in ['chrome', 'firefox', 'safari', 'edge', 'opera']):
|
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')
|
return web.Response(text=subscription, content_type='text/plain')
|
||||||
|
|
||||||
async def _get_template_context(self, username: str) -> TemplateContext:
|
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)
|
user_info = self.hysteria_cli.get_user_info(username)
|
||||||
ipv4_uri, ipv6_uri = self.hysteria_cli.get_uris(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)
|
ipv4_qrcode = Utils.generate_qrcode_base64(ipv4_uri)
|
||||||
ipv6_qrcode = Utils.generate_qrcode_base64(ipv6_uri)
|
ipv6_qrcode = Utils.generate_qrcode_base64(ipv6_uri)
|
||||||
sublink_qrcode = Utils.generate_qrcode_base64(sub_link)
|
sublink_qrcode = Utils.generate_qrcode_base64(sub_link)
|
||||||
@ -565,4 +575,4 @@ class HysteriaServer:
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
server = HysteriaServer()
|
server = HysteriaServer()
|
||||||
server.run()
|
server.run()
|
||||||
@ -22,6 +22,7 @@ HYSTERIA_DOMAIN=$domain
|
|||||||
HYSTERIA_PORT=$port
|
HYSTERIA_PORT=$port
|
||||||
HYSTERIA_CERTFILE=$cert_dir/fullchain.pem
|
HYSTERIA_CERTFILE=$cert_dir/fullchain.pem
|
||||||
HYSTERIA_KEYFILE=$cert_dir/privkey.pem
|
HYSTERIA_KEYFILE=$cert_dir/privkey.pem
|
||||||
|
SUBPATH=$(pwgen -s 32 1)
|
||||||
EOL
|
EOL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user