Template
Wire Hysteria 2 UI with per-server SSL defaults and renew.
Expose domain/email on servers, warn that TCP 80/443 must be free on install, and allow Let's Encrypt re-issue from Hysteria settings. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -62,6 +62,10 @@ class BackupManager:
|
||||
remote_dir = inst_path('/opt/amnezia/telemt')
|
||||
paths['host'] = [remote_dir]
|
||||
paths['container'] = [remote_dir]
|
||||
elif base == 'hysteria':
|
||||
remote_dir = inst_path('/opt/amnezia/hysteria')
|
||||
paths['host'] = [remote_dir]
|
||||
paths['container'] = ['/etc/hysteria']
|
||||
elif base == 'dns':
|
||||
paths['host'] = ['/opt/amnezia/dns']
|
||||
paths['container'] = ['/opt/amnezia/dns']
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Hysteria 2 protocol manager — teddysun/hysteria Docker image.
|
||||
"""
|
||||
Hysteria 2 protocol manager — official tobyxdd/hysteria image (apernet/hysteria).
|
||||
|
||||
Installs Hysteria with Let's Encrypt TLS for an admin-provided domain
|
||||
(certbot standalone on port 80), host networking, BBR, and salamander obfs.
|
||||
@@ -32,7 +32,7 @@ def _rand_token(length=16):
|
||||
class HysteriaManager:
|
||||
PROTOCOL = 'hysteria'
|
||||
CONTAINER_NAME = 'amnezia-hysteria'
|
||||
IMAGE_NAME = 'teddysun/hysteria:latest'
|
||||
IMAGE_NAME = 'tobyxdd/hysteria:v2'
|
||||
CERTBOT_IMAGE = 'certbot/certbot:latest'
|
||||
BASE_DIR = '/opt/amnezia/hysteria'
|
||||
DEFAULT_PORT = 8998
|
||||
@@ -449,7 +449,8 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP
|
||||
f"--network host "
|
||||
f"--cap-add=NET_ADMIN "
|
||||
f"-v {_q(self.base_dir)}:/etc/hysteria "
|
||||
f"{self.IMAGE_NAME}"
|
||||
f"{self.IMAGE_NAME} "
|
||||
f"-c /etc/hysteria/server.yaml server"
|
||||
)
|
||||
_, err, code = self.ssh.run_sudo_command(run_cmd, timeout=60)
|
||||
if code != 0:
|
||||
@@ -475,8 +476,8 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP
|
||||
'protocol': self.protocol,
|
||||
}
|
||||
|
||||
def update_settings(self, port=None):
|
||||
"""Change listen UDP port and recreate the container."""
|
||||
def update_settings(self, port=None, domain=None, email=None, renew_ssl=False):
|
||||
"""Change listen UDP port and/or re-issue Let's Encrypt for domain."""
|
||||
meta = self._ensure_secrets(self._read_metadata())
|
||||
if port is None:
|
||||
port = meta.get('port') or self.DEFAULT_PORT
|
||||
@@ -486,6 +487,32 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP
|
||||
if port == 80:
|
||||
return {'status': 'error', 'message': 'Port 80 is reserved for Let\'s Encrypt validation'}
|
||||
|
||||
old_domain = (meta.get('domain') or '').strip().lower()
|
||||
domain_changed = False
|
||||
try:
|
||||
if domain is not None and str(domain).strip():
|
||||
new_domain = self._validate_domain(domain)
|
||||
domain_changed = new_domain != old_domain
|
||||
meta['domain'] = new_domain
|
||||
if email is not None and str(email).strip():
|
||||
meta['email'] = self._validate_email(email)
|
||||
except ValueError as e:
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
|
||||
if renew_ssl or domain_changed:
|
||||
# Domain change or explicit renew → re-issue certificate (needs free TCP 80)
|
||||
try:
|
||||
d = meta.get('domain') or ''
|
||||
e = meta.get('email') or ''
|
||||
if not d or not e:
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'Domain and email are required to issue SSL',
|
||||
}
|
||||
self._issue_certificate(d, e)
|
||||
except Exception as exc:
|
||||
return {'status': 'error', 'message': str(exc)}
|
||||
|
||||
meta['port'] = port
|
||||
self._write_metadata(meta)
|
||||
self._write_config_from_clients(port)
|
||||
@@ -497,13 +524,17 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP
|
||||
'message': str(e),
|
||||
'port': str(port),
|
||||
'domain': meta.get('domain'),
|
||||
'email': meta.get('email'),
|
||||
}
|
||||
msg = 'Hysteria settings updated'
|
||||
if renew_ssl or domain_changed:
|
||||
msg = 'SSL certificate issued and Hysteria settings updated'
|
||||
return {
|
||||
'status': 'success',
|
||||
'port': str(port),
|
||||
'domain': meta.get('domain'),
|
||||
'email': meta.get('email'),
|
||||
'message': f'Hysteria listening on UDP {port}',
|
||||
'message': msg,
|
||||
}
|
||||
|
||||
def _reload_container(self):
|
||||
@@ -539,7 +570,7 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP
|
||||
f"Point A-record to this server and free TCP port 80. {err or out}"
|
||||
)
|
||||
|
||||
# Copy live certs into paths expected by teddysun image
|
||||
# Copy live certs into paths expected by the server config
|
||||
copy_script = f"""
|
||||
set -e
|
||||
LIVE={_q(self.letsencrypt_dir)}/live/{_q(domain)}
|
||||
|
||||
Reference in New Issue
Block a user