From 2c43b7b5e5d92f6d6f0b891563e7fbe59c3318d2 Mon Sep 17 00:00:00 2001 From: UrekD Date: Thu, 8 Oct 2026 15:23:17 +0200 Subject: [PATCH] swag: ondemand add per-container stop threshold label Add an optional `swag_ondemand_stop_threshold=` container label that overrides SWAG_ONDEMAND_STOP_THRESHOLD for that container. Containers without the label keep using the global value, so existing setups are unchanged. The label is parsed with int() like the env var. Values that don't parse or aren't positive fall back to the global threshold instead of raising, since an exception in process_containers would skip starting and stopping for every host in that cycle. The effective value is logged in the "Started monitoring" line, and again only if it changes (e.g. the container is recreated with a different label), so a bad label doesn't spam the log every query cycle. --- README.md | 4 +++- root/app/ondemand/container_thread.py | 18 ++++++++++++++---- root/app/ondemand/data_classes.py | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6c862cc47..1898fe7e7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This mod gives SWAG the ability to start containers on-demand when accessed thro access_log /config/log/nginx/access.log main; ``` - *Optional* - Additional environment variables - - `SWAG_ONDEMAND_STOP_THRESHOLD` - duration of inactivity in seconds before stopping on-demand containers, defaults to `600` (10 minutes). + - `SWAG_ONDEMAND_STOP_THRESHOLD` - duration of inactivity in seconds before stopping on-demand containers, defaults to `600` (10 minutes). Can be overridden per container with the `swag_ondemand_stop_threshold` label. - `SWAG_ONDEMAND_CONTAINER_QUERY_SLEEP` - sleep time in seconds between querying containers, defaults to `5.0`. - `SWAG_ONDEMAND_LOG_READER_SLEEP` - sleep time in seconds between log reads, defaults to `1.0`. - `SWAG_ONDEMAND_DOCKER_API_TIMEOUT` - the timeout for docker's API. Defaults to `5`. @@ -81,12 +81,14 @@ Or set the following label if using `swag-auto-proxy`: - `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. +- `swag_ondemand_stop_threshold=300` - *optional* - overrides `SWAG_ONDEMAND_STOP_THRESHOLD` for this container. Must be a positive whole number of seconds, otherwise `SWAG_ONDEMAND_STOP_THRESHOLD` is used. ### 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`. - `swag_ondemand_urls` default to `https://somecontainer.,http://somecontainer.`, for example `https://plex.,http://plex.`. - `swag_ondemand_urls` don't need to be valid, it will work as long as it reaches swag and gets logged by nginx under `/config/log/nginx/access.log`. - The same URL can be set on multiple containers and all of them will be started when accessing that URL. +- Containers sharing a URL are stopped independently, each according to its own stop threshold. ### Logging: The log file can be found under `/config/log/ondemand/ondemand.log`. diff --git a/root/app/ondemand/container_thread.py b/root/app/ondemand/container_thread.py index 0348b2f8a..e2c1a81c2 100644 --- a/root/app/ondemand/container_thread.py +++ b/root/app/ondemand/container_thread.py @@ -62,6 +62,12 @@ def process_containers(self): 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") + try: + stop_threshold = int(container.labels.get("swag_ondemand_stop_threshold", STOP_THRESHOLD)) + except ValueError: + stop_threshold = STOP_THRESHOLD + if stop_threshold <= 0: + stop_threshold = STOP_THRESHOLD if container.name in docker_host.ondemand_containers: docker_host.ondemand_containers[container.name].status = container.status @@ -69,14 +75,18 @@ def process_containers(self): 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}") + if stop_threshold != docker_host.ondemand_containers[container.name].stop_threshold: + docker_host.ondemand_containers[container.name].stop_threshold = stop_threshold + logging.info(f"Updated stop threshold for {container.name} on {docker_host.url} to: {stop_threshold}s") else: docker_host.ondemand_containers[container.name] = OnDemandContainer( status=container.status, urls=container_urls, last_accessed=datetime.now(), - websocket=websocket + websocket=websocket, + stop_threshold=stop_threshold ) - logging.info(f"Started monitoring {container.name} on {docker_host.url} for urls: {container_urls}") + logging.info(f"Started monitoring {container.name} on {docker_host.url} for urls: {container_urls}, stop threshold: {stop_threshold}s") def stop_containers(self, websocket_terminated_urls_combined: str): @@ -97,7 +107,7 @@ def stop_containers(self, websocket_terminated_urls_combined: str): continue inactive_seconds = (datetime.now() - ondemand_container.last_accessed).total_seconds() - if inactive_seconds < STOP_THRESHOLD: + if inactive_seconds < ondemand_container.stop_threshold: continue container = docker_host.get_container(container_name) @@ -106,7 +116,7 @@ def stop_containers(self, websocket_terminated_urls_combined: str): container.stop() ondemand_container.status = "exited" - logging.info(f"Stopped {container_name} on {docker_host.url} after {STOP_THRESHOLD}s of inactivity") + logging.info(f"Stopped {container_name} on {docker_host.url} after {ondemand_container.stop_threshold}s of inactivity") def start_containers(self, last_accessed_urls_combined: str): if not last_accessed_urls_combined: diff --git a/root/app/ondemand/data_classes.py b/root/app/ondemand/data_classes.py index 5a9af2255..73abdf403 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 + stop_threshold: int terminated: bool = False @dataclass