From ff194d0098517960dac2d535132f98b39241a27f Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:17:28 +0330 Subject: [PATCH 1/4] Changes: feat: Add SNI checker and certificate manager Key features: - Domain-to-IP resolution verification - Automatic Let's Encrypt certificate acquisition - Self-signed fallback for domains not pointed to the server - Updates insecure flag in config.json based on certificate type - Updates SNI in environment config - Generates and updates SHA-256 fingerprint fix(urls): Update URI generator to respect TLS insecure flag Changes: - Added insecure parameter to generate_uri() function - Read TLS insecure flag from config.json - Set insecure=0 for valid certificates and insecure=1 for self-signed ones - Updated all URI generation calls to include the insecure parameter --- core/scripts/hysteria2/change_sni.sh | 109 +++++++++++++++++------- core/scripts/hysteria2/show_user_uri.py | 16 ++-- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/core/scripts/hysteria2/change_sni.sh b/core/scripts/hysteria2/change_sni.sh index 2f590ea..a00affb 100644 --- a/core/scripts/hysteria2/change_sni.sh +++ b/core/scripts/hysteria2/change_sni.sh @@ -1,44 +1,88 @@ #!/bin/bash -# Source the necessary paths source /etc/hysteria/core/scripts/path.sh +sni="$1" + +if [ -f "$CONFIG_ENV" ]; then + source "$CONFIG_ENV" +else + echo "Error: Config file $CONFIG_ENV not found." + exit 1 +fi update_sni() { local sni=$1 - + local server_ip + if [ -z "$sni" ]; then echo "Invalid SNI. Please provide a valid SNI." + echo "Example: $0 yourdomain.com" return 1 fi - cd /etc/hysteria/ || exit - rm -f ca.key ca.crt + if [ -n "$IP4" ]; then + server_ip="$IP4" + echo "Using server IP from config: $server_ip" + else + server_ip=$(curl -s ifconfig.me) + echo "Using auto-detected server IP: $server_ip" + fi - echo "Generating CA key and certificate for SNI: $sni ..." - openssl ecparam -genkey -name prime256v1 -out ca.key >/dev/null 2>&1 - openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/CN=$sni" >/dev/null 2>&1 + echo "Checking if $sni points to this server ($server_ip)..." + domain_ip=$(dig +short "$sni" A | head -n 1) + + if [ -z "$domain_ip" ]; then + echo "Warning: Could not resolve $sni to an IPv4 address." + use_certbot=false + elif [ "$domain_ip" = "$server_ip" ]; then + echo "Success: $sni correctly points to this server ($server_ip)." + use_certbot=true + else + echo "Notice: $sni points to $domain_ip, not to this server ($server_ip)." + use_certbot=false + fi + + cd /etc/hysteria/ || exit + + if [ "$use_certbot" = true ]; then + echo "Using certbot to obtain a valid certificate for $sni..." + + if certbot certificates | grep -q "$sni"; then + echo "Certificate for $sni already exists. Renewing..." + certbot renew --cert-name "$sni" + else + echo "Requesting new certificate for $sni..." + certbot certonly --standalone -d "$sni" --non-interactive --agree-tos --email admin@"$sni" + fi + + cp /etc/letsencrypt/live/"$sni"/fullchain.pem /etc/hysteria/ca.crt + cp /etc/letsencrypt/live/"$sni"/privkey.pem /etc/hysteria/ca.key + + echo "Certificates successfully installed from Let's Encrypt." + + if [ -f "$CONFIG_FILE" ]; then + jq '.tls.insecure = false' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" + echo "TLS insecure flag set to false in $CONFIG_FILE" + fi + else + echo "Using self-signed certificate with openssl for $sni..." + rm -f ca.key ca.crt + + echo "Generating CA key and certificate for SNI: $sni ..." + openssl ecparam -genkey -name prime256v1 -out ca.key >/dev/null 2>&1 + openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/CN=$sni" >/dev/null 2>&1 + echo "Self-signed certificate generated for $sni" + + if [ -f "$CONFIG_FILE" ]; then + jq '.tls.insecure = true' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE" + echo "TLS insecure flag set to true in $CONFIG_FILE" + fi + fi + chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt + sha256=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s///g') - -# sha256=$(python3 - < /dev/null 2>&1 echo "Hysteria2 restarted successfully with new SNI: $sni." + + if [ "$use_certbot" = true ]; then + echo "โœ… Valid Let's Encrypt certificate installed for $sni" + echo " TLS insecure mode is now DISABLED" + else + echo "โš ๏ธ Self-signed certificate installed for $sni" + echo " TLS insecure mode is now ENABLED" + echo " (This certificate won't be trusted by browsers)" + fi } -update_sni "$1" +update_sni "$sni" \ No newline at end of file diff --git a/core/scripts/hysteria2/show_user_uri.py b/core/scripts/hysteria2/show_user_uri.py index b5c68e1..ff11d52 100644 --- a/core/scripts/hysteria2/show_user_uri.py +++ b/core/scripts/hysteria2/show_user_uri.py @@ -66,11 +66,10 @@ def is_service_active(service_name: str) -> bool: return False def generate_uri(username: str, auth_password: str, ip: str, port: str, - obfs_password: str, sha256: str, sni: str, ip_version: int) -> str: + obfs_password: str, sha256: str, sni: str, ip_version: int, insecure: bool) -> str: """Generate Hysteria2 URI for the given parameters.""" uri_base = f"hy2://{username}%3A{auth_password}@{ip}:{port}" - # Handle IPv6 address formatting if ip_version == 6 and re.match(r'^[0-9a-fA-F:]+$', ip): uri_base = f"hy2://{username}%3A{auth_password}@[{ip}]:{port}" @@ -82,7 +81,8 @@ def generate_uri(username: str, auth_password: str, ip: str, port: str, if sha256: params.append(f"pinSHA256={sha256}") - params.append(f"insecure=1&sni={sni}") + insecure_value = "1" if insecure else "0" + params.append(f"insecure={insecure_value}&sni={sni}") params_str = "&".join(params) return f"{uri_base}?{params_str}#{username}-IPv{ip_version}" @@ -138,6 +138,8 @@ def show_uri(args: argparse.Namespace) -> None: sha256 = config.get("tls", {}).get("pinSHA256", "") obfs_password = config.get("obfs", {}).get("salamander", {}).get("password", "") + insecure = config.get("tls", {}).get("insecure", True) + ip4, ip6, sni = load_hysteria2_ips() available_ip4 = ip4 and ip4 != "None" available_ip6 = ip6 and ip6 != "None" @@ -148,21 +150,21 @@ def show_uri(args: argparse.Namespace) -> None: if args.all: if available_ip4: uri_ipv4 = generate_uri(args.username, auth_password, ip4, port, - obfs_password, sha256, sni, 4) + obfs_password, sha256, sni, 4, insecure) print(f"\nIPv4:\n{uri_ipv4}\n") if available_ip6: uri_ipv6 = generate_uri(args.username, auth_password, ip6, port, - obfs_password, sha256, sni, 6) + obfs_password, sha256, sni, 6, insecure) print(f"\nIPv6:\n{uri_ipv6}\n") else: if args.ip_version == 4 and available_ip4: uri_ipv4 = generate_uri(args.username, auth_password, ip4, port, - obfs_password, sha256, sni, 4) + obfs_password, sha256, sni, 4, insecure) print(f"\nIPv4:\n{uri_ipv4}\n") elif args.ip_version == 6 and available_ip6: uri_ipv6 = generate_uri(args.username, auth_password, ip6, port, - obfs_password, sha256, sni, 6) + obfs_password, sha256, sni, 6, insecure) print(f"\nIPv6:\n{uri_ipv6}\n") else: print("Invalid IP version or no available IP for the requested version.") From 748d0bf2bdf88580203b011f8d5eb0637e01251f Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Thu, 24 Apr 2025 20:23:31 +0330 Subject: [PATCH 2/4] add init_paths to handle sys.path setup for importing shared modules like paths.py --- core/scripts/hysteria2/init_paths.py | 7 +++++++ core/scripts/hysteria2/kickuser.py | 5 +++-- core/scripts/hysteria2/show_user_uri.py | 11 +++-------- core/scripts/hysteria2/version.py | 6 ++---- core/scripts/hysteria2/wrapper_uri.py | 6 ++++-- core/scripts/paths.py | 21 +++++++++++++++++++++ 6 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 core/scripts/hysteria2/init_paths.py create mode 100644 core/scripts/paths.py diff --git a/core/scripts/hysteria2/init_paths.py b/core/scripts/hysteria2/init_paths.py new file mode 100644 index 0000000..118c17b --- /dev/null +++ b/core/scripts/hysteria2/init_paths.py @@ -0,0 +1,7 @@ +import sys +from pathlib import Path + +core_scripts_dir = Path(__file__).resolve().parents[1] + +if str(core_scripts_dir) not in sys.path: + sys.path.append(str(core_scripts_dir)) diff --git a/core/scripts/hysteria2/kickuser.py b/core/scripts/hysteria2/kickuser.py index 465e86c..140fd14 100644 --- a/core/scripts/hysteria2/kickuser.py +++ b/core/scripts/hysteria2/kickuser.py @@ -6,8 +6,9 @@ import sys import os from hysteria2_api import Hysteria2Client, Hysteria2Error -CONFIG_FILE = '/etc/hysteria/config.json' -API_BASE_URL = 'http://127.0.0.1:25413' +from init_paths import * +from paths import * + def get_api_secret(config_path: str) -> str: if not os.path.exists(config_path): diff --git a/core/scripts/hysteria2/show_user_uri.py b/core/scripts/hysteria2/show_user_uri.py index ff11d52..11f142b 100644 --- a/core/scripts/hysteria2/show_user_uri.py +++ b/core/scripts/hysteria2/show_user_uri.py @@ -7,13 +7,8 @@ import subprocess import argparse import re from typing import Tuple, Optional, Dict, List, Any - -CORE_DIR = "/etc/hysteria" -CONFIG_FILE = f"{CORE_DIR}/config.json" -USERS_FILE = f"{CORE_DIR}/users.json" -HYSTERIA2_ENV = f"{CORE_DIR}/.configs.env" -SINGBOX_ENV = f"{CORE_DIR}/core/scripts/singbox/.env" -NORMALSUB_ENV = f"{CORE_DIR}/core/scripts/normalsub/.env" +from init_paths import * +from paths import * def load_env_file(env_file: str) -> Dict[str, str]: """Load environment variables from a file into a dictionary.""" @@ -29,7 +24,7 @@ def load_env_file(env_file: str) -> Dict[str, str]: def load_hysteria2_env() -> Dict[str, str]: """Load Hysteria2 environment variables.""" - return load_env_file(HYSTERIA2_ENV) + return load_env_file(CONFIG_ENV) def load_hysteria2_ips() -> Tuple[str, str, str]: """Load Hysteria2 IPv4 and IPv6 addresses from environment.""" diff --git a/core/scripts/hysteria2/version.py b/core/scripts/hysteria2/version.py index b17cc02..ac623c7 100644 --- a/core/scripts/hysteria2/version.py +++ b/core/scripts/hysteria2/version.py @@ -4,10 +4,8 @@ import os import sys import requests from pathlib import Path - -LOCALVERSION = "/etc/hysteria/VERSION" -LATESTVERSION = "https://raw.githubusercontent.com/ReturnFI/Blitz/main/VERSION" -LASTESTCHANGE = "https://raw.githubusercontent.com/ReturnFI/Blitz/main/changelog" +from init_paths import * +from paths import * def version_greater_equal(version1, version2): version1_parts = [int(part) for part in version1.strip().split('.')] diff --git a/core/scripts/hysteria2/wrapper_uri.py b/core/scripts/hysteria2/wrapper_uri.py index 4f3342d..f898f9d 100644 --- a/core/scripts/hysteria2/wrapper_uri.py +++ b/core/scripts/hysteria2/wrapper_uri.py @@ -4,12 +4,14 @@ import re import json import sys -SHOW_URI_SCRIPT = "/etc/hysteria/core/scripts/hysteria2/show_user_uri.py" +from init_paths import * +from paths import * + DEFAULT_ARGS = ["-a", "-n", "-s"] def run_show_uri(username): try: - cmd = ["python3", SHOW_URI_SCRIPT, "-u", username] + DEFAULT_ARGS + cmd = ["python3", CLI_PATH, "show-user-uri", "-u", username] + DEFAULT_ARGS result = subprocess.run(cmd, capture_output=True, text=True, check=True) output = result.stdout if "Invalid username" in output: diff --git a/core/scripts/paths.py b/core/scripts/paths.py new file mode 100644 index 0000000..850499e --- /dev/null +++ b/core/scripts/paths.py @@ -0,0 +1,21 @@ +from pathlib import Path + +BASE_DIR = Path("/etc/hysteria") + +CLI_PATH = BASE_DIR / "core/cli.py" +USERS_FILE = BASE_DIR / "users.json" +TRAFFIC_FILE = BASE_DIR / "traffic_data.json" +CONFIG_FILE = BASE_DIR / "config.json" +CONFIG_ENV = BASE_DIR / ".configs.env" +TELEGRAM_ENV = BASE_DIR / "core/scripts/telegrambot/.env" +SINGBOX_ENV = BASE_DIR / "core/scripts/singbox/.env" +NORMALSUB_ENV = BASE_DIR / "core/scripts/normalsub/.env" +WEBPANEL_ENV = BASE_DIR / "core/scripts/webpanel/.env" +API_BASE_URL = "http://127.0.0.1:25413" +ONLINE_API_URL = "http://127.0.0.1:25413/online" +LOCALVERSION = BASE_DIR / "VERSION" +LATESTVERSION = "https://raw.githubusercontent.com/ReturnFI/Blitz/main/VERSION" +LASTESTCHANGE = "https://raw.githubusercontent.com/ReturnFI/Blitz/main/changelog" +CONNECTIONS_FILE = BASE_DIR / "hysteria_connections.json" +BLOCK_LIST = Path("/tmp/hysteria_blocked_ips.txt") +SCRIPT_PATH = BASE_DIR / "core/scripts/hysteria2/limit.sh" From 782049d384895fa1f0357cf240f821d7c2b47cc0 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:35:51 +0330 Subject: [PATCH 3/4] Update changelog --- changelog | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/changelog b/changelog index 5131a10..fde6e87 100644 --- a/changelog +++ b/changelog @@ -1,9 +1,6 @@ -## [1.7.0] - 2025-04-19 +## [1.7.1] - 2025-04-24 ### Changed -- ๐Ÿงช feat: Add show-user-uri-json CLI command -- ๐ŸŒ feat: Introduce user URI API endpoint -- ๐Ÿ–ฅ๏ธ feat: Integrate show_user_uri_api into the Users page -- ๐Ÿงน refactor: Move URI generation logic from ViewModel to backend logic -- ๐Ÿ“ฆ refactor: Rewrite show-user-uri to Python for consistency -- โš™๏ธ optimize: Improve server_info.sh for better performance and lower resource usage +- feat: Add init_paths to handle sys.path setup for importing shared modules +- feat: Add insecure parameter to generate_uri() function +- feat: Add SNI checker and certificate manager From e0191149b8e672a969c10adfe3f2239a4588a715 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:37:14 +0330 Subject: [PATCH 4/4] Update changelog --- changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog b/changelog index fde6e87..f34e965 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,4 @@ -## [1.7.1] - 2025-04-24 +## [1.7.1] - 2025-04-25 ### Changed - feat: Add init_paths to handle sys.path setup for importing shared modules