From e448e6068462ca09faa282ddeea2872a26108a03 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 6 Nov 2026 09:07:22 +0300 Subject: [PATCH] fix(console): novncd that cannot listen ends with a failure status websockify logs "Openening socket failed" and calls sys.exit() when its port is taken, so novncd ended with status 0. Under systemd the unit then counts as stopped, not failed: Restart=on-failure does not start it again, systemctl --failed does not list it, and VNC consoles stay down until someone starts it by hand. novncd now ends with status 1 there. --- console/novncd | 10 ++++++++-- console/tests/test_novncd_exit.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 console/tests/test_novncd_exit.py diff --git a/console/novncd b/console/novncd index 823b688a..779016a6 100755 --- a/console/novncd +++ b/console/novncd @@ -200,7 +200,7 @@ if __name__ == "__main__": options.verbose = options.verbose or options.debug # nginx ends TLS: novncd speaks plain websockets on the loopback - WebSocketProxy( + server = WebSocketProxy( RequestHandlerClass=ConsoleProxyHandler, listen_host=options.host, listen_port=options.port, @@ -212,4 +212,10 @@ if __name__ == "__main__": # a ping while the VM's screen stays the same: nginx, and a proxy on # the way, end a websocket that sends nothing for 60 seconds heartbeat=30, - ).start_server() + ) + try: + server.start_server() + except SystemExit as stop: + # websockify ends with status 0 when it cannot listen (the port is + # taken): systemd's Restart=on-failure would not start it again + raise SystemExit(stop.code or 1) from None diff --git a/console/tests/test_novncd_exit.py b/console/tests/test_novncd_exit.py new file mode 100644 index 00000000..fe883928 --- /dev/null +++ b/console/tests/test_novncd_exit.py @@ -0,0 +1,22 @@ +"""novncd that cannot listen ends with a failure status, which systemd restarts.""" + +import socket +import subprocess +import sys +from pathlib import Path + +from django.test import SimpleTestCase + +ROOT = Path(__file__).resolve().parent.parent.parent + + +class NovncdExitTestCase(SimpleTestCase): + def test_a_taken_port_ends_novncd_with_a_failure_status(self): + with socket.socket() as taken: + taken.bind(("127.0.0.1", 0)) + taken.listen() + port = taken.getsockname()[1] + novncd = subprocess.run([sys.executable, str(ROOT / "console" / "novncd"), "-v", "-H", "127.0.0.1", + "-p", str(port)], cwd=ROOT, capture_output=True, timeout=60) + self.assertNotEqual(novncd.returncode, 0) + self.assertIn(b"Openening socket failed", novncd.stderr)