From 9c250c925be57902dda107b83bb09c8d07ed5c40 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 11:01:03 +0300 Subject: [PATCH 1/9] Fix websockets --- README.md | 1 + root/app/ondemand/container_thread.py | 23 +++++++++++++++++++---- root/app/ondemand/data_classes.py | 1 + root/app/ondemand/log_reader_thread.py | 10 +++++++--- root/app/ondemand/shared_state.py | 2 ++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 01e57f8eb..6c862cc47 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Or set the following label if using `swag-auto-proxy`: ### Labels: - `swag_ondemand=enable` - required for on-demand. - `swag_ondemand_urls=https://wake.domain.com,https://app.domain.com/up` - *optional* - overrides the monitored URLs for starting the container on-demand. Defaults to using the value of the `swag_url` label, if you've already set it for `swag-auto-proxy`, or `https://somecontainer.,http://somecontainer.` otherwise. +- `swag_ondemand_websocket=1` - required for apps that communicate over a websocket, such as selkies based apps. ### URLs: - Accessed URLs need to start with one of `swag_ondemand_urls` to be matched, for example, setting `swag_ondemand_urls=https://plex.` will apply to `https://plex.domain.com` and `https://plex.domain.com/something`. diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index a9823ac5e..9a1036f82 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -1,5 +1,5 @@ from data_classes import DockerHost, OnDemandContainer -from shared_state import last_accessed_urls, last_accessed_urls_lock +from shared_state import last_accessed_urls, last_accessed_urls_lock, websocket_terminated_urls, websocket_terminated_urls_lock from datetime import datetime import logging @@ -61,6 +61,7 @@ def process_containers(self): for container in containers: default_url = container.labels.get("swag_url", f"{container.name}.").rstrip("*") container_urls = container.labels.get("swag_ondemand_urls", f"https://{default_url},http://{default_url}") + websocket = container.labels.get("swag_ondemand_websocket", "0") != "0" if container.name not in docker_host.ondemand_containers: last_accessed = datetime.now() @@ -74,10 +75,11 @@ def process_containers(self): docker_host.ondemand_containers[container.name] = OnDemandContainer( status=container.status, urls=container_urls, - last_accessed=last_accessed + last_accessed=last_accessed, + websocket=websocket ) - def stop_containers(self): + def stop_containers(self, websocket_terminated_urls_combined: str): for docker_host in self.docker_hosts: if not docker_host.is_connected: continue @@ -89,6 +91,15 @@ def stop_containers(self): if inactive_seconds < STOP_THRESHOLD: continue + if ondemand_container.websocket: + terminated = False + for ondemand_url in ondemand_container.urls.split(","): + if ondemand_url in websocket_terminated_urls_combined: + terminated = True + break + if not terminated: + continue + container = docker_host.get_container(container_name) if not container: continue @@ -148,9 +159,13 @@ def run(self): last_accessed_urls_combined = ",".join(last_accessed_urls) last_accessed_urls.clear() + with websocket_terminated_urls_lock: + websocket_terminated_urls_combined = ",".join(websocket_terminated_urls) + websocket_terminated_urls.clear() + self.send_wol(last_accessed_urls_combined) self.process_containers() - self.start_containers(last_accessed_urls_combined) + self.start_containers(last_accessed_urls_combined, websocket_terminated_urls_combined) self.stop_containers() except Exception as e: logging.exception(e) diff --git a/root/app/ondemand/data_classes.py b/root/app/ondemand/data_classes.py index 4bc1cf02f..b5c5cefbe 100644 --- a/root/app/ondemand/data_classes.py +++ b/root/app/ondemand/data_classes.py @@ -10,6 +10,7 @@ class OnDemandContainer: status: str urls: str last_accessed: datetime + websocket: bool @dataclass class DockerHost: diff --git a/root/app/ondemand/log_reader_thread.py b/root/app/ondemand/log_reader_thread.py index c624ca378..4e9f71c77 100644 --- a/root/app/ondemand/log_reader_thread.py +++ b/root/app/ondemand/log_reader_thread.py @@ -1,4 +1,4 @@ -from shared_state import last_accessed_urls, last_accessed_urls_lock +from shared_state import last_accessed_urls, last_accessed_urls_lock, websocket_terminated_urls, websocket_terminated_urls_lock import logging import os @@ -43,8 +43,12 @@ def run(self): for part in line.split(): if not part.startswith("http"): continue - with last_accessed_urls_lock: - last_accessed_urls.add(part) + if '" 101 ' in line: + with websocket_terminated_urls_lock: + websocket_terminated_urls.add(part) + else: + with last_accessed_urls_lock: + last_accessed_urls.add(part) break except Exception as e: logging.exception(e) diff --git a/root/app/ondemand/shared_state.py b/root/app/ondemand/shared_state.py index d6bc12af2..50c0fd16a 100644 --- a/root/app/ondemand/shared_state.py +++ b/root/app/ondemand/shared_state.py @@ -1,4 +1,6 @@ import threading +websocket_terminated_urls = set() +websocket_terminated_urls_lock = threading.Lock() last_accessed_urls = set() last_accessed_urls_lock = threading.Lock() From dc7d0a5872acd2358da87ce9fc21549fa033b260 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 11:01:51 +0300 Subject: [PATCH 2/9] Fix typo --- root/app/ondemand/container_thread.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index 9a1036f82..277f2be2f 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -165,8 +165,8 @@ def run(self): self.send_wol(last_accessed_urls_combined) self.process_containers() - self.start_containers(last_accessed_urls_combined, websocket_terminated_urls_combined) - self.stop_containers() + self.start_containers(last_accessed_urls_combined) + self.stop_containers(websocket_terminated_urls_combined) except Exception as e: logging.exception(e) time.sleep(CONTAINER_QUERY_SLEEP) From 2b00e9dc15f86e069547f412028ff2da3fe97a79 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 11:19:35 +0300 Subject: [PATCH 3/9] Accept other values on swag_ondemand_websocket --- root/app/ondemand/container_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index 277f2be2f..a6bc10f35 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -61,7 +61,7 @@ def process_containers(self): for container in containers: default_url = container.labels.get("swag_url", f"{container.name}.").rstrip("*") container_urls = container.labels.get("swag_ondemand_urls", f"https://{default_url},http://{default_url}") - websocket = container.labels.get("swag_ondemand_websocket", "0") != "0" + websocket = container.labels.get("swag_ondemand_websocket", "0").lower() in ("true", "1") if container.name not in docker_host.ondemand_containers: last_accessed = datetime.now() From f9afddec38546051e9fd951d932ac9fff7fa48c0 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 11:41:36 +0300 Subject: [PATCH 4/9] Allow for the threshold to pass before terminating --- root/app/ondemand/container_thread.py | 9 +++++---- root/app/ondemand/data_classes.py | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index a6bc10f35..ce4167305 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -91,13 +91,13 @@ def stop_containers(self, websocket_terminated_urls_combined: str): if inactive_seconds < STOP_THRESHOLD: continue - if ondemand_container.websocket: - terminated = False + if ondemand_container.websocket and not ondemand_container.terminated: for ondemand_url in ondemand_container.urls.split(","): if ondemand_url in websocket_terminated_urls_combined: - terminated = True + ondemand_container.last_accessed = datetime.now() + ondemand_container.terminated = True break - if not terminated: + if not ondemand_container.terminated: continue container = docker_host.get_container(container_name) @@ -120,6 +120,7 @@ def start_containers(self, last_accessed_urls_combined: str): for ondemand_url in ondemand_container.urls.split(","): if ondemand_url in last_accessed_urls_combined: ondemand_container.last_accessed = datetime.now() + ondemand_container.terminated = False accessed = True break diff --git a/root/app/ondemand/data_classes.py b/root/app/ondemand/data_classes.py index b5c5cefbe..279fcea72 100644 --- a/root/app/ondemand/data_classes.py +++ b/root/app/ondemand/data_classes.py @@ -11,6 +11,7 @@ class OnDemandContainer: urls: str last_accessed: datetime websocket: bool + terminated: bool = False @dataclass class DockerHost: From 6fd67470bda4f04f58b36d2bea2b1f2cc9a552c8 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 11:50:23 +0300 Subject: [PATCH 5/9] Fix logic --- root/app/ondemand/container_thread.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index ce4167305..6ad332cda 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -87,10 +87,6 @@ def stop_containers(self, websocket_terminated_urls_combined: str): if ondemand_container.status != "running": continue - inactive_seconds = (datetime.now() - ondemand_container.last_accessed).total_seconds() - if inactive_seconds < STOP_THRESHOLD: - continue - if ondemand_container.websocket and not ondemand_container.terminated: for ondemand_url in ondemand_container.urls.split(","): if ondemand_url in websocket_terminated_urls_combined: @@ -100,6 +96,10 @@ def stop_containers(self, websocket_terminated_urls_combined: str): if not ondemand_container.terminated: continue + inactive_seconds = (datetime.now() - ondemand_container.last_accessed).total_seconds() + if inactive_seconds < STOP_THRESHOLD: + continue + container = docker_host.get_container(container_name) if not container: continue From 17c6b65ec238d2a280b10cb0a712d1b347b55ca0 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 12:15:31 +0300 Subject: [PATCH 6/9] Trying to fix logic bug --- root/app/ondemand/container_thread.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index 6ad332cda..74e45752b 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -65,19 +65,20 @@ def process_containers(self): if container.name not in docker_host.ondemand_containers: last_accessed = datetime.now() + docker_host.ondemand_containers[container.name] = OnDemandContainer( + status=container.status, + urls=container_urls, + last_accessed=last_accessed, + websocket=websocket + ) logging.info(f"Started monitoring {container.name} on {docker_host.url} for urls: {container_urls}") else: - existing_container = docker_host.ondemand_containers[container.name] - last_accessed = existing_container.last_accessed - if container_urls != existing_container.urls: + docker_host.ondemand_containers[container.name].status = container.status + docker_host.ondemand_containers[container.name].websocket = websocket + if container_urls != docker_host.ondemand_containers[container.name].urls: + docker_host.ondemand_containers[container.name] = container_urls logging.info(f"Updated urls for {container.name} on {docker_host.url} to: {container_urls}") - docker_host.ondemand_containers[container.name] = OnDemandContainer( - status=container.status, - urls=container_urls, - last_accessed=last_accessed, - websocket=websocket - ) def stop_containers(self, websocket_terminated_urls_combined: str): for docker_host in self.docker_hosts: From e7eeb96e6c943d2c9aa5133f27d3250514ae80a9 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 12:33:10 +0300 Subject: [PATCH 7/9] Refactor --- root/app/ondemand/container_thread.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index 74e45752b..d7f971304 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -63,21 +63,20 @@ def process_containers(self): container_urls = container.labels.get("swag_ondemand_urls", f"https://{default_url},http://{default_url}") websocket = container.labels.get("swag_ondemand_websocket", "0").lower() in ("true", "1") - if container.name not in docker_host.ondemand_containers: - last_accessed = datetime.now() + if container.name in docker_host.ondemand_containers: + docker_host.ondemand_containers[container.name].status = container.status + docker_host.ondemand_containers[container.name].websocket = websocket + if container_urls != docker_host.ondemand_containers[container.name].urls: + docker_host.ondemand_containers[container.name] = container_urls + logging.info(f"Updated urls for {container.name} on {docker_host.url} to: {container_urls}") + else: docker_host.ondemand_containers[container.name] = OnDemandContainer( status=container.status, urls=container_urls, - last_accessed=last_accessed, + last_accessed=datetime.now(), websocket=websocket ) logging.info(f"Started monitoring {container.name} on {docker_host.url} for urls: {container_urls}") - else: - docker_host.ondemand_containers[container.name].status = container.status - docker_host.ondemand_containers[container.name].websocket = websocket - if container_urls != docker_host.ondemand_containers[container.name].urls: - docker_host.ondemand_containers[container.name] = container_urls - logging.info(f"Updated urls for {container.name} on {docker_host.url} to: {container_urls}") def stop_containers(self, websocket_terminated_urls_combined: str): From c342886c51686c469b5151aec216de04d3a6ce25 Mon Sep 17 00:00:00 2001 From: quietsy Date: Fri, 3 Jul 2026 14:01:11 +0300 Subject: [PATCH 8/9] Fix typo --- root/app/ondemand/container_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index d7f971304..0348b2f8a 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -67,7 +67,7 @@ def process_containers(self): docker_host.ondemand_containers[container.name].status = container.status docker_host.ondemand_containers[container.name].websocket = websocket if container_urls != docker_host.ondemand_containers[container.name].urls: - docker_host.ondemand_containers[container.name] = container_urls + docker_host.ondemand_containers[container.name].urls = container_urls logging.info(f"Updated urls for {container.name} on {docker_host.url} to: {container_urls}") else: docker_host.ondemand_containers[container.name] = OnDemandContainer( From 76a0ac68f4e9809fd4603ee0b7df2c3783e3c38c Mon Sep 17 00:00:00 2001 From: quietsy Date: Sat, 4 Jul 2026 03:19:04 +0300 Subject: [PATCH 9/9] Add error handling --- root/app/ondemand/data_classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/root/app/ondemand/data_classes.py b/root/app/ondemand/data_classes.py index 279fcea72..5a9af2255 100644 --- a/root/app/ondemand/data_classes.py +++ b/root/app/ondemand/data_classes.py @@ -59,7 +59,7 @@ def get_container(self, container_name: str): if not client or not self.is_connected: return None return client.containers.get(container_name) - except (docker.errors.DockerException, requests.exceptions.ConnectionError): + except (docker.errors.DockerException, requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout): self.handle_disconnect() return None @@ -69,6 +69,6 @@ def get_containers(self): if not client or not self.is_connected: return None return client.containers.list(all=True, filters={"label": ["swag_ondemand=enable"]}) - except (docker.errors.DockerException, requests.exceptions.ConnectionError): + except (docker.errors.DockerException, requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout): self.handle_disconnect() return None