Files
IOT_SIM/compose.yaml
T
xuyueandClaude Opus 5.5 5e5d29b2aa
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
导出 EXE:实验页工具栏"导出 EXE"把学生的程序编译成一个 exe,拷到机房电脑上双击就能控制真的实训台。编译服务照教材工程编译学生写的原始代码(.NET Framework 4.5 引用程序集,构建时只复制教材工程引用的 13 个到 netfx/;真机版设备类库),不做平台的仿真改写和 API 限制,和 VS 里一样。教材 dll(DeviceCtrlLibrary 及 API 下 7 个、WinFormsDeviceControlLibrary、媒体播放器的两个 Interop)作为资源嵌进 exe,模块初始化器注册 AssemblyResolve 从 exe 里加载,Program.cs 不用改;Properties.Resources 的图片、声音嵌进 exe,代码里按文件名用到的素材(项目 7 的 ring.mp3)启动时放到 exe 旁边的 Resources;媒体播放器的 OcxState 用教材 resx 的默认状态;书上的 ledHelper.Close() 在教材类库里没有,补一个空的扩展方法。23 个实验都能导出(约 0.9 MB),mono + Xvfb 里实测能启动、控件和图片正常。接口 POST /api/export-exe,代码有错时 422,实验页弹框列出报错、点击跳到代码。教材 dll 有版权、GitHub 仓库公开,不进仓库:本机放 textbook-libs/,服务器放 ~/IOT_SIM/textbook-libs/ 由 compose 只读挂载(deploy/README.md),没放时提示原因。使用说明、设计方案、README 同步
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:10:07 +08:00

84 lines
3.1 KiB
YAML

# 部署:先在 .env 里写好 TEACHER_PASSWORD(参考 .env.example),再 docker compose up -d
# HTTPS 由 Nginx Proxy Manager 负责,它在 npm_proxy 网络里转发到 iot-sim:80;步骤见 deploy/README.md
services:
# 启动前把数据卷交给平台用户(Docker 新建数据卷时属主是 root,平台用普通用户运行,写不进去)
init-data:
image: iot-sim:latest
# 和 app 同一个镜像(构建有缓存,不会重复构建);写上 build,新服务器上就不会去 Docker Hub 拉这个镜像
build: .
user: root
command: ['sh', '-c', 'chown -R "$$APP_UID" /data']
volumes:
- data:/data
restart: 'no'
networks: [default]
app:
image: iot-sim:latest
build: .
restart: unless-stopped
depends_on:
init-data:
condition: service_completed_successfully
environment:
# 老师账号(只有一个);学生账号由老师在网页上导入名单
TEACHER_USERNAME: ${TEACHER_USERNAME:-admin}
TEACHER_PASSWORD: ${TEACHER_PASSWORD:?请在 .env 里设置 TEACHER_PASSWORD}
DATA_DIR: /data
# AI 评阅(DeepSeek);不设置密钥就不做 AI 评阅
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
AI_MODEL: ${AI_MODEL:-deepseek-flash}
# 以下可在 .env 里调整,说明见 deploy/README.md
SIM_MAX_RUNS: ${SIM_MAX_RUNS:-150}
SIM_HEAP_LIMIT_MB: ${SIM_HEAP_LIMIT_MB:-128}
SIM_CPU_LIMIT_SECONDS: ${SIM_CPU_LIMIT_SECONDS:-120}
SIM_MAX_RUN_MINUTES: ${SIM_MAX_RUN_MINUTES:-60}
SIM_GRADE_CONCURRENCY: ${SIM_GRADE_CONCURRENCY:-16}
# 容器文件系统只读,只有 /tmp(学生工程的临时目录)和数据卷可写
read_only: true
tmpfs:
- /tmp:size=1g
# 数据库(账号、班级、学生作品),重新部署不会丢
volumes:
- data:/data
# 导出 EXE 用的教材设备类库(真机版 dll,有版权,不进仓库):放在服务器的 ~/IOT_SIM/textbook-libs/,见 deploy/README.md
- ./textbook-libs:/app/textbook-libs:ro
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
# bubblewrap 要创建用户命名空间、挂载新的 /proc,Docker 默认的 seccomp 规则和 /proc 遮罩会拦住它。
# 放开后,学生程序仍然在 bubblewrap 里:断网、看不到其他进程和文件、不是 root。
- seccomp=unconfined
- apparmor=unconfined
- systempaths=unconfined
# 整个平台的资源上限(两个班同时运行约需 1.5 GB),可在 .env 里用 APP_MEM_LIMIT 调整
mem_limit: ${APP_MEM_LIMIT:-4g}
pids_limit: 4096
expose: ['3000']
networks: [default]
caddy:
image: caddy:2-alpine
restart: unless-stopped
depends_on: [app]
expose:
- 80
networks:
default:
npm_proxy:
# NPM 里转发到 http://iot-sim:80
aliases: [iot-sim]
volumes:
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
volumes:
data:
caddy_data:
caddy_config:
networks:
npm_proxy:
external: true