mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08: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:
parent
eb7703a44e
commit
ccbbabac1c
17
.github/workflows/ghcr.yml
vendored
17
.github/workflows/ghcr.yml
vendored
@ -42,8 +42,21 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete huge unnecessary tools folder
|
||||
run: rm -rf /opt/hostedtoolcache
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
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 }}
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
@ -32,7 +32,8 @@ FROM python:3.12-slim as runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV RUN_AS_DEVIN=false
|
||||
ENV RUN_AS_DEVIN=true
|
||||
ENV SANDBOX_USER_ID=1000
|
||||
ENV USE_HOST_NETWORK=false
|
||||
ENV SSH_HOSTNAME=host.docker.internal
|
||||
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 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 \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH='/app'
|
||||
|
||||
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 ./agenthub ./agenthub
|
||||
@ -55,4 +66,17 @@ RUN playwright install --with-deps chromium
|
||||
|
||||
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.SSH_HOSTNAME: 'localhost',
|
||||
ConfigType.DISABLE_COLOR: 'false',
|
||||
ConfigType.SANDBOX_USER_ID: os.getuid() if hasattr(os, 'getuid') else None,
|
||||
ConfigType.SANDBOX_TIMEOUT: 120,
|
||||
ConfigType.GITHUB_TOKEN: None
|
||||
}
|
||||
|
||||
@ -41,7 +41,6 @@ if SANDBOX_USER_ID := config.get(ConfigType.SANDBOX_USER_ID):
|
||||
elif hasattr(os, 'getuid'):
|
||||
USER_ID = os.getuid()
|
||||
|
||||
|
||||
class DockerSSHBox(Sandbox):
|
||||
instance_id: str
|
||||
container_image: str
|
||||
@ -62,6 +61,7 @@ class DockerSSHBox(Sandbox):
|
||||
timeout: int = 120,
|
||||
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.
|
||||
try:
|
||||
self.docker_client = docker.from_env()
|
||||
@ -150,8 +150,10 @@ class DockerSSHBox(Sandbox):
|
||||
workdir=SANDBOX_WORKSPACE_DIR,
|
||||
)
|
||||
if exit_code != 0:
|
||||
raise Exception(
|
||||
f'Failed to chown workspace directory for opendevin in sandbox: {logs}')
|
||||
# This is not a fatal error, just a warning
|
||||
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:
|
||||
exit_code, logs = self.container.exec_run(
|
||||
# change password for root
|
||||
@ -351,7 +353,6 @@ class DockerSSHBox(Sandbox):
|
||||
**network_kwargs,
|
||||
working_dir=SANDBOX_WORKSPACE_DIR,
|
||||
name=self.container_name,
|
||||
hostname='opendevin_sandbox',
|
||||
detach=True,
|
||||
volumes={
|
||||
mount_dir: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user