auto-save 2026-04-19 21:01 (+3, ~2)

This commit is contained in:
2026-04-19 21:01:27 +08:00
parent eea42305fe
commit 1b8a216fbd
5 changed files with 139 additions and 2 deletions

View File

@@ -132,6 +132,13 @@
"message": "auto-save 2026-04-19 18:14 (~1)",
"hash": "867ca6f",
"files_changed": 1
},
{
"ts": "2026-04-19T20:27:26+08:00",
"type": "commit",
"message": "auto-save 2026-04-19 20:15 (~1)",
"hash": "eea4230",
"files_changed": 1
}
]
}

View File

@@ -54,8 +54,8 @@ incus exec "$BUILDER" --project "$PROJECT" -- bash -c '
log "Installing uv"
incus exec "$BUILDER" --project "$PROJECT" -- bash -c '
curl -LsSf https://astral.sh/uv/install.sh | env INSTALL_DIR=/usr/local/bin UV_UNMANAGED_INSTALL=1 sh
ls -la /usr/local/bin/uv
curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --no-modify-path --install-dir /usr/local/bin
ls -la /usr/local/bin/uv /usr/local/bin/uvx
'
log "Installing bun"

53
images/base/patch-uv.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Patch existing lobe-sandbox-base image with correctly-installed uv.
# One-shot fix for the UV_INSTALL_DIR mistake in initial build.
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 from base"
incus init "$IMAGE" "$BUILDER" --project "$PROJECT" -p sandbox-default
incus start "$BUILDER" --project "$PROJECT"
sleep 3
log "Install uv via UV_INSTALL_DIR (correct env var)"
incus exec "$BUILDER" --project "$PROJECT" -- bash -c '
rm -f /usr/local/bin/uv /usr/local/bin/uvx /root/.local/bin/uv /root/.local/bin/uvx 2>/dev/null || true
curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin UV_NO_MODIFY_PATH=1 sh
ls -la /usr/local/bin/uv /usr/local/bin/uvx
'
log "Verify uv as root AND as sandbox user"
incus exec "$BUILDER" --project "$PROJECT" -- uv --version
incus exec "$BUILDER" --project "$PROJECT" --user 1000 -- uv --version
log "Realistic LLM flow: create venv, install requests"
incus exec "$BUILDER" --project "$PROJECT" --user 1000 --cwd /workspace -- bash -c '
uv venv .venv --seed 2>&1 | tail -3
uv pip install --python .venv/bin/python requests 2>&1 | tail -3
.venv/bin/python -c "import requests; print(\"requests\", requests.__version__)"
'
log "Cleanup cache (shrink image)"
incus exec "$BUILDER" --project "$PROJECT" --user 1000 -- rm -rf /home/sandbox/.cache /workspace/.venv
incus exec "$BUILDER" --project "$PROJECT" -- bash -c "rm -rf /root/.cache /tmp/* /var/tmp/*"
log "Stop builder"
incus stop "$BUILDER" --project "$PROJECT"
log "Replace image alias"
incus image delete "$IMAGE" --project "$PROJECT" || true
incus publish "$BUILDER" --project "$PROJECT" --alias "$IMAGE"
log "Cleanup patch container"
incus delete "$BUILDER" --project "$PROJECT"
log "Final"
incus image list --project "$PROJECT"
log "DONE"

18
orchestrator/src/auth.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { MiddlewareHandler } from 'hono';
import { env } from './env.ts';
// 常量时间比较,防 timing attack
const safeEqual = (a: string, b: string): boolean => {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
return diff === 0;
};
export const authMiddleware: MiddlewareHandler = async (c, next) => {
const header = c.req.header('X-Sandbox-Secret');
if (!header || !safeEqual(header, env.orchSecret)) {
return c.json({ error: 'unauthorized' }, 401);
}
await next();
};

59
orchestrator/src/state.ts Normal file
View File

@@ -0,0 +1,59 @@
import { Database } from 'bun:sqlite';
import { mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
import { env } from './env.ts';
await mkdir(dirname(env.stateDbPath), { recursive: true });
const db = new Database(env.stateDbPath, { create: true });
db.exec(`
CREATE TABLE IF NOT EXISTS users (
user_id TEXT PRIMARY KEY,
created_at INTEGER NOT NULL,
provisioned_at INTEGER,
deleted_at INTEGER
);
CREATE TABLE IF NOT EXISTS activity (
user_id TEXT PRIMARY KEY,
last_used INTEGER NOT NULL
);
`);
const now = () => Date.now();
export const state = {
recordCreate: (userId: string): void => {
db.prepare(
`INSERT INTO users (user_id, created_at) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET deleted_at = NULL`,
).run(userId, now());
},
markProvisioned: (userId: string): void => {
db.prepare(`UPDATE users SET provisioned_at = ? WHERE user_id = ?`).run(now(), userId);
},
markDeleted: (userId: string): void => {
db.prepare(`UPDATE users SET deleted_at = ? WHERE user_id = ?`).run(now(), userId);
db.prepare(`DELETE FROM activity WHERE user_id = ?`).run(userId);
},
touch: (userId: string): void => {
db.prepare(
`INSERT INTO activity (user_id, last_used) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET last_used = excluded.last_used`,
).run(userId, now());
},
// 查找过期:最后使用时间距今 > timeoutMs
findIdle: (timeoutMs: number): string[] => {
const cutoff = now() - timeoutMs;
return db
.prepare(`SELECT user_id FROM activity WHERE last_used < ?`)
.all(cutoff)
.map((r: any) => r.user_id);
},
clearActivity: (userId: string): void => {
db.prepare(`DELETE FROM activity WHERE user_id = ?`).run(userId);
},
};