From 2325d5280ac9e630ec6bcb94a19ad2f945521a1d Mon Sep 17 00:00:00 2001 From: Jiachen Zhang Date: Thu, 5 Mar 2026 11:45:45 +0000 Subject: [PATCH] feat(rocklet): support Alpine/musl, Nix and glibc older version compatibility - docker_run.sh: detect musl vs glibc; for musl (Alpine) install glibc-compat from local apk and set up ld symlinks; for Nix symlink glibc/bash from store - Use /tmp/data/logs for non-root to avoid permission issues - runtime_env: run docker_run.sh with bash if available, else sh (minimal images) - pyproject: Pin numpy<=2.2.6 in rocklet deps for compatibility with older glibc. - tests: add integration test for sandbox startup on python/ubuntu/alpine/nix images Signed-off-by: Jiachen Zhang --- pyproject.toml | 1 + rock/deployments/runtime_env.py | 4 +- rock/rocklet/local_files/docker_run.sh | 65 +++++++++++++++++-- .../sdk/sandbox/test_sandbox_images.py | 38 +++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 tests/integration/sdk/sandbox/test_sandbox_images.py diff --git a/pyproject.toml b/pyproject.toml index b02d52359..837e4e220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ rocklet = [ "psutil", "twisted", "gem-llm>=0.1.0", + "numpy<=2.2.6", ] sandbox-actor = [ diff --git a/rock/deployments/runtime_env.py b/rock/deployments/runtime_env.py index 2f105375b..f05d98a04 100644 --- a/rock/deployments/runtime_env.py +++ b/rock/deployments/runtime_env.py @@ -64,7 +64,7 @@ def get_rocklet_start_cmd(self): Makes the docker_run.sh script executable and executes it. """ - cmd = f"cp /tmp/local_files/docker_run.sh /tmp/docker_run.sh && chmod +x /tmp/docker_run.sh && /tmp/docker_run.sh {Port.PROXY}" + cmd = f"cp /tmp/local_files/docker_run.sh /tmp/docker_run.sh && chmod +x /tmp/docker_run.sh && (command -v bash >/dev/null 2>&1 && bash /tmp/docker_run.sh {Port.PROXY} || sh /tmp/docker_run.sh {Port.PROXY})" return cmd @@ -119,7 +119,7 @@ def get_rocklet_start_cmd(self): Makes the docker_run.sh script executable and executes it. """ - cmd = f"cp /tmp/local_files/docker_run.sh /tmp/docker_run.sh && chmod +x /tmp/docker_run.sh && /tmp/docker_run.sh {Port.PROXY}" + cmd = f"cp /tmp/local_files/docker_run.sh /tmp/docker_run.sh && chmod +x /tmp/docker_run.sh && (command -v bash >/dev/null 2>&1 && bash /tmp/docker_run.sh {Port.PROXY} || sh /tmp/docker_run.sh {Port.PROXY})" return cmd diff --git a/rock/rocklet/local_files/docker_run.sh b/rock/rocklet/local_files/docker_run.sh index 701edf4c7..96bae85cf 100755 --- a/rock/rocklet/local_files/docker_run.sh +++ b/rock/rocklet/local_files/docker_run.sh @@ -3,12 +3,63 @@ set -o errexit port=$1 -if [ ! -f /etc/alpine-release ]; then - # Not Alpine Linux system - # Run rocklet - mkdir -p /data/logs - /tmp/miniforge/bin/rocklet --port ${port} >> /data/logs/rocklet_uvicorn.log 2>&1 - +# Log directory: use /tmp if the user is not root, in case of permission issues +if [ "$(whoami)" != "root" ]; then + LOG_DIR="/tmp/data/logs" else - echo "Alpine Linux system is not supported yet" + LOG_DIR="/data/logs" +fi + +is_musl() { + if ldd --version 2>&1 | grep -q musl; then + echo "true" + elif [ -e /lib/ld-musl-x86_64.so.1 ] || [ -e /lib/ld-musl-aarch64.so.1 ] && [ ! -f /usr/glibc-compat/lib/libc.so.6 ]; then + echo "true" + else + echo "false" + fi +} + +is_nix() { + if [ -d /nix/store ]; then + echo "true" + else + echo "false" + fi +} + +# Run rocklet +if [ "$(is_nix)" = "true" ]; then + # NixOS + ln -sf $(ls -d /nix/store/*glibc*/lib 2>/dev/null | head -1) /lib + ln -sf $(ls -d /nix/store/*glibc*/lib64 2>/dev/null | head -1) /lib64 + mkdir -p /bin + ln -sf $(ls -d /nix/store/*bash*/bin/bash 2>/dev/null | head -1) /bin/bash + GCC_LIB=$(ls -d /nix/store/*gcc*lib/lib 2>/dev/null | head -1) + ZLIB_LIB=$(ls -d /nix/store/*zlib*/lib 2>/dev/null | head -1) + NIX_LIBS="" + [ -n "$GCC_LIB" ] && NIX_LIBS="${GCC_LIB}:" + [ -n "$ZLIB_LIB" ] && NIX_LIBS="${NIX_LIBS}${ZLIB_LIB}:" + [ -n "$NIX_LIBS" ] && export LD_LIBRARY_PATH="${NIX_LIBS}${LD_LIBRARY_PATH}" fi + +if [ "$(is_musl)" = "true" ]; then + # musl-based distributions + if [ ! -d /tmp/local_files/alpine_glibc ]; then + echo "Alpine Linux system is not supported yet" + exit 1 + fi + + sed -i "s|https://.*alpinelinux.org|https://mirrors.aliyun.com|g" /etc/apk/repositories + apk add bash + apk add --allow-untrusted --force-overwrite /tmp/local_files/alpine_glibc/*.apk + mkdir -p /lib64 + ln -sf /usr/glibc-compat/lib/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 + ln -sf /usr/glibc-compat/lib/ld-linux-x86-64.so.2 /lib/ld-linux-x86-64.so.2 + mkdir -p "${LOG_DIR}" + /tmp/miniforge/bin/rocklet --port ${port} >> "${LOG_DIR}/rocklet_uvicorn.log" 2>&1 +else + # glibc-based distributions + mkdir -p "${LOG_DIR}" + /tmp/miniforge/bin/rocklet --port ${port} >> "${LOG_DIR}/rocklet_uvicorn.log" 2>&1 +fi \ No newline at end of file diff --git a/tests/integration/sdk/sandbox/test_sandbox_images.py b/tests/integration/sdk/sandbox/test_sandbox_images.py new file mode 100644 index 000000000..66242ffac --- /dev/null +++ b/tests/integration/sdk/sandbox/test_sandbox_images.py @@ -0,0 +1,38 @@ +""" +Verify that sandbox can start and run on each image in the list. +""" +import pytest + +from rock.sdk.sandbox.client import Sandbox +from tests.integration.conftest import SKIP_IF_NO_DOCKER + +SANDBOX_IMAGES_TO_CHECK = [ + # python + "python:3.11", + # ubuntu + "ubuntu:16.04", + "ubuntu:24.04", + # alpine + #"alpine:3.23", + #"alpine:3.14", + # nix + "nixos/nix:2.20.9", + "nixos/nix:2.32.6", +] + +@pytest.mark.parametrize( + "sandbox_instance", + [{"image": img} for img in SANDBOX_IMAGES_TO_CHECK], + ids=SANDBOX_IMAGES_TO_CHECK, + indirect=True, +) + +@pytest.mark.need_admin_and_network +@SKIP_IF_NO_DOCKER +@pytest.mark.asyncio +async def test_sandbox_can_start_with_image(request: pytest.FixtureRequest, sandbox_instance: Sandbox): + image_id = request.node.callspec.id + result = await sandbox_instance.arun(cmd="echo ok", session="default") + assert result.output is not None + assert "ok" in result.output + print(f"PASSED: {image_id}")