From d6bf80a68abcd82165eddb9f2ba6723ef46b0bc2 Mon Sep 17 00:00:00 2001 From: StyxX65 <150797939+StyxX65@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:23:18 +0200 Subject: [PATCH] Keep the same port across app restarts The port probe did a plain bind() without SO_REUSEADDR, so TIME_WAIT connections left by the previous instance (e.g. the in-app update restart) made the port look occupied and the app hopped to the next one. Probe with SO_REUSEADDR like Werkzeug binds, and give the requested port a 10-second grace period before auto-incrementing. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 ++ gdpr_scanner.py | 33 ++++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 118cc90..0b63bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ### Fixed +- **App restart no longer hops to a new port** — the in-app update restart (and any quick stop/start) left connections from the previous instance in TIME_WAIT, and the startup port probe did a plain `bind()` that treats TIME_WAIT as occupied — so the restarted app silently came up on 5101 and the browser's reload poll never found it. The probe now sets `SO_REUSEADDR` (matching how Werkzeug actually binds, so an actively listening port is still detected as occupied), and the requested port gets a 10-second grace period before the auto-increment fallback kicks in, covering the brief window where the old process hasn't fully released the socket. + - **Share links now respect a reverse proxy** — `_getShareBaseUrl()` rewrote every copied share link to `http://:5100` (via `/api/local_ip`), which would bypass TLS when the scanner sits behind a reverse proxy (Zoraxy, Caddy, nginx, …): a DPO opening the link would silently fall back to plain HTTP. The LAN-IP rewrite now only applies in the case it was built for — browsing the app at `localhost` over HTTP, where `window.location.origin` would produce links unusable from other machines. Any HTTPS or non-localhost origin is used as-is. --- diff --git a/gdpr_scanner.py b/gdpr_scanner.py index 8ce267f..686cb9f 100644 --- a/gdpr_scanner.py +++ b/gdpr_scanner.py @@ -2261,14 +2261,33 @@ Example --settings file with SMTP: # Find a free port — auto-increment from the requested port if in use. import socket as _socket + + def _can_bind(p: int, host: str) -> bool: + with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s: + # Probe with SO_REUSEADDR, matching how Werkzeug binds. + # Without it, connections left in TIME_WAIT by a previous + # instance (e.g. the in-app update restart) make the port + # look occupied and the app silently moves to the next one. + s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) + try: + s.bind((host, p)) + return True + except OSError: + return False + def _find_free_port(start: int, host: str) -> int: - for p in range(start, start + 100): - with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s: - try: - s.bind((host, p)) - return p - except OSError: - continue + # Give the requested port a grace period — after a self-restart + # the previous process may not have released it yet. + deadline = time.time() + 10 + while True: + if _can_bind(start, host): + return start + if time.time() >= deadline: + break + time.sleep(0.5) + for p in range(start + 1, start + 100): + if _can_bind(p, host): + return p raise RuntimeError(f"No free port found in range {start}–{start + 99}") actual_port = _find_free_port(args.port, args.host)