diff --git a/README.md b/README.md index 01e57f8e..6c862cc4 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 a9823ac5..0348b2f8 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,23 +61,25 @@ 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").lower() in ("true", "1") - if container.name not in docker_host.ondemand_containers: - last_accessed = datetime.now() - 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: + 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].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( + status=container.status, + urls=container_urls, + last_accessed=datetime.now(), + websocket=websocket + ) + logging.info(f"Started monitoring {container.name} on {docker_host.url} for urls: {container_urls}") - docker_host.ondemand_containers[container.name] = OnDemandContainer( - status=container.status, - urls=container_urls, - last_accessed=last_accessed - ) - 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 @@ -85,6 +87,15 @@ def stop_containers(self): if ondemand_container.status != "running": 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: + ondemand_container.last_accessed = datetime.now() + ondemand_container.terminated = True + break + if not ondemand_container.terminated: + continue + inactive_seconds = (datetime.now() - ondemand_container.last_accessed).total_seconds() if inactive_seconds < STOP_THRESHOLD: continue @@ -109,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 @@ -148,10 +160,14 @@ 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.stop_containers() + self.stop_containers(websocket_terminated_urls_combined) except Exception as e: logging.exception(e) time.sleep(CONTAINER_QUERY_SLEEP) diff --git a/root/app/ondemand/data_classes.py b/root/app/ondemand/data_classes.py index 4bc1cf02..5a9af225 100644 --- a/root/app/ondemand/data_classes.py +++ b/root/app/ondemand/data_classes.py @@ -10,6 +10,8 @@ class OnDemandContainer: status: str urls: str last_accessed: datetime + websocket: bool + terminated: bool = False @dataclass class DockerHost: @@ -57,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 @@ -67,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 diff --git a/root/app/ondemand/log_reader_thread.py b/root/app/ondemand/log_reader_thread.py index c624ca37..4e9f71c7 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 d6bc12af..50c0fd16 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()