Skip to content

Commit ccbbaba

Browse files
authored
Get RUN_AS_DEVIN working with app sandbox (OpenHands#1426)
* get RUN_AS_DEVIN and network=host working with app sandbox * attempt to fix the workspace base permission * sandbox might failed in chown due to mounting, but it won't be fatal * update sshbox instruction * remove default user id since it will be passed in the instruction * revert permission fix since it should be resolved by correct SANDBOX_USER_ID * the permission issue can be fixed by simply provide correct env var * remove log * set sandbox user id to getuid by default * move logging to initializer * make the uid consistent across host, app container, and sandbox * remove hostname as it causes sudo issue * fix permission of entrypoint script * make the uvicron app run as host user uid for jupyter plugin * revert use host network * get docker socket gid and usermod instead of chmod 777 * try to fix app build disk space issue
1 parent eb7703a commit ccbbaba

5 files changed

Lines changed: 71 additions & 9 deletions

File tree

.github/workflows/ghcr.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,21 @@ jobs:
4242
username: ${{ github.repository_owner }}
4343
password: ${{ secrets.GITHUB_TOKEN }}
4444

45-
- name: Delete huge unnecessary tools folder
46-
run: rm -rf /opt/hostedtoolcache
45+
- name: Free Disk Space (Ubuntu)
46+
uses: jlumbroso/free-disk-space@main
47+
with:
48+
# this might remove tools that are actually needed,
49+
# if set to "true" but frees about 6 GB
50+
tool-cache: true
51+
52+
# all of these default to true, but feel free to set to
53+
# "false" if necessary for your workflow
54+
android: true
55+
dotnet: true
56+
haskell: true
57+
large-packages: true
58+
docker-images: false
59+
swap-storage: true
4760

4861
- name: Build and push ${{ matrix.image }}
4962
if: github.event.pull_request.head.repo.full_name == github.repository

containers/app/Dockerfile

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,32 @@ FROM python:3.12-slim as runtime
3232

3333
WORKDIR /app
3434

35-
ENV RUN_AS_DEVIN=false
35+
ENV RUN_AS_DEVIN=true
36+
ENV SANDBOX_USER_ID=1000
3637
ENV USE_HOST_NETWORK=false
3738
ENV SSH_HOSTNAME=host.docker.internal
3839
ENV WORKSPACE_BASE=/opt/workspace_base
3940
ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
4041
RUN mkdir -p $WORKSPACE_BASE
4142

4243
RUN apt-get update -y \
43-
&& apt-get install -y curl ssh
44+
&& apt-get install -y curl ssh sudo
45+
46+
RUN useradd -m -u $SANDBOX_USER_ID -s /bin/bash opendevin && \
47+
usermod -aG sudo opendevin && \
48+
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
49+
RUN chown -R opendevin:opendevin /app
50+
USER opendevin
4451

4552
ENV VIRTUAL_ENV=/app/.venv \
4653
PATH="/app/.venv/bin:$PATH" \
4754
PYTHONPATH='/app'
4855

4956
COPY --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
57+
# change ownership of the virtual environment to the sandbox user
58+
USER root
59+
RUN chown -R opendevin:opendevin ${VIRTUAL_ENV}
60+
USER opendevin
5061

5162
COPY ./opendevin ./opendevin
5263
COPY ./agenthub ./agenthub
@@ -55,4 +66,17 @@ RUN playwright install --with-deps chromium
5566

5667
COPY --from=frontend-builder /app/dist ./frontend/dist
5768

58-
CMD ["uvicorn", "opendevin.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]
69+
USER root
70+
RUN chown -R opendevin:opendevin /app
71+
# make group permissions the same as user permissions
72+
RUN chmod -R g=u /app
73+
USER opendevin
74+
75+
# change ownership of the app directory to the sandbox user
76+
COPY ./containers/app/entrypoint.sh /app/entrypoint.sh
77+
78+
# run the script as root
79+
USER root
80+
RUN chown opendevin:opendevin /app/entrypoint.sh
81+
RUN chmod 777 /app/entrypoint.sh
82+
CMD ["/app/entrypoint.sh"]

containers/app/entrypoint.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
# check user is root
3+
if [ "$(id -u)" -ne 0 ]; then
4+
echo "Please run as root"
5+
exit 1
6+
fi
7+
8+
if [ -z "$SANDBOX_USER_ID" ]; then
9+
echo "SANDBOX_USER_ID is not set"
10+
exit 1
11+
fi
12+
13+
# change uid of opendevin user to match the host user
14+
# but the group id is not changed, so the user can still access everything under /app
15+
usermod -u $SANDBOX_USER_ID opendevin
16+
17+
# get the user group of /var/run/docker.sock and set opendevin to that group
18+
DOCKER_SOCKET_GID=$(stat -c '%g' /var/run/docker.sock)
19+
echo "Docker socket group id: $DOCKER_SOCKET_GID"
20+
usermod -aG $DOCKER_SOCKET_GID opendevin
21+
22+
# switch to the user and start the server
23+
su opendevin -c "cd /app && uvicorn opendevin.server.listen:app --host 0.0.0.0 --port 3000"

opendevin/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
ConfigType.USE_HOST_NETWORK: 'false',
5353
ConfigType.SSH_HOSTNAME: 'localhost',
5454
ConfigType.DISABLE_COLOR: 'false',
55+
ConfigType.SANDBOX_USER_ID: os.getuid() if hasattr(os, 'getuid') else None,
5556
ConfigType.SANDBOX_TIMEOUT: 120,
5657
ConfigType.GITHUB_TOKEN: None
5758
}

opendevin/sandbox/docker/ssh_box.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
elif hasattr(os, 'getuid'):
4242
USER_ID = os.getuid()
4343

44-
4544
class DockerSSHBox(Sandbox):
4645
instance_id: str
4746
container_image: str
@@ -62,6 +61,7 @@ def __init__(
6261
timeout: int = 120,
6362
sid: str | None = None,
6463
):
64+
logger.info(f'SSHBox is running as {"opendevin" if RUN_AS_DEVIN else "root"} user with USER_ID={USER_ID} in the sandbox')
6565
# Initialize docker client. Throws an exception if Docker is not reachable.
6666
try:
6767
self.docker_client = docker.from_env()
@@ -150,8 +150,10 @@ def setup_user(self):
150150
workdir=SANDBOX_WORKSPACE_DIR,
151151
)
152152
if exit_code != 0:
153-
raise Exception(
154-
f'Failed to chown workspace directory for opendevin in sandbox: {logs}')
153+
# This is not a fatal error, just a warning
154+
logger.warning(
155+
f'Failed to chown workspace directory for opendevin in sandbox: {logs}. But this should be fine if the {SANDBOX_WORKSPACE_DIR=} is mounted by the app docker container.'
156+
)
155157
else:
156158
exit_code, logs = self.container.exec_run(
157159
# change password for root
@@ -351,7 +353,6 @@ def restart_docker_container(self):
351353
**network_kwargs,
352354
working_dir=SANDBOX_WORKSPACE_DIR,
353355
name=self.container_name,
354-
hostname='opendevin_sandbox',
355356
detach=True,
356357
volumes={
357358
mount_dir: {

0 commit comments

Comments
 (0)