From 6845fe45b7b560cd3c88b026acb74c05b1283a89 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Thu, 29 Jan 2026 19:46:46 +0000 Subject: [PATCH] docker mode: allow username override --- README.md | 9 +++++++++ src/webterm/docker_exec_session.py | 3 +++ src/webterm/session_manager.py | 3 +++ tests/test_session_manager.py | 26 ++++++++++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/README.md b/README.md index c753dc4..2ae3507 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,15 @@ When a container starts with either label, it automatically appears in the dashb Containers that only specify `webterm-theme` are still included and use the default auto command. +**Environment Variables:** +- `WEBTERM_DOCKER_USERNAME` - Set to run Docker exec sessions as a specific user (default: root) +- `WEBTERM_DOCKER_AUTO_COMMAND` - Override the default `auto` command (default: `tmux new-session -As webterm`) + +Example: Start containers and exec into them as `developer` user: +```bash +WEBTERM_DOCKER_USERNAME=developer webterm --docker-watch +``` + Example docker-compose.yaml: ```yaml diff --git a/src/webterm/docker_exec_session.py b/src/webterm/docker_exec_session.py index 7ddf35d..26fdfcb 100644 --- a/src/webterm/docker_exec_session.py +++ b/src/webterm/docker_exec_session.py @@ -43,6 +43,7 @@ DA_PARTIAL_PATTERN = re.compile(rb"\x1b(?:\[(?:\?[\d;]*)?)?$") class DockerExecSpec: container: str command: list[str] + user: str | None = None class DockerExecSession(Session): @@ -162,6 +163,8 @@ class DockerExecSession(Session): "Tty": True, "Cmd": self.exec_spec.command, } + if self.exec_spec.user: + payload["User"] = self.exec_spec.user response = self._request_json( "POST", f"/containers/{self.exec_spec.container}/exec", payload ) diff --git a/src/webterm/session_manager.py b/src/webterm/session_manager.py index d14348f..e95b897 100644 --- a/src/webterm/session_manager.py +++ b/src/webterm/session_manager.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio import logging +import os import shlex import sys from typing import TYPE_CHECKING @@ -137,9 +138,11 @@ class SessionManager: log.warning("Sorry, webterm does not currently support terminals on Windows") return None if app.command == AUTO_COMMAND_SENTINEL: + docker_user = os.environ.get("WEBTERM_DOCKER_USERNAME") exec_spec = DockerExecSpec( container=app.name, command=shlex.split(_get_auto_command()), + user=docker_user, ) session_process = DockerExecSession(self.poller, session_id, exec_spec) else: diff --git a/tests/test_session_manager.py b/tests/test_session_manager.py index c95027b..eedcbaa 100644 --- a/tests/test_session_manager.py +++ b/tests/test_session_manager.py @@ -214,6 +214,32 @@ class TestSessionManager: assert result is not None assert isinstance(result, DockerExecSession) + assert result.exec_spec.user is None + + async def test_new_docker_exec_session_with_user(self, mock_poller, mock_path, monkeypatch): + from webterm.docker_exec_session import DockerExecSession + + monkeypatch.setenv("WEBTERM_DOCKER_USERNAME", "testuser") + + app = App( + name="my-container", + slug="my-container", + path="./", + command=AUTO_COMMAND_SENTINEL, + terminal=True, + ) + manager = SessionManager(mock_poller, mock_path, [app]) + + with patch.object(DockerExecSession, "open", new_callable=AsyncMock): + result = await manager.new_session( + "my-container", + SessionID("test-session"), + RouteKey("test-route"), + ) + + assert result is not None + assert isinstance(result, DockerExecSession) + assert result.exec_spec.user == "testuser" class TestSessionManagerRoutes: