Refactor: Implement Change SNI in Python

This commit is contained in:
Whispering Wind
2025-05-05 23:55:36 +03:30
committed by GitHub
parent ce4d23d18e
commit 24338dfe1f
3 changed files with 214 additions and 136 deletions

View File

@ -21,7 +21,7 @@ class Command(Enum):
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.py') UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.py')
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.py') RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.py')
CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.py') CHANGE_PORT_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_port.py')
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.sh') CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.py')
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py') GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py')
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py') ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh') EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
@ -176,7 +176,7 @@ def change_hysteria2_sni(sni: str):
''' '''
Changes the SNI for Hysteria2. Changes the SNI for Hysteria2.
''' '''
run_cmd(['bash', Command.CHANGE_SNI_HYSTERIA2.value, sni]) run_cmd(['python3', Command.CHANGE_SNI_HYSTERIA2.value, sni])
def backup_hysteria2(): def backup_hysteria2():

View File

@ -0,0 +1,212 @@
#!/usr/bin/env python3
import os
import sys
import json
import time
import subprocess
import socket
from pathlib import Path
from init_paths import *
from paths import *
def run_command(command, capture_output=True, shell=True):
"""Run a shell command and return its output"""
result = subprocess.run(
command,
shell=shell,
capture_output=capture_output,
text=True
)
if capture_output:
return result.stdout.strip()
return None
def get_ip_from_domain(domain):
"""Get the first IPv4 address from a domain using dig"""
try:
output = run_command(f"dig +short {domain} A | head -n 1")
if output and is_valid_ipv4(output):
return output
except:
pass
return None
def is_valid_ipv4(ip):
"""Check if a string is a valid IPv4 address"""
try:
socket.inet_pton(socket.AF_INET, ip)
return True
except (socket.error, ValueError):
return False
def get_server_ip():
"""Get the server's public IP address"""
return run_command("curl -s -4 ifconfig.me")
def update_sni(sni):
if not sni:
print("Invalid SNI. Please provide a valid SNI.")
print(f"Example: {sys.argv[0]} yourdomain.com")
return 1
if os.path.isfile(CONFIG_ENV):
env_vars = {}
with open(CONFIG_ENV, 'r') as f:
for line in f:
if '=' in line:
name, value = line.strip().split('=', 1)
env_vars[name] = value
else:
print(f"Error: Config file {CONFIG_ENV} not found.")
return 1
server_ip = None
if 'IP4' in env_vars:
ip4 = env_vars['IP4']
if is_valid_ipv4(ip4):
server_ip = ip4
print(f"Using server IP from config: {server_ip}")
else:
domain_ip = get_ip_from_domain(ip4)
if domain_ip:
server_ip = domain_ip
print(f"Resolved domain {ip4} to IP: {server_ip}")
else:
server_ip = get_server_ip()
print(f"Could not resolve domain {ip4}. Using auto-detected server IP: {server_ip}")
else:
server_ip = get_server_ip()
print(f"Using auto-detected server IP: {server_ip}")
print(f"Checking if {sni} points to this server ({server_ip})...")
domain_ip = get_ip_from_domain(sni)
use_certbot = False
if not domain_ip:
print(f"Warning: Could not resolve {sni} to an IPv4 address.")
elif domain_ip == server_ip:
print(f"Success: {sni} correctly points to this server ({server_ip}).")
use_certbot = True
else:
print(f"Notice: {sni} points to {domain_ip}, not to this server ({server_ip}).")
os.chdir('/etc/hysteria/')
if use_certbot:
print(f"Using certbot to obtain a valid certificate for {sni}...")
certbot_output = run_command(f"certbot certificates")
if sni in certbot_output:
print(f"Certificate for {sni} already exists. Renewing...")
run_command(f"certbot renew --cert-name {sni}", capture_output=False)
else:
print(f"Requesting new certificate for {sni}...")
run_command(f"certbot certonly --standalone -d {sni} --non-interactive --agree-tos --email admin@{sni}",
capture_output=False)
run_command(f"cp /etc/letsencrypt/live/{sni}/fullchain.pem /etc/hysteria/ca.crt", capture_output=False)
run_command(f"cp /etc/letsencrypt/live/{sni}/privkey.pem /etc/hysteria/ca.key", capture_output=False)
print("Certificates successfully installed from Let's Encrypt.")
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
config['tls']['insecure'] = False
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
print(f"TLS insecure flag set to false in {CONFIG_FILE}")
else:
print(f"Using self-signed certificate with openssl for {sni}...")
if os.path.exists("ca.key"):
os.remove("ca.key")
if os.path.exists("ca.crt"):
os.remove("ca.crt")
print(f"Generating CA key and certificate for SNI: {sni} ...")
run_command("openssl ecparam -genkey -name prime256v1 -out ca.key > /dev/null 2>&1", capture_output=False)
run_command(f"openssl req -new -x509 -days 36500 -key ca.key -out ca.crt -subj '/CN={sni}' > /dev/null 2>&1",
capture_output=False)
print(f"Self-signed certificate generated for {sni}")
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
config['tls']['insecure'] = True
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
print(f"TLS insecure flag set to true in {CONFIG_FILE}")
run_command("chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt", capture_output=False)
run_command("chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt", capture_output=False)
sha256 = run_command(
"openssl x509 -noout -fingerprint -sha256 -inform pem -in ca.crt | sed 's/.*=//;s///g'"
)
print(f"SHA-256 fingerprint generated: {sha256}")
if os.path.isfile(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
config['tls']['pinSHA256'] = sha256
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
print(f"SHA-256 updated successfully in {CONFIG_FILE}")
else:
print(f"Error: Config file {CONFIG_FILE} not found.")
return 1
sni_found = False
if os.path.isfile(CONFIG_ENV):
with open(CONFIG_ENV, 'r') as f:
lines = f.readlines()
with open(CONFIG_ENV, 'w') as f:
for line in lines:
if line.startswith('SNI='):
f.write(f'SNI={sni}\n')
sni_found = True
else:
f.write(line)
if not sni_found:
f.write(f'SNI={sni}\n')
print(f"Added new SNI entry to {CONFIG_ENV}")
else:
print(f"SNI updated successfully in {CONFIG_ENV}")
else:
with open(CONFIG_ENV, 'w') as f:
f.write(f'SNI={sni}\n')
print(f"Created {CONFIG_ENV} with new SNI.")
run_command(f"python3 {CLI_PATH} restart-hysteria2 > /dev/null 2>&1", capture_output=False)
print(f"Hysteria2 restarted successfully with new SNI: {sni}.")
if use_certbot:
print(f"✅ Valid Let's Encrypt certificate installed for {sni}")
print(" TLS insecure mode is now DISABLED")
else:
print(f"⚠️ Self-signed certificate installed for {sni}")
print(" TLS insecure mode is now ENABLED")
print(" (This certificate won't be trusted by browsers)")
return 0
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <sni>")
sys.exit(1)
sni = sys.argv[1]
sys.exit(update_sni(sni))

View File

@ -1,134 +0,0 @@
#!/bin/bash
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
if [ -n "$IP4" ]; then
if [[ $IP4 =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
server_ip="$IP4"
echo "Using server IP from config: $server_ip"
else
domain_ip=$(dig +short "$IP4" A | head -n 1)
if [ -n "$domain_ip" ]; then
server_ip="$domain_ip"
echo "Resolved domain $IP4 to IP: $server_ip"
else
server_ip=$(curl -s -4 ifconfig.me)
echo "Could not resolve domain $IP4. Using auto-detected server IP: $server_ip"
fi
fi
else
server_ip=$(curl -s -4 ifconfig.me)
echo "Using auto-detected server IP: $server_ip"
fi
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')
echo "SHA-256 fingerprint generated: $sha256"
if [ -f "$CONFIG_FILE" ]; then
jq --arg sha256 "$sha256" '.tls.pinSHA256 = $sha256' "$CONFIG_FILE" > "${CONFIG_FILE}.temp" && mv "${CONFIG_FILE}.temp" "$CONFIG_FILE"
echo "SHA-256 updated successfully in $CONFIG_FILE"
else
echo "Error: Config file $CONFIG_FILE not found."
return 1
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
echo "SNI=$sni" >> "$CONFIG_ENV"
echo "Added new SNI entry to $CONFIG_ENV"
fi
else
echo "SNI=$sni" > "$CONFIG_ENV"
echo "Created $CONFIG_ENV with new SNI."
fi
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 "$sni"