feat(docker): 新增 yggdrasil-runner-bun 沙箱镜像

bun 在 Alpine 3.18 官方仓库不可用,用 bun.sh 安装脚本(多架构、自动检测
/etc/alpine-release 选 musl 变体)。踩坑三处:

1. BUN_INSTALL 必须对管道两侧生效——「VAR=x cmd | bash」只给左侧设变量,
   bash 侧读不到会 fallback 到 $HOME/.bun。改用 export。
2. bun 的 musl 二进制是 C++ 写的,依赖 libstdc++ + libgcc(musl libc 不带
   C++ 运行时),缺失时报 _ZNSt18condition_variable... symbol not found。
   这两个是运行时依赖,apk del 不得删除。
3. 安装路径固定到 /opt/bun + symlink /usr/local/bin/bun,避免依赖 $HOME
   (runner 用户 $HOME 可能不可写)。

本地验证通过:
- bun --version → 1.3.14
- runner 用户(1000:1000) + 只读根 + tmpfs(/code mode=1777, /tmp exec) 下
  成功执行含 interface/类型注解/循环的 TypeScript 代码。

build-runners.sh 加 bun 构建步骤;deploy-to-linux SKILL.md 的 build 循环、
docker save 列表、docker tag 列表、verify 循环全部补 bun(5→6 个镜像)。
This commit is contained in:
xfy 2026-07-21 15:44:38 +08:00
parent 52e5ee2736
commit ca676a6146
4 changed files with 52 additions and 6 deletions

View File

@ -117,7 +117,7 @@ docker buildx build --platform linux/amd64 --load -t localhost/yggdrasil:latest
- 首次约 15-30 分钟Rosetta 下 cargo 全量编译),有 buildkit 缓存后分钟级
- 产物 `localhost/yggdrasil:latest`scratch 运行时层约 16MB
### 5 个 Code Runner 沙箱镜像
### 6 个 Code Runner 沙箱镜像
runner Dockerfile `FROM yggdrasil-runner-base:latest`(无 `localhost/` 前缀),必须**先建 base 再建子镜像**
@ -127,8 +127,8 @@ docker buildx build --platform linux/amd64 --load \
-t localhost/yggdrasil-runner-base:latest docker/runner-base
# 2. 再 tag 无前缀名,让子镜像 FROM 能解析
docker tag localhost/yggdrasil-runner-base:latest yggdrasil-runner-base:latest
# 3. 4 个子镜像(它们 FROM yggdrasil-runner-base:latest)
for img in python node go rust; do
# 3. 5 个子镜像(它们 FROM yggdrasil-runner-base:latest)
for img in python node go rust bun; do
docker buildx build --platform linux/amd64 --load \
-t localhost/yggdrasil-runner-$img:latest docker/runner-$img
docker tag localhost/yggdrasil-runner-$img:latest yggdrasil-runner-$img:latest
@ -140,7 +140,7 @@ done
### 构建验证
```bash
for img in yggdrasil yggdrasil-runner-base yggdrasil-runner-python yggdrasil-runner-node yggdrasil-runner-go yggdrasil-runner-rust; do
for img in yggdrasil yggdrasil-runner-base yggdrasil-runner-python yggdrasil-runner-node yggdrasil-runner-go yggdrasil-runner-rust yggdrasil-runner-bun; do
docker image inspect localhost/$img:latest --format "$img: {{.Architecture}} manifests={{.Manifests}}"
done
# 期望: 每行 amd64 且 manifests=[](单平台,非 manifest list)
@ -157,6 +157,7 @@ docker save \
localhost/yggdrasil-runner-node:latest \
localhost/yggdrasil-runner-go:latest \
localhost/yggdrasil-runner-rust:latest \
localhost/yggdrasil-runner-bun:latest \
-o /tmp/yggdrasil-runners.tar
gzip -f /tmp/yggdrasil-app.tar /tmp/yggdrasil-runners.tar
@ -184,6 +185,7 @@ ssh <host> 'docker tag localhost/yggdrasil-runner-python:latest yggdrasil-runner
ssh <host> 'docker tag localhost/yggdrasil-runner-node:latest yggdrasil-runner-node:latest'
ssh <host> 'docker tag localhost/yggdrasil-runner-go:latest yggdrasil-runner-go:latest'
ssh <host> 'docker tag localhost/yggdrasil-runner-rust:latest yggdrasil-runner-rust:latest'
ssh <host> 'docker tag localhost/yggdrasil-runner-bun:latest yggdrasil-runner-bun:latest'
```
验证短名可解析:

View File

@ -1,13 +1,14 @@
#!/bin/sh
# 构建代码运行器所需的沙箱镜像base → python / node / go / rust
# 构建代码运行器所需的沙箱镜像base → python / node / go / rust / bun
#
# 依赖:本机已安装 docker。构建顺序固定python/node/go/rust 均为 FROM base
# 依赖:本机已安装 docker。构建顺序固定python/node/go/rust/bun 均为 FROM base
# 镜像 tag 与 src/api/code_runner/languages.rs 的注册项严格对应:
# yggdrasil-runner-base:latest
# yggdrasil-runner-python:latest
# yggdrasil-runner-node:latest
# yggdrasil-runner-go:latest
# yggdrasil-runner-rust:latest
# yggdrasil-runner-bun:latest
set -e
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
@ -27,6 +28,9 @@ docker build -t yggdrasil-runner-go:latest "$SCRIPT_DIR/runner-go"
echo "==> Building yggdrasil-runner-rust:latest"
docker build -t yggdrasil-runner-rust:latest "$SCRIPT_DIR/runner-rust"
echo "==> Building yggdrasil-runner-bun:latest"
docker build -t yggdrasil-runner-bun:latest "$SCRIPT_DIR/runner-bun"
echo "==> Done. Images:"
docker images --filter "reference=yggdrasil-runner-*" \
--format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

View File

@ -0,0 +1,25 @@
# Bun 运行沙箱镜像。
# 与 src/api/code_runner/languages.rs 的 bun 注册项对齐:
# image: yggdrasil-runner-bun:latest
# run_cmd: bun run /code/main.ts
#
# bun 在 Alpine 3.18 的官方仓库里没有,用 bun.sh 官方安装脚本(多架构、
# 自动检测 /etc/alpine-release 选 musl 变体。bun 的 musl 二进制是 C++ 写的,
# 运行时依赖 libstdc++ + libgccmusl 只带 libc不带 C++ 运行时),必须保留。
# 安装期另需 unzip + bash + curl安装后卸载 unzip/curl 减小镜像。
FROM yggdrasil-runner-base:latest
# libstdc++ / libgcc 是 bun 运行时依赖不能删curl/unzip/bash 仅供安装期。
# BUN_INSTALL 用 export 对管道两侧生效——「VAR=x cmd | bash」只给左侧设变量
# bash 侧读不到会 fallback 到 $HOME/.bun。指定到 /opt/bun 避免依赖 $HOME
# runner 用户 $HOME 可能不可写或不存在)。
RUN apk add --no-cache curl unzip bash libstdc++ libgcc \
&& export BUN_INSTALL=/opt/bun \
&& curl -fsSL https://bun.sh/install | bash \
&& ln -s /opt/bun/bin/bun /usr/local/bin/bun \
&& apk del curl unzip \
&& bun --version
COPY runner.toml /runner.toml
USER runner

View File

@ -0,0 +1,15 @@
# Bun runner 镜像的运行时默认值(参考用)。
# 实际生效配置由 Rust 侧 LANGUAGES 注册表 + CODE_RUNNER_* 环境变量决定(见
# src/api/code_runner/languages.rs、src/infra/runner_config.rs本文件仅作为
# 镜像构建产物的自描述,便于运维核对镜像与代码的契约一致。
language = "bun"
extension = "ts"
run_command = "bun run /code/main.ts"
allow_network = false
[default_limits]
cpu_cores = 1.0
memory_mb = 256
timeout_secs = 5
output_bytes = 1048576
allow_network = false