Refactor: Implement Change SNI in Python
This commit is contained in:
@ -21,7 +21,7 @@ class Command(Enum):
|
||||
UPDATE_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'update.py')
|
||||
RESTART_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'restart.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')
|
||||
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
|
||||
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.
|
||||
'''
|
||||
run_cmd(['bash', Command.CHANGE_SNI_HYSTERIA2.value, sni])
|
||||
run_cmd(['python3', Command.CHANGE_SNI_HYSTERIA2.value, sni])
|
||||
|
||||
|
||||
def backup_hysteria2():
|
||||
|
||||
212
core/scripts/hysteria2/change_sni.py
Normal file
212
core/scripts/hysteria2/change_sni.py
Normal 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))
|
||||
@ -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"
|
||||
Reference in New Issue
Block a user