From 327ad50f8bc79f97486d8f9765ac3f6949f47063 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:39:15 +0100 Subject: [PATCH] fix(update): stop the refusal sending deployer-run stacks to a path that is not on the host When Compose runs inside a webhook or deployer container, the working_dir label holds the deployer's own path. The refusal told the operator to set CASHPILOT_COMPOSE_DIR to it as if it were the host folder. It now names the path as what Compose recorded and says to update from the deployer in that case. --- app/orchestrator.py | 13 ++++++++++--- tests/test_socket_proxy.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/orchestrator.py b/app/orchestrator.py index 4c096f99..c1e9b6c4 100644 --- a/app/orchestrator.py +++ b/app/orchestrator.py @@ -1907,10 +1907,17 @@ def _check_proxy_binds(working_dir: str, extra_paths: list[str]) -> None: """Refuse, with the line to paste, an update the proxy would not let bind its folders.""" compose_dir = os.getenv("CASHPILOT_COMPOSE_DIR", "").strip().rstrip("/") if compose_dir != working_dir.rstrip("/"): + # working_dir is the path Compose itself ran in. Under a webhook or a + # deployer container (Portainer and the like) that path is inside the + # deployer and may not exist on this host; the helper binds it as is, + # so no other value would work either: the deployer has to update. raise SelfUpdateUnavailable( - "The update helper needs your compose folder, and the Docker socket proxy lets " - "containers bind only the folders it was told about. Add this line to the .env " - f"next to your compose file, then run docker compose up -d once: " + f"The update helper needs your compose folder, which Docker Compose recorded as " + f"{working_dir}, and the Docker socket proxy lets containers bind only the folders " + "it was told about. If Compose runs inside another container for you (a webhook, " + "Portainer or another deployer), that path is inside it, not on this host: update " + "the stack from your deployer instead. Otherwise add this line to the .env next to " + "your compose file, then run docker compose up -d once: " f"{COMPOSE_DIR_ENV}={working_dir}" ) roots = _proxy_bind_roots() diff --git a/tests/test_socket_proxy.py b/tests/test_socket_proxy.py index b0e24499..ba45cd62 100644 --- a/tests/test_socket_proxy.py +++ b/tests/test_socket_proxy.py @@ -611,6 +611,25 @@ def test_proxied_a_compose_folder_the_proxy_does_not_know_is_refused_with_the_li client.containers.run.assert_not_called() +def test_proxied_under_a_deployer_container_the_refusal_does_not_pass_its_path_off_as_the_hosts(monkeypatch): + """Compose run by a webhook or Portainer records the deployer's own path + (/data/compose/12), which need not exist on the host. The advice must say + so and send the operator to the deployer, not only to that path.""" + _proxied_env(monkeypatch, compose_dir="") + labels = {**tuf._COMPOSE_LABELS, "com.docker.compose.project.working_dir": "/data/compose/12"} + client = tuf._fake_docker(me=tuf._fake_container(labels=labels)) + tuf._wire_runs(client) + with ( + patch.object(orchestrator, "_get_client", return_value=client), + pytest.raises(orchestrator.SelfUpdateUnavailable) as refused, + ): + orchestrator.spawn_self_update() + message = str(refused.value) + assert "recorded as /data/compose/12" in message + assert "not on this host" in message and "update the stack from your deployer" in message + client.containers.run.assert_not_called() + + def test_proxied_a_trailing_slash_is_the_same_folder(monkeypatch): _proxied_env(monkeypatch, compose_dir="/srv/cashpilot/") client, result = _run_update()