diff --git a/.memory/worklog.json b/.memory/worklog.json index 15d2b57..1ff04c1 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -195,6 +195,13 @@ "message": "auto-save 2026-04-19 21:28 (~1)", "hash": "9442547", "files_changed": 1 + }, + { + "ts": "2026-04-19T21:34:28+08:00", + "type": "commit", + "message": "auto-save 2026-04-19 21:34 (~1)", + "hash": "05dc59d", + "files_changed": 1 } ] } diff --git a/images/base/patch-venv.sh b/images/base/patch-venv.sh new file mode 100644 index 0000000..141d57c --- /dev/null +++ b/images/base/patch-venv.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# 补丁:给 sandbox 用户预建 venv,env 由 orchestrator 的 incus.ts 每次 exec 时传入。 +set -euo pipefail + +PROJECT="lobe-sandbox" +BUILDER="sb-patch" +IMAGE="lobe-sandbox-base" +log() { echo "==> [$(date +%H:%M:%S)] $*"; } + +incus info "$BUILDER" --project "$PROJECT" >/dev/null 2>&1 && \ + incus delete "$BUILDER" --project "$PROJECT" --force + +log "Launch" +incus init "$IMAGE" "$BUILDER" --project "$PROJECT" -p sandbox-default +incus start "$BUILDER" --project "$PROJECT" +sleep 3 + +log "Create /home/sandbox/.venv" +incus exec "$BUILDER" --project "$PROJECT" --user 1000 -- \ + uv venv /home/sandbox/.venv --seed --quiet + +log "Sanity: install requests via env-injected VIRTUAL_ENV" +incus exec "$BUILDER" --project "$PROJECT" \ + --user 1000 \ + --env VIRTUAL_ENV=/home/sandbox/.venv \ + --env 'PATH=/home/sandbox/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \ + --env HOME=/home/sandbox \ + -- bash -c ' + uv pip install --quiet requests + python3 -c "import requests; print(\"requests\", requests.__version__, \"OK\")" +' + +log "Cleanup cache" +incus exec "$BUILDER" --project "$PROJECT" --user 1000 -- \ + bash -c "rm -rf /home/sandbox/.cache/uv" +incus exec "$BUILDER" --project "$PROJECT" -- bash -c "rm -rf /root/.cache /tmp/* /var/tmp/*" + +log "Stop + publish" +incus stop "$BUILDER" --project "$PROJECT" +incus image delete "$IMAGE" --project "$PROJECT" || true +incus publish "$BUILDER" --project "$PROJECT" --alias "$IMAGE" + +log "Cleanup" +incus delete "$BUILDER" --project "$PROJECT" +incus image list --project "$PROJECT" +log "DONE" diff --git a/orchestrator/src/incus.ts b/orchestrator/src/incus.ts index efb6152..bacc7b1 100644 --- a/orchestrator/src/incus.ts +++ b/orchestrator/src/incus.ts @@ -84,7 +84,7 @@ export const incus = { } }, - // user=0 root, user=1000 sandbox + // user=0 root, user=1000 sandbox (defaults to sandbox venv + $HOME set for consistent Python env) exec: async ( name: string, cmd: string[], @@ -93,6 +93,19 @@ export const incus = { const args = ['exec', name, ...projArgs]; if (opts.user !== undefined) args.push('--user', String(opts.user)); if (opts.cwd) args.push('--cwd', opts.cwd); + // For sandbox user, bake in venv + HOME so `uv pip install xxx` (no --system) just works + if (opts.user === 1000) { + args.push( + '--env', + 'VIRTUAL_ENV=/home/sandbox/.venv', + '--env', + 'PATH=/home/sandbox/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + '--env', + 'HOME=/home/sandbox', + '--env', + 'LANG=zh_CN.UTF-8', + ); + } args.push('--'); args.push(...cmd); const proc = Bun.spawn(['incus', ...args], {