mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
Get RUN_AS_DEVIN working with app sandbox (#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
This commit is contained in:
17
.github/workflows/ghcr.yml
vendored
17
.github/workflows/ghcr.yml
vendored
@@ -42,8 +42,21 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Delete huge unnecessary tools folder
|
- name: Free Disk Space (Ubuntu)
|
||||||
run: rm -rf /opt/hostedtoolcache
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
with:
|
||||||
|
# this might remove tools that are actually needed,
|
||||||
|
# if set to "true" but frees about 6 GB
|
||||||
|
tool-cache: true
|
||||||
|
|
||||||
|
# all of these default to true, but feel free to set to
|
||||||
|
# "false" if necessary for your workflow
|
||||||
|
android: true
|
||||||
|
dotnet: true
|
||||||
|
haskell: true
|
||||||
|
large-packages: true
|
||||||
|
docker-images: false
|
||||||
|
swap-storage: true
|
||||||
|
|
||||||
- name: Build and push ${{ matrix.image }}
|
- name: Build and push ${{ matrix.image }}
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ FROM python:3.12-slim as runtime
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV RUN_AS_DEVIN=false
|
ENV RUN_AS_DEVIN=true
|
||||||
|
ENV SANDBOX_USER_ID=1000
|
||||||
ENV USE_HOST_NETWORK=false
|
ENV USE_HOST_NETWORK=false
|
||||||
ENV SSH_HOSTNAME=host.docker.internal
|
ENV SSH_HOSTNAME=host.docker.internal
|
||||||
ENV WORKSPACE_BASE=/opt/workspace_base
|
ENV WORKSPACE_BASE=/opt/workspace_base
|
||||||
@@ -40,13 +41,23 @@ ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
|
|||||||
RUN mkdir -p $WORKSPACE_BASE
|
RUN mkdir -p $WORKSPACE_BASE
|
||||||
|
|
||||||
RUN apt-get update -y \
|
RUN apt-get update -y \
|
||||||
&& apt-get install -y curl ssh
|
&& apt-get install -y curl ssh sudo
|
||||||
|
|
||||||
|
RUN useradd -m -u $SANDBOX_USER_ID -s /bin/bash opendevin && \
|
||||||
|
usermod -aG sudo opendevin && \
|
||||||
|
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||||
|
RUN chown -R opendevin:opendevin /app
|
||||||
|
USER opendevin
|
||||||
|
|
||||||
ENV VIRTUAL_ENV=/app/.venv \
|
ENV VIRTUAL_ENV=/app/.venv \
|
||||||
PATH="/app/.venv/bin:$PATH" \
|
PATH="/app/.venv/bin:$PATH" \
|
||||||
PYTHONPATH='/app'
|
PYTHONPATH='/app'
|
||||||
|
|
||||||
COPY --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
COPY --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||||
|
# change ownership of the virtual environment to the sandbox user
|
||||||
|
USER root
|
||||||
|
RUN chown -R opendevin:opendevin ${VIRTUAL_ENV}
|
||||||
|
USER opendevin
|
||||||
|
|
||||||
COPY ./opendevin ./opendevin
|
COPY ./opendevin ./opendevin
|
||||||
COPY ./agenthub ./agenthub
|
COPY ./agenthub ./agenthub
|
||||||
@@ -55,4 +66,17 @@ RUN playwright install --with-deps chromium
|
|||||||
|
|
||||||
COPY --from=frontend-builder /app/dist ./frontend/dist
|
COPY --from=frontend-builder /app/dist ./frontend/dist
|
||||||
|
|
||||||
CMD ["uvicorn", "opendevin.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]
|
USER root
|
||||||
|
RUN chown -R opendevin:opendevin /app
|
||||||
|
# make group permissions the same as user permissions
|
||||||
|
RUN chmod -R g=u /app
|
||||||
|
USER opendevin
|
||||||
|
|
||||||
|
# change ownership of the app directory to the sandbox user
|
||||||
|
COPY ./containers/app/entrypoint.sh /app/entrypoint.sh
|
||||||
|
|
||||||
|
# run the script as root
|
||||||
|
USER root
|
||||||
|
RUN chown opendevin:opendevin /app/entrypoint.sh
|
||||||
|
RUN chmod 777 /app/entrypoint.sh
|
||||||
|
CMD ["/app/entrypoint.sh"]
|
||||||
|
|||||||
23
containers/app/entrypoint.sh
Executable file
23
containers/app/entrypoint.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# check user is root
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "Please run as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$SANDBOX_USER_ID" ]; then
|
||||||
|
echo "SANDBOX_USER_ID is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# change uid of opendevin user to match the host user
|
||||||
|
# but the group id is not changed, so the user can still access everything under /app
|
||||||
|
usermod -u $SANDBOX_USER_ID opendevin
|
||||||
|
|
||||||
|
# get the user group of /var/run/docker.sock and set opendevin to that group
|
||||||
|
DOCKER_SOCKET_GID=$(stat -c '%g' /var/run/docker.sock)
|
||||||
|
echo "Docker socket group id: $DOCKER_SOCKET_GID"
|
||||||
|
usermod -aG $DOCKER_SOCKET_GID opendevin
|
||||||
|
|
||||||
|
# switch to the user and start the server
|
||||||
|
su opendevin -c "cd /app && uvicorn opendevin.server.listen:app --host 0.0.0.0 --port 3000"
|
||||||
@@ -52,6 +52,7 @@ DEFAULT_CONFIG: dict = {
|
|||||||
ConfigType.USE_HOST_NETWORK: 'false',
|
ConfigType.USE_HOST_NETWORK: 'false',
|
||||||
ConfigType.SSH_HOSTNAME: 'localhost',
|
ConfigType.SSH_HOSTNAME: 'localhost',
|
||||||
ConfigType.DISABLE_COLOR: 'false',
|
ConfigType.DISABLE_COLOR: 'false',
|
||||||
|
ConfigType.SANDBOX_USER_ID: os.getuid() if hasattr(os, 'getuid') else None,
|
||||||
ConfigType.SANDBOX_TIMEOUT: 120,
|
ConfigType.SANDBOX_TIMEOUT: 120,
|
||||||
ConfigType.GITHUB_TOKEN: None
|
ConfigType.GITHUB_TOKEN: None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ if SANDBOX_USER_ID := config.get(ConfigType.SANDBOX_USER_ID):
|
|||||||
elif hasattr(os, 'getuid'):
|
elif hasattr(os, 'getuid'):
|
||||||
USER_ID = os.getuid()
|
USER_ID = os.getuid()
|
||||||
|
|
||||||
|
|
||||||
class DockerSSHBox(Sandbox):
|
class DockerSSHBox(Sandbox):
|
||||||
instance_id: str
|
instance_id: str
|
||||||
container_image: str
|
container_image: str
|
||||||
@@ -62,6 +61,7 @@ class DockerSSHBox(Sandbox):
|
|||||||
timeout: int = 120,
|
timeout: int = 120,
|
||||||
sid: str | None = None,
|
sid: str | None = None,
|
||||||
):
|
):
|
||||||
|
logger.info(f'SSHBox is running as {"opendevin" if RUN_AS_DEVIN else "root"} user with USER_ID={USER_ID} in the sandbox')
|
||||||
# Initialize docker client. Throws an exception if Docker is not reachable.
|
# Initialize docker client. Throws an exception if Docker is not reachable.
|
||||||
try:
|
try:
|
||||||
self.docker_client = docker.from_env()
|
self.docker_client = docker.from_env()
|
||||||
@@ -150,8 +150,10 @@ class DockerSSHBox(Sandbox):
|
|||||||
workdir=SANDBOX_WORKSPACE_DIR,
|
workdir=SANDBOX_WORKSPACE_DIR,
|
||||||
)
|
)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
raise Exception(
|
# This is not a fatal error, just a warning
|
||||||
f'Failed to chown workspace directory for opendevin in sandbox: {logs}')
|
logger.warning(
|
||||||
|
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.'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
exit_code, logs = self.container.exec_run(
|
exit_code, logs = self.container.exec_run(
|
||||||
# change password for root
|
# change password for root
|
||||||
@@ -351,7 +353,6 @@ class DockerSSHBox(Sandbox):
|
|||||||
**network_kwargs,
|
**network_kwargs,
|
||||||
working_dir=SANDBOX_WORKSPACE_DIR,
|
working_dir=SANDBOX_WORKSPACE_DIR,
|
||||||
name=self.container_name,
|
name=self.container_name,
|
||||||
hostname='opendevin_sandbox',
|
|
||||||
detach=True,
|
detach=True,
|
||||||
volumes={
|
volumes={
|
||||||
mount_dir: {
|
mount_dir: {
|
||||||
|
|||||||
Reference in New Issue
Block a user