From 258dc9f100ddc0cbb99d3dbe0246bd8e0c7bdca0 Mon Sep 17 00:00:00 2001 From: orohi Date: Sun, 26 Jul 2026 02:45:24 +0300 Subject: [PATCH] Make Hysteria UDP port selectable when 443 is busy. Show an inline port field on the stopped card, a dedicated install port, and clearer bind-error hints so admins can switch to 8998/8443 without guessing. Co-authored-by: Cursor --- app.py | 7 +++ managers/hysteria_manager.py | 52 ++++++++++-------- templates/server.html | 100 +++++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 39 deletions(-) diff --git a/app.py b/app.py index e422ac8..85f4674 100644 --- a/app.py +++ b/app.py @@ -2708,6 +2708,13 @@ async def api_check_server(request: Request, server_id: int): for key in ('domain', 'email', 'site_url'): if db_proto.get(key) not in (None, ''): merged[key] = db_proto[key] + if protocol_base(proto) == 'hysteria': + for key in ('domain', 'email'): + if db_proto.get(key) not in (None, '') and not merged.get(key): + merged[key] = db_proto[key] + # Keep a useful error even when merging preserved records + if db_proto.get('port') and not merged.get('port'): + merged['port'] = db_proto['port'] return merged def should_preserve_saved_protocol(proto, result=None, err=None): diff --git a/managers/hysteria_manager.py b/managers/hysteria_manager.py index 1a41f2c..57ed12d 100644 --- a/managers/hysteria_manager.py +++ b/managers/hysteria_manager.py @@ -156,33 +156,38 @@ class HysteriaManager: elif diag['error']: diag['error_summary'] = diag['error'] elif diag['exit_code'] not in (None, 0): - # Pull last meaningful log line + # Prefer bind/port conflicts over generic FATAL lines + bind_line = '' last = '' - for line in reversed((diag['recent_logs'] or '').splitlines()): - line = self._strip_ansi(line).strip() + for raw in reversed((diag['recent_logs'] or '').splitlines()): + line = self._strip_ansi(raw).strip() if not line: continue - # Prefer the FATAL / bind error line - if 'address already in use' in line.lower() or 'FATAL' in line or 'failed to load' in line.lower(): + # strip docker --timestamps prefix + m_ts = re.match(r'^\d{4}-\d{2}-\d{2}T\S+\s+(.*)$', line) + if m_ts: + line = m_ts.group(1).strip() + low = line.lower() + if 'address already in use' in low and not bind_line: + bind_line = line + if ('FATAL' in line or 'failed to load' in low) and not last: last = line - break if not last: last = line - if last: - # strip docker timestamp prefix if present - if ' ' in last and (last[0].isdigit() or last[0] == '2'): - last = last.split(' ', 1)[-1] - last = self._strip_ansi(last) - if 'address already in use' in last.lower(): - m = re.search(r'listen udp :(\d+)', last, re.I) or re.search(r':(\d+):\s*bind', last) - p = m.group(1) if m else '?' - diag['error_summary'] = ( - f'UDP port {p} already in use — open Settings and choose another port' - ) - else: - # keep message short for badge - short = re.sub(r'\s+', ' ', last)[:160] - diag['error_summary'] = f"exit {diag['exit_code']}: {short}" + pick = bind_line or last + if pick and 'address already in use' in pick.lower(): + m = re.search(r'listen udp :(\d+)', pick, re.I) or re.search(r':(\d+):\s*bind', pick) + p = m.group(1) if m else '?' + if p == '?': + m2 = re.search(r'udp\s*:(\d+)', pick, re.I) + p = m2.group(1) if m2 else '?' + diag['error_summary'] = ( + f'UDP port {p} already in use — change port below (try 8998 or 8443)' + ) + diag['port_busy'] = True + elif pick: + short = re.sub(r'\s+', ' ', pick)[:160] + diag['error_summary'] = f"exit {diag['exit_code']}: {short}" else: diag['error_summary'] = f"exited with code {diag['exit_code']}" elif diag['status'] and diag['status'] != 'running': @@ -244,6 +249,7 @@ class HysteriaManager: 'exit_code': diag.get('exit_code'), 'container_status': diag.get('status') or '', 'recent_logs': diag.get('recent_logs') or '', + 'port_busy': bool(diag.get('port_busy')), } # ===================== HELPERS ===================== @@ -460,8 +466,10 @@ iptables -C INPUT -p udp --dport {port} -j ACCEPT 2>/dev/null || iptables -I INP def get_settings(self): meta = self._ensure_secrets(self._read_metadata()) + port = int(meta.get('port') or self.DEFAULT_PORT) return { - 'port': int(meta.get('port') or self.DEFAULT_PORT), + 'port': port, + 'suggested_port': 8998 if port in (443, 80) else port, 'domain': meta.get('domain') or '', 'email': meta.get('email') or '', 'protocol': self.protocol, diff --git a/templates/server.html b/templates/server.html index 22d65ef..e0c2b45 100644 --- a/templates/server.html +++ b/templates/server.html @@ -574,7 +574,7 @@
-
+
{{ _('port_default_hint') }}
@@ -681,6 +681,11 @@