mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Non root user (#10155)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
18b5139237
commit
c0bb84dfa2
@ -58,34 +58,34 @@ RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs
|
||||
# Default is 60000, but we've seen up to 200000
|
||||
RUN sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs
|
||||
|
||||
RUN groupadd --gid $OPENHANDS_USER_ID app
|
||||
RUN groupadd --gid $OPENHANDS_USER_ID openhands
|
||||
RUN useradd -l -m -u $OPENHANDS_USER_ID --gid $OPENHANDS_USER_ID -s /bin/bash openhands && \
|
||||
usermod -aG app openhands && \
|
||||
usermod -aG openhands openhands && \
|
||||
usermod -aG sudo openhands && \
|
||||
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
RUN chown -R openhands:app /app && chmod -R 770 /app
|
||||
RUN sudo chown -R openhands:app $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
|
||||
RUN chown -R openhands:openhands /app && chmod -R 770 /app
|
||||
RUN sudo chown -R openhands:openhands $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
|
||||
USER openhands
|
||||
|
||||
ENV VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH='/app'
|
||||
|
||||
COPY --chown=openhands:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||
COPY --chown=openhands:openhands --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||
|
||||
COPY --chown=openhands:app --chmod=770 ./microagents ./microagents
|
||||
COPY --chown=openhands:app --chmod=770 ./openhands ./openhands
|
||||
COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
|
||||
COPY --chown=openhands:app pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
|
||||
COPY --chown=openhands:openhands --chmod=770 ./microagents ./microagents
|
||||
COPY --chown=openhands:openhands --chmod=770 ./openhands ./openhands
|
||||
COPY --chown=openhands:openhands --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
|
||||
COPY --chown=openhands:openhands pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
|
||||
|
||||
# This is run as "openhands" user, and will create __pycache__ with openhands:openhands ownership
|
||||
RUN python openhands/core/download.py # No-op to download assets
|
||||
# Add this line to set group ownership of all files/directories not already in "app" group
|
||||
# openhands:openhands -> openhands:app
|
||||
RUN find /app \! -group app -exec chgrp app {} +
|
||||
# openhands:openhands -> openhands:openhands
|
||||
RUN find /app \! -group openhands -exec chgrp openhands {} +
|
||||
|
||||
COPY --chown=openhands:app --chmod=770 --from=frontend-builder /app/build ./frontend/build
|
||||
COPY --chown=openhands:app --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
|
||||
COPY --chown=openhands:openhands --chmod=770 --from=frontend-builder /app/build ./frontend/build
|
||||
COPY --chown=openhands:openhands --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ import os
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
|
||||
class SandboxConfig(BaseModel):
|
||||
"""Configuration for the sandbox.
|
||||
@ -55,6 +57,7 @@ class SandboxConfig(BaseModel):
|
||||
)
|
||||
runtime_container_image: str | None = Field(default=None)
|
||||
user_id: int = Field(default=os.getuid() if hasattr(os, 'getuid') else 1000)
|
||||
logger.debug(f'SandboxConfig user_id default: {user_id}')
|
||||
timeout: int = Field(default=120)
|
||||
remote_runtime_init_timeout: int = Field(default=180)
|
||||
remote_runtime_api_timeout: int = Field(default=10)
|
||||
|
||||
@ -647,7 +647,6 @@ class ActionExecutor:
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.warning('Starting Action Execution Server')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('port', type=int, help='Port to listen on')
|
||||
parser.add_argument('--working-dir', type=str, help='Working directory')
|
||||
|
||||
@ -76,6 +76,7 @@ class RemoteRuntime(ActionExecutionClient):
|
||||
user_id,
|
||||
git_provider_tokens,
|
||||
)
|
||||
logger.debug(f'RemoteRuntime.init user_id {user_id}')
|
||||
if self.config.sandbox.api_key is None:
|
||||
raise ValueError(
|
||||
'API key is required to use the remote runtime. '
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
|
||||
DEFAULT_PYTHON_PREFIX = [
|
||||
@ -23,6 +24,9 @@ def get_action_execution_server_startup_command(
|
||||
python_executable: str = 'python',
|
||||
) -> list[str]:
|
||||
sandbox_config = app_config.sandbox
|
||||
logger.debug(f'app_config {vars(app_config)}')
|
||||
logger.debug(f'sandbox_config {vars(sandbox_config)}')
|
||||
logger.debug(f'override_user_id {override_user_id}')
|
||||
|
||||
# Plugin args
|
||||
plugin_args = []
|
||||
@ -39,9 +43,7 @@ def get_action_execution_server_startup_command(
|
||||
username = override_username or (
|
||||
'openhands' if app_config.run_as_openhands else 'root'
|
||||
)
|
||||
user_id = override_user_id or (
|
||||
sandbox_config.user_id if app_config.run_as_openhands else 0
|
||||
)
|
||||
user_id = override_user_id or (1000 if app_config.run_as_openhands else 0)
|
||||
|
||||
base_cmd = [
|
||||
*python_prefix,
|
||||
@ -62,5 +64,6 @@ def get_action_execution_server_startup_command(
|
||||
|
||||
if not app_config.enable_browser:
|
||||
base_cmd.append('--no-enable-browser')
|
||||
logger.debug(f'get_action_execution_server_startup_command: {base_cmd}')
|
||||
|
||||
return base_cmd
|
||||
|
||||
@ -49,6 +49,61 @@ def init_user_and_working_directory(
|
||||
if username == os.getenv('USER') and username not in ['root', 'openhands']:
|
||||
return None
|
||||
|
||||
# Skip root since it is already created
|
||||
if username != 'root':
|
||||
# Check if the username already exists
|
||||
logger.debug(f'Attempting to create user `{username}` with UID {user_id}.')
|
||||
existing_user_id = -1
|
||||
try:
|
||||
result = subprocess.run(
|
||||
f'id -u {username}', shell=True, check=True, capture_output=True
|
||||
)
|
||||
existing_user_id = int(result.stdout.decode().strip())
|
||||
|
||||
# The user ID already exists, skip setup
|
||||
if existing_user_id == user_id:
|
||||
logger.debug(
|
||||
f'User `{username}` already has the provided UID {user_id}. Skipping user setup.'
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f'User `{username}` already exists with UID {existing_user_id}. Skipping user setup.'
|
||||
)
|
||||
return existing_user_id
|
||||
return None
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Returncode 1 indicates, that the user does not exist yet
|
||||
if e.returncode == 1:
|
||||
logger.debug(
|
||||
f'User `{username}` does not exist. Proceeding with user creation.'
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f'Error checking user `{username}`, skipping setup:\n{e}\n'
|
||||
)
|
||||
raise
|
||||
|
||||
# Add sudoer
|
||||
sudoer_line = r"echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
|
||||
output = subprocess.run(sudoer_line, shell=True, capture_output=True)
|
||||
if output.returncode != 0:
|
||||
raise RuntimeError(f'Failed to add sudoer: {output.stderr.decode()}')
|
||||
logger.debug(f'Added sudoer successfully. Output: [{output.stdout.decode()}]')
|
||||
|
||||
command = (
|
||||
f'useradd -rm -d /home/{username} -s /bin/bash '
|
||||
f'-g root -G sudo -u {user_id} {username}'
|
||||
)
|
||||
output = subprocess.run(command, shell=True, capture_output=True)
|
||||
if output.returncode == 0:
|
||||
logger.debug(
|
||||
f'Added user `{username}` successfully with UID {user_id}. Output: [{output.stdout.decode()}]'
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'Failed to create user `{username}` with UID {user_id}. Output: [{output.stderr.decode()}]'
|
||||
)
|
||||
|
||||
# First create the working directory, independent of the user
|
||||
logger.debug(f'Client working directory: {initial_cwd}')
|
||||
command = f'umask 002; mkdir -p {initial_cwd}'
|
||||
@ -64,57 +119,4 @@ def init_user_and_working_directory(
|
||||
out_str += output.stdout.decode()
|
||||
logger.debug(f'Created working directory. Output: [{out_str}]')
|
||||
|
||||
# Skip root since it is already created
|
||||
if username == 'root':
|
||||
return None
|
||||
|
||||
# Check if the username already exists
|
||||
existing_user_id = -1
|
||||
try:
|
||||
result = subprocess.run(
|
||||
f'id -u {username}', shell=True, check=True, capture_output=True
|
||||
)
|
||||
existing_user_id = int(result.stdout.decode().strip())
|
||||
|
||||
# The user ID already exists, skip setup
|
||||
if existing_user_id == user_id:
|
||||
logger.debug(
|
||||
f'User `{username}` already has the provided UID {user_id}. Skipping user setup.'
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f'User `{username}` already exists with UID {existing_user_id}. Skipping user setup.'
|
||||
)
|
||||
return existing_user_id
|
||||
return None
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Returncode 1 indicates, that the user does not exist yet
|
||||
if e.returncode == 1:
|
||||
logger.debug(
|
||||
f'User `{username}` does not exist. Proceeding with user creation.'
|
||||
)
|
||||
else:
|
||||
logger.error(f'Error checking user `{username}`, skipping setup:\n{e}\n')
|
||||
raise
|
||||
|
||||
# Add sudoer
|
||||
sudoer_line = r"echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
|
||||
output = subprocess.run(sudoer_line, shell=True, capture_output=True)
|
||||
if output.returncode != 0:
|
||||
raise RuntimeError(f'Failed to add sudoer: {output.stderr.decode()}')
|
||||
logger.debug(f'Added sudoer successfully. Output: [{output.stdout.decode()}]')
|
||||
|
||||
command = (
|
||||
f'useradd -rm -d /home/{username} -s /bin/bash '
|
||||
f'-g root -G sudo -u {user_id} {username}'
|
||||
)
|
||||
output = subprocess.run(command, shell=True, capture_output=True)
|
||||
if output.returncode == 0:
|
||||
logger.debug(
|
||||
f'Added user `{username}` successfully with UID {user_id}. Output: [{output.stdout.decode()}]'
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'Failed to create user `{username}` with UID {user_id}. Output: [{output.stderr.decode()}]'
|
||||
)
|
||||
return None
|
||||
|
||||
@ -14,12 +14,16 @@ ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry \
|
||||
|
||||
{% macro setup_base_system() %}
|
||||
|
||||
# Set PATH early to ensure system commands are available
|
||||
ENV PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
||||
|
||||
# Install base system dependencies
|
||||
|
||||
{% if (('ubuntu' in base_image) or ('mswebench' in base_image)) %}
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \
|
||||
coreutils util-linux procps findutils grep sed \
|
||||
{%- if (base_image.endswith(':latest') or base_image.endswith(':24.04') or ('mswebench' in base_image)) -%}
|
||||
libgl1 \
|
||||
{%- else %}
|
||||
@ -41,6 +45,7 @@ RUN apt-get update && \
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \
|
||||
coreutils util-linux procps findutils grep sed \
|
||||
libgl1-mesa-glx \
|
||||
libasound2-plugins libatomic1 \
|
||||
# Install Docker dependencies
|
||||
@ -58,15 +63,30 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/openhands/
|
||||
# Add /openhands/bin to PATH
|
||||
ENV PATH="/openhands/bin:${PATH}"
|
||||
|
||||
# Remove UID 1000 named pn or ubuntu, so the 'openhands' user can be created from ubuntu hosts
|
||||
# Remove UID 1000 and GID 1000 users/groups that might conflict with openhands user
|
||||
RUN (if getent passwd 1000 | grep -q pn; then userdel pn; fi) && \
|
||||
(if getent passwd 1000 | grep -q ubuntu; then userdel ubuntu; fi)
|
||||
(if getent passwd 1000 | grep -q ubuntu; then userdel ubuntu; fi) && \
|
||||
(if getent group 1000 | grep -q pn; then groupdel pn; fi) && \
|
||||
(if getent group 1000 | grep -q ubuntu; then groupdel ubuntu; fi)
|
||||
|
||||
# Create openhands group and user
|
||||
RUN groupadd -g 1000 openhands && \
|
||||
useradd -u 1000 -g 1000 -m -s /bin/bash openhands && \
|
||||
usermod -aG sudo openhands && \
|
||||
echo 'openhands ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
|
||||
# Set empty password for openhands user to allow passwordless su
|
||||
passwd -d openhands && \
|
||||
# Set empty password for root user as well to ensure su works in both directions
|
||||
passwd -d root && \
|
||||
# Ensure root can su to openhands without password by configuring PAM
|
||||
sed -i '/pam_rootok.so/d' /etc/pam.d/su && \
|
||||
sed -i '1i auth sufficient pam_rootok.so' /etc/pam.d/su
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /openhands && \
|
||||
mkdir -p /openhands/logs && \
|
||||
mkdir -p /openhands/poetry
|
||||
mkdir -p /openhands/poetry && \
|
||||
chown -R openhands:openhands /openhands
|
||||
|
||||
|
||||
# ================================================================
|
||||
@ -147,14 +167,16 @@ RUN if [ -z "${RELEASE_TAG}" ]; then \
|
||||
if [ -d "${OPENVSCODE_SERVER_ROOT}" ]; then rm -rf "${OPENVSCODE_SERVER_ROOT}"; fi && \
|
||||
mv ${RELEASE_TAG}-linux-${arch} ${OPENVSCODE_SERVER_ROOT} && \
|
||||
cp ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/openvscode-server ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/code && \
|
||||
rm -f ${RELEASE_TAG}-linux-${arch}.tar.gz
|
||||
rm -f ${RELEASE_TAG}-linux-${arch}.tar.gz && \
|
||||
chown -R openhands:openhands ${OPENVSCODE_SERVER_ROOT}
|
||||
|
||||
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro install_vscode_extensions() %}
|
||||
# Install our custom extension
|
||||
# Install our custom extensions as openhands user
|
||||
USER openhands
|
||||
RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world && \
|
||||
cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/hello-world/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world/
|
||||
|
||||
@ -165,27 +187,72 @@ RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor && \
|
||||
RUN rm -rf ${OPENVSCODE_SERVER_ROOT}/extensions/{handlebars,pug,json,diff,grunt,ini,npm}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro install_dependencies() %}
|
||||
# Install all dependencies
|
||||
{% macro install_dependencies_root() %}
|
||||
# Install system-level dependencies that require root
|
||||
USER root
|
||||
RUN \
|
||||
{% if enable_browser %}
|
||||
# Install system dependencies for Playwright (requires root)
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libnss3 libnspr4 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 \
|
||||
libxdamage1 libxrandr2 libgbm1 libxss1 && \
|
||||
# Install libasound2 - try new package name first (Ubuntu 24.04+), fallback to old name
|
||||
(apt-get install -y --no-install-recommends libasound2t64 || apt-get install -y --no-install-recommends libasound2) && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
# Install Playwright browsers in shared location accessible to all users
|
||||
export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers && \
|
||||
mkdir -p /opt/playwright-browsers && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \
|
||||
# Set proper permissions for shared access
|
||||
chmod -R 755 /opt/playwright-browsers && \
|
||||
# Create cache directories and symlinks for both users
|
||||
mkdir -p /home/openhands/.cache && \
|
||||
mkdir -p /root/.cache && \
|
||||
ln -sf /opt/playwright-browsers /home/openhands/.cache/ms-playwright && \
|
||||
ln -sf /opt/playwright-browsers /root/.cache/ms-playwright && \
|
||||
chown -h openhands:openhands /home/openhands/.cache/ms-playwright && \
|
||||
# Set environment variable for all users
|
||||
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /etc/environment && \
|
||||
{% endif %}
|
||||
# Set environment variables (requires root)
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
|
||||
# Set permissions for shared read-only access
|
||||
chmod -R 755 /openhands/poetry && \
|
||||
chmod -R 755 /openhands/micromamba && \
|
||||
chown -R openhands:openhands /openhands/poetry && \
|
||||
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
|
||||
chown -R openhands:openhands /openhands/workspace && \
|
||||
chown -R openhands:openhands /openhands/micromamba && \
|
||||
# Ensure PATH includes system binaries early in startup
|
||||
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/environment && \
|
||||
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/bash.bashrc && \
|
||||
# Set up conda environment activation for all users
|
||||
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /etc/bash.bashrc && \
|
||||
echo 'micromamba activate openhands 2>/dev/null || true' >> /etc/bash.bashrc && \
|
||||
# Set up environment for root user
|
||||
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /root/.bashrc && \
|
||||
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /root/.bashrc && \
|
||||
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /root/.bashrc && \
|
||||
echo 'micromamba activate openhands 2>/dev/null || true' >> /root/.bashrc && \
|
||||
# Clean up system packages (requires root)
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro install_dependencies_user() %}
|
||||
# Install user-level dependencies as openhands user
|
||||
WORKDIR /openhands/code
|
||||
|
||||
USER openhands
|
||||
RUN \
|
||||
/openhands/micromamba/bin/micromamba config set changeps1 False && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && \
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.12 && \
|
||||
# Install project dependencies
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \
|
||||
# Update and install additional tools
|
||||
# (There used to be an "apt-get update" here, hopefully we can skip it.)
|
||||
{% if enable_browser %}/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \{% endif %}
|
||||
# Set environment variables
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
|
||||
# Set permissions
|
||||
chmod -R g+rws /openhands/poetry && \
|
||||
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
|
||||
# Clean up
|
||||
# Clean up user caches
|
||||
/openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
|
||||
/openhands/micromamba/bin/micromamba clean --all
|
||||
|
||||
{% endmacro %}
|
||||
@ -203,7 +270,16 @@ RUN \
|
||||
RUN mkdir -p /openhands/micromamba/bin && \
|
||||
/bin/bash -c "PREFIX_LOCATION=/openhands/micromamba BIN_FOLDER=/openhands/micromamba/bin INIT_YES=no CONDA_FORGE_YES=yes $(curl -L https://micro.mamba.pm/install.sh)" && \
|
||||
/openhands/micromamba/bin/micromamba config remove channels defaults && \
|
||||
/openhands/micromamba/bin/micromamba config list
|
||||
/openhands/micromamba/bin/micromamba config list && \
|
||||
chown -R openhands:openhands /openhands/micromamba && \
|
||||
# Create read-only shared access to micromamba for all users
|
||||
# This allows both root and openhands users to access the same packages
|
||||
# while maintaining security by keeping openhands as the owner
|
||||
chmod -R 755 /openhands/micromamba && \
|
||||
# Create a separate writable location for root's micromamba cache/config
|
||||
mkdir -p /root/.local/share/micromamba && \
|
||||
# Set up environment variables for system-wide access
|
||||
echo 'export PATH="/openhands/micromamba/bin:$PATH"' >> /etc/environment
|
||||
|
||||
# Create the openhands virtual environment and install poetry and python
|
||||
RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \
|
||||
@ -214,40 +290,75 @@ RUN \
|
||||
if [ -d /openhands/code ]; then rm -rf /openhands/code; fi && \
|
||||
mkdir -p /openhands/code/openhands && \
|
||||
touch /openhands/code/openhands/__init__.py && \
|
||||
chown -R openhands:openhands /openhands/code && \
|
||||
# Set global git configuration to ensure proper author/committer information
|
||||
git config --global user.name "openhands" && \
|
||||
git config --global user.email "openhands@all-hands.dev"
|
||||
|
||||
COPY ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
|
||||
{{ install_dependencies() }}
|
||||
{{ install_dependencies_user() }}
|
||||
{{ install_dependencies_root() }}
|
||||
|
||||
# ================================================================
|
||||
# END: Build Runtime Image from Scratch
|
||||
# ================================================================
|
||||
{% endif %}
|
||||
|
||||
# Ensure openhands user/group and base dirs exist even when not building from scratch
|
||||
USER root
|
||||
RUN \
|
||||
# Ensure group exists (prefer GID 1000 if available)
|
||||
if ! getent group openhands >/dev/null 2>&1; then \
|
||||
if getent group 1000 >/dev/null 2>&1; then groupadd openhands; else groupadd -g 1000 openhands; fi; \
|
||||
fi && \
|
||||
# Ensure user exists (prefer UID 1000 if available)
|
||||
if ! id -u openhands >/dev/null 2>&1; then \
|
||||
if getent passwd 1000 >/dev/null 2>&1; then useradd -m -s /bin/bash -g openhands openhands; else useradd -u 1000 -g openhands -m -s /bin/bash openhands; fi; \
|
||||
fi && \
|
||||
# Ensure home and required directories exist before later steps
|
||||
mkdir -p /home/openhands && \
|
||||
mkdir -p /openhands && \
|
||||
mkdir -p $(dirname ${OPENVSCODE_SERVER_ROOT}) && \
|
||||
# Ensure ownership is correct for all OpenHands paths
|
||||
chown -R openhands:openhands /home/openhands || true && \
|
||||
chown -R openhands:openhands /openhands || true
|
||||
|
||||
{{ setup_vscode_server() }}
|
||||
|
||||
# ================================================================
|
||||
# Copy Project source files
|
||||
# ================================================================
|
||||
RUN if [ -d /openhands/code/openhands ]; then rm -rf /openhands/code/openhands; fi
|
||||
COPY ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
|
||||
RUN if [ -d /openhands/code/microagents ]; then rm -rf /openhands/code/microagents; fi
|
||||
COPY ./code/microagents /openhands/code/microagents
|
||||
COPY ./code/openhands /openhands/code/openhands
|
||||
RUN chmod a+rwx /openhands/code/openhands/__init__.py
|
||||
|
||||
COPY --chown=openhands:openhands ./code/microagents /openhands/code/microagents
|
||||
COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands
|
||||
RUN chmod a+rwx /openhands/code/openhands/__init__.py && \
|
||||
chown -R openhands:openhands /openhands/code
|
||||
|
||||
|
||||
# ================================================================
|
||||
# END: Build from versioned image
|
||||
# ================================================================
|
||||
{% if build_from_versioned %}
|
||||
{{ install_dependencies() }}
|
||||
{{ install_dependencies_user() }}
|
||||
{{ install_dependencies_root() }}
|
||||
{{ install_vscode_extensions() }}
|
||||
{% endif %}
|
||||
|
||||
# Install extra dependencies if specified
|
||||
{% if extra_deps %}RUN {{ extra_deps }} {% endif %}
|
||||
# Install extra dependencies if specified (as openhands user)
|
||||
{% if extra_deps %}
|
||||
USER openhands
|
||||
RUN {{ extra_deps }}
|
||||
{% endif %}
|
||||
|
||||
# Set up environment for openhands user
|
||||
USER root
|
||||
RUN \
|
||||
# Set up environment for openhands user
|
||||
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /home/openhands/.bashrc && \
|
||||
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /home/openhands/.bashrc && \
|
||||
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /home/openhands/.bashrc && \
|
||||
echo 'micromamba activate openhands 2>/dev/null || true' >> /home/openhands/.bashrc && \
|
||||
chown openhands:openhands /home/openhands/.bashrc
|
||||
|
||||
@ -166,7 +166,10 @@ def test_generate_dockerfile_build_from_scratch():
|
||||
assert 'python=3.12' in dockerfile_content
|
||||
|
||||
# Check the update command
|
||||
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
|
||||
assert (
|
||||
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
|
||||
in dockerfile_content
|
||||
)
|
||||
assert (
|
||||
'/openhands/micromamba/bin/micromamba run -n openhands poetry install'
|
||||
in dockerfile_content
|
||||
@ -188,7 +191,10 @@ def test_generate_dockerfile_build_from_lock():
|
||||
assert 'poetry install' not in dockerfile_content
|
||||
|
||||
# These update commands SHOULD still in the dockerfile
|
||||
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
|
||||
assert (
|
||||
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
|
||||
in dockerfile_content
|
||||
)
|
||||
|
||||
|
||||
def test_generate_dockerfile_build_from_versioned():
|
||||
@ -206,7 +212,10 @@ def test_generate_dockerfile_build_from_versioned():
|
||||
|
||||
# this SHOULD exist when build from versioned
|
||||
assert 'poetry install' in dockerfile_content
|
||||
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
|
||||
assert (
|
||||
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
|
||||
in dockerfile_content
|
||||
)
|
||||
|
||||
|
||||
def test_get_runtime_image_repo_and_tag_eventstream():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user