From 2d36f656354bb256392e49a91b0c6366629e24cc Mon Sep 17 00:00:00 2001 From: Dmitry Meyer Date: Mon, 29 Dec 2025 14:54:13 +0000 Subject: [PATCH 1/2] Change /dstack/venv ownership to the current user Part-of: https://github.com/dstackai/dstack/issues/3436 --- .../server/services/jobs/configurators/base.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/dstack/_internal/server/services/jobs/configurators/base.py b/src/dstack/_internal/server/services/jobs/configurators/base.py index 0e770b2e90..72821f1dd3 100644 --- a/src/dstack/_internal/server/services/jobs/configurators/base.py +++ b/src/dstack/_internal/server/services/jobs/configurators/base.py @@ -220,6 +220,17 @@ def _dstack_image_commands(self) -> List[str]: ): return [] return [ + f"eval $(echo 'export DSTACK_VENV_DIR={DSTACK_DIR}/venv' | sudo tee -a {DSTACK_PROFILE_PATH})", + # Make sure /dstack/venv is owned by the current user. + # XXX: Generally, /dstack and all its descendants should be owned by root, as it is + # intended to be a place for files shared by all users, but since a non-root user + # should be able to install packages via pip and we want to avoid cluttering the user's + # home dir if possible, we make the venv dir owned by the current user rather than + # creating it inside the user's home or (even worse) making /dstack/venv + # world-writable. + "sudo rm -rf $DSTACK_VENV_DIR", + "sudo mkdir $DSTACK_VENV_DIR", + "sudo chown $(id -u):$(id -g) $DSTACK_VENV_DIR", # `uv` may emit: # > warning: `VIRTUAL_ENV=/dstack/venv` does not match the project environment path # > `.venv` and will be ignored; use `--active` to target the active environment @@ -228,9 +239,8 @@ def _dstack_image_commands(self) -> List[str]: # used for legacy `pip`-based configurations). `--no-active` suppresses the warning. # Alternatively, the user can call `deactivate` once before using `uv`. # If the user really wants to reuse dstack's venv, they must spefify `--active`. - f"uv venv -q --prompt dstack -p {self._python()} --seed {DSTACK_DIR}/venv", - f"echo '. {DSTACK_DIR}/venv/bin/activate' >> {DSTACK_PROFILE_PATH}", - f". {DSTACK_DIR}/venv/bin/activate", + f"uv venv -q --prompt dstack -p {self._python()} --seed $DSTACK_VENV_DIR", + f"eval $(echo '. $DSTACK_VENV_DIR/bin/activate' | sudo tee -a {DSTACK_PROFILE_PATH})", ] def _app_specs(self) -> List[AppSpec]: From 6463f4e1c5f65217f224b5d8ccd5dbea5da6c5d0 Mon Sep 17 00:00:00 2001 From: Dmitry Meyer Date: Mon, 29 Dec 2025 15:15:12 +0000 Subject: [PATCH 2/2] Fix tests --- .../_internal/server/routers/test_runs.py | 18 ++++++++++++------ .../services/jobs/configurators/test_task.py | 13 +++++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/tests/_internal/server/routers/test_runs.py b/src/tests/_internal/server/routers/test_runs.py index 5f5037c79d..4f3ab2ed2d 100644 --- a/src/tests/_internal/server/routers/test_runs.py +++ b/src/tests/_internal/server/routers/test_runs.py @@ -115,9 +115,12 @@ def get_dev_env_run_plan_dict( "-i", "-c", ( - "uv venv -q --prompt dstack -p 3.13 --seed /dstack/venv" - " && echo '. /dstack/venv/bin/activate' >> /dstack/profile" - " && . /dstack/venv/bin/activate" + "eval $(echo 'export DSTACK_VENV_DIR=/dstack/venv' | sudo tee -a /dstack/profile)" + " && sudo rm -rf $DSTACK_VENV_DIR" + " && sudo mkdir $DSTACK_VENV_DIR" + " && sudo chown $(id -u):$(id -g) $DSTACK_VENV_DIR" + " && uv venv -q --prompt dstack -p 3.13 --seed $DSTACK_VENV_DIR" + " && eval $(echo '. $DSTACK_VENV_DIR/bin/activate' | sudo tee -a /dstack/profile)" " && (echo 'pip install ipykernel...'" " && pip install -q --no-cache-dir ipykernel 2> /dev/null)" " || echo 'no pip, ipykernel was not installed'" @@ -344,9 +347,12 @@ def get_dev_env_run_dict( "-i", "-c", ( - "uv venv -q --prompt dstack -p 3.13 --seed /dstack/venv" - " && echo '. /dstack/venv/bin/activate' >> /dstack/profile" - " && . /dstack/venv/bin/activate" + "eval $(echo 'export DSTACK_VENV_DIR=/dstack/venv' | sudo tee -a /dstack/profile)" + " && sudo rm -rf $DSTACK_VENV_DIR" + " && sudo mkdir $DSTACK_VENV_DIR" + " && sudo chown $(id -u):$(id -g) $DSTACK_VENV_DIR" + " && uv venv -q --prompt dstack -p 3.13 --seed $DSTACK_VENV_DIR" + " && eval $(echo '. $DSTACK_VENV_DIR/bin/activate' | sudo tee -a /dstack/profile)" " && (echo 'pip install ipykernel...'" " && pip install -q --no-cache-dir ipykernel 2> /dev/null)" " || echo 'no pip, ipykernel was not installed'" diff --git a/src/tests/_internal/server/services/jobs/configurators/test_task.py b/src/tests/_internal/server/services/jobs/configurators/test_task.py index 6397c40e2c..3c80bf226f 100644 --- a/src/tests/_internal/server/services/jobs/configurators/test_task.py +++ b/src/tests/_internal/server/services/jobs/configurators/test_task.py @@ -98,10 +98,15 @@ async def test_with_commands_no_image(self, shell: Optional[str], expected_shell expected_shell, "-i", "-c", - "uv venv -q --prompt dstack -p 3.12 --seed /dstack/venv" - " && echo '. /dstack/venv/bin/activate' >> /dstack/profile" - " && . /dstack/venv/bin/activate" - " && sleep inf", + ( + "eval $(echo 'export DSTACK_VENV_DIR=/dstack/venv' | sudo tee -a /dstack/profile)" + " && sudo rm -rf $DSTACK_VENV_DIR" + " && sudo mkdir $DSTACK_VENV_DIR" + " && sudo chown $(id -u):$(id -g) $DSTACK_VENV_DIR" + " && uv venv -q --prompt dstack -p 3.12 --seed $DSTACK_VENV_DIR" + " && eval $(echo '. $DSTACK_VENV_DIR/bin/activate' | sudo tee -a /dstack/profile)" + " && sleep inf" + ), ] async def test_no_commands(self, image_config_mock: ImageConfig):