部署:bubblewrap 沙箱、编译检查、访问口令、Docker Compose

- 学生程序每次运行放进 bubblewrap:断网,只读挂载系统文件、运行时和自己的工程目录,
  看不到其他进程和服务器环境变量;限制堆内存 128 MB、CPU 时间 120 秒、运行 60 分钟,
  nice 10,全站同时运行上限 150;启动时自检沙箱,不可用时拒绝运行
- 编译检查(SIM001):网络、反射、启动进程、调用本机代码、加载程序集、dynamic 给出中文报错
- 访问口令:cookie 保存口令哈希,按真实 IP 限制输错次数;前端口令页
- 服务端配置集中到 config.ts,可用环境变量覆盖;Bun 直接提供打包好的网页
- Dockerfile(多阶段,服务端打包成单文件)、compose.yaml(Caddy 只收 HTTP,接入 NPM 的
  npm_proxy 网络)、deploy/deploy.sh(本机构建后经 ssh 传到服务器)和部署说明
- 测试:沙箱隔离、内存上限、CPU 时间上限、编译检查

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 15:40:09 +08:00
1 parent 081ac6f924
commit 697f31e9ce
25 files changed
+869 -52

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
// 服务器配置:都可以用环境变量覆盖,开发时不设置也能直接运行。
import { resolve } from 'node:path'
const env = process.env
/** 项目根目录。部署时服务端打包成单个文件,位置变了,用 APP_ROOT 指定 */
export const ROOT = env.APP_ROOT ?? resolve(import.meta.dir, '../..')
export const config = {
port: Number(env.PORT ?? 3000),
/** 运行时程序(编译服务和运行进程共用),部署时用 Release 版 */
runnerDll: resolve(ROOT, env.RUNNER_DLL ?? 'runtime/Sim.Runner/bin/Debug/net10.0/Sim.Runner.dll'),
/** 每次运行的临时工程目录放在这里 */
runsDir: resolve(ROOT, env.RUNS_DIR ?? '.runs'),
samplesDir: resolve(ROOT, env.SAMPLES_DIR ?? 'samples'),
/** 打包好的网页(web/dist);目录不存在时不提供网页,开发时由 Vite 提供 */
staticDir: resolve(ROOT, env.STATIC_DIR ?? 'web/dist'),
/** 学生程序的沙箱:bwrap(bubblewrap)或 none;默认装了 bwrap 就用 */
sandbox: (env.SIM_SANDBOX ?? (Bun.which('bwrap') ? 'bwrap' : 'none')) as 'bwrap' | 'none',
/** 学生程序托管堆上限(MB) */
heapLimitMb: Number(env.SIM_HEAP_LIMIT_MB ?? 128),
/**
* 一次运行最多用多少秒 CPU。正常的界面程序大部分时间在等事件,一小时也用不了几秒;
* 写出死循环的程序会一直占满一个核(服务器只有 2 核),到时间就被结束
*/
cpuLimitSeconds: Number(env.SIM_CPU_LIMIT_SECONDS ?? 120),
/** 全站同时运行的学生程序最多多少个(两个班约 100 人) */
maxRuns: Number(env.SIM_MAX_RUNS ?? 150),
/** 一次运行最长多少分钟,到时自动结束 */
maxRunMinutes: Number(env.SIM_MAX_RUN_MINUTES ?? 60),
/** 访问口令;不设置表示不需要口令(本机开发) */
accessPassword: env.ACCESS_PASSWORD ?? '',
timeZone: env.TZ ?? 'Asia/Shanghai',
}