Merge pull request #132 from ReturnFI/beta

SNI checker
This commit is contained in:
Whispering Wind
2025-04-25 11:38:22 +03:30
committed by GitHub
8 changed files with 133 additions and 59 deletions

View File

@ -1,9 +1,6 @@
## [1.7.0] - 2025-04-19
## [1.7.1] - 2025-04-25
### 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

View File

@ -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 - <<EOF
# import base64
# import binascii
# # Hexadecimal string
# hex_string = "$fingerprint"
# # Convert hex to binary
# binary_data = binascii.unhexlify(hex_string)
# # Encode binary data to base64
# base64_encoded = base64.b64encode(binary_data).decode('utf-8')
# # Print the result prefixed with 'sha256/'
# print('sha256/' + base64_encoded)
# EOF
# )
echo "SHA-256 fingerprint generated: $sha256"
if [ -f "$CONFIG_FILE" ]; then
@ -50,9 +94,7 @@ update_sni() {
fi
if [ -f "$CONFIG_ENV" ]; then
if grep -q "^SNI=" "$CONFIG_ENV"; then
sed -i "s/^SNI=.*$/SNI=$sni/" "$CONFIG_ENV"
echo "SNI updated successfully in $CONFIG_ENV"
else
@ -66,6 +108,15 @@ update_sni() {
python3 "$CLI_PATH" restart-hysteria2 > /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"

View File

@ -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))

View File

@ -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):

View File

@ -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."""
@ -66,11 +61,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 +76,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 +133,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 +145,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.")

View File

@ -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('.')]

View File

@ -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:

21
core/scripts/paths.py Normal file
View File

@ -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"