# 本地开发依赖 开发环境用 Compose 提供数据库和消息队列。单机交付使用独立的受限服务,安装入口见 [单机交付说明](release/README.md)。管理员账号、题包导入、发布、下架和审计见 [题目维护运维说明](admin-problems.md)。 ## 启动开发依赖 将 `.env.example` 复制为 `.env`,在仓库根目录运行: ```bash make infra-config make infra-up make infra-down ``` `compose.dev.yml` 只启动 PostgreSQL 和 RabbitMQ,端口绑定 localhost。 `infra-down` 保留数据卷。API 存活检查和前端预览可以先独立启动。 不要在这个 Compose 或宿主机上运行学生代码,也不要将示例口令用于正式部署。 开发镜像固定 PostgreSQL 18.6 和 RabbitMQ 4.3.6;正式实验还需锁定镜像摘要。 配置检查只检查 Compose 结构。服务健康和备份恢复要在实际环境验证。 镜像用法见官方 [PostgreSQL](https://hub.docker.com/_/postgres) 和 [RabbitMQ](https://hub.docker.com/_/rabbitmq) 说明。 ## 单机 helper 工厂 `kf-sandbox factory --config /etc/kf-judge/runtime.json` 启动 Root 工厂。 普通 Worker 连接配置中的 `state_root/factory/control.sock`。 安装 `deploy/kf-sandbox-factory.service` 示例前,管理员需核对固定二进制、不可写的 GNU 镜像与配置、Worker UID/GID,以及 Root 所有且与监督器 cgroup 分离的任务委派根。 工厂读回服务和任务预算,每次编译、测试点运行都经过完整隔离检查。 工厂无等待队列,只接收有界 run ID。槽位满、重复 ID 和未知字段立即拒绝。 每次请求创建全新的单线程 helper,继承已认证连接和工厂生命线。 额外控制帧、断连或生命线消失会终止任务树。ID 先进入持久记录,最多 4096 个, 跨重启继续拒绝重放。清理失败时保留身份租约并停止分配;恢复只处理已记录的 归属和 inode,未知资源保留并拒绝启动。 ### 轮换记录周期 可信 Root 参数 `--max-requests` 设置本周期受理上限,范围 1 到 4096,默认 4096。 容量用尽后,先停止任务并核对回收,再由管理员维护记录周期。 `kf-sandbox rotate-factory-cycle --config ROOT_CONFIG --cycle-id ID` 在受限监督器作用域 中核对配置和镜像、取得工厂排他锁,确认任务、身份、捕获目录、委派 cgroup 均为空, 且控制 socket 已消失,才归档请求记录到 Root 私有 `factory/cycles/`。 归档文件和目录经 fsync 后清空当前记录,最多保留 64 个周期;历史 ID 继续用于拒绝重放。 正常启动、重启、寿命结束和容量用尽不会自动轮换或删除历史记录。 若归档完成后中断,先排空,再显式用同一 cycle ID 重试。未知或不完整记录需要检查, 不能删日志续用工厂。轮换入口不启动服务。新安装路径的轮换、崩溃恢复和跨周期拒绝 仍需独立 VM 验收。 ## 单机提交服务的启动与停止 `kf-worker judge-submission` 是受控本地适配入口;常驻 `kf-worker serve --config` 使用有界 AMQP 通知和认证后的 Coordinator claim。 配置见[判题工程说明](../judge/README.md#单机持久化协调与常驻-worker)。 Worker 校验不可变清单摘要,将 C17/C++20 映射到固定 GNU 命令,编译一次。 产物经完整清理后封印并计算 SHA-256;每个测试点单独申请 helper、stdin 和工作区。 期望输出留在比较端。Worker 用 `sha2 0.10.9` 校验输入与产物;Sandbox 的同版本 开发依赖仅生成独立 VM 测试数据,Engine 不依赖加密或 OS 库。 ### 启动顺序 1. 用独立迁移账号执行 Backend Alembic。平台与协调器登录账号分别继承各自的 NOLOGIN 角色;平台只插入初始评测,执行状态由 Coordinator 写入。 2. 为平台准备私有工件目录,通过应用服务导入不可变题目并创建测试账号。 3. 验证 GNU 镜像、Root 配置、工厂服务和任务 cgroup 的实际限额,启动工厂。 4. 独立启动 API、Coordinator、outbox publisher 和普通 Worker。HTTP/AMQP 使用 数值 loopback,PostgreSQL 使用 loopback/Unix socket;启动入口不创建表。 5. 检查 Coordinator 数据库权限与 readiness,再在独立 VM 验证执行隔离。 Root 工厂不持有平台/MQ/数据库口令,用户运行镜像不含服务文件。 平台配置包括私有 `KF_OJ_ARTIFACT_ROOT`、工件容量和可信 `KF_OJ_JUDGE_ADMISSION`。工具链、编译/运行预算、作业期限、并发/prefetch/retry 必须与 Coordinator/Worker 一致。单机并发 1、prefetch 1,共享任务身份池。 源码最多 256 KiB,测试点最多 256 个;stdout/stderr 各不超过 file_bytes, 合计不超过 workdir_bytes。增加 Worker 数量不会扩大节点槽位。 以下开发命令均在 `backend/` 运行;迁移账号与平台账号分别配置: ```sh uv run --locked alembic upgrade head # 私有迁移账号,仅执行受控 schema 操作 uv run --locked kf-accounts create local_user # 换成平台角色;终端读取密码并二次确认 uv run --locked kf-problems /srv/private-import uv run --locked uvicorn kf_oj.api.app:create_app --factory --host 127.0.0.1 --port 8000 \ --no-proxy-headers --limit-concurrency 32 --backlog 64 uv run --locked python -m kf_oj.infra.messaging.publisher ``` 账号和导入目录由管理员准备,浏览器不能传入私有路径。导入格式见 [题目模块](../backend/src/kf_oj/modules/problems/README.md)。题库、题面和提交使用 不可变版本 UUID;更新需发布新版本。临时验收使用合成数据和测试口令,业务数据与 配置放在仓库外。 取消功能还需 API 配置 `KF_OJ_COORDINATOR_URL=http://127.0.0.1:8100` 和 `KF_OJ_COORDINATOR_TOKEN`,后者对应 Coordinator 的 platform_token。 独立生成服务令牌,保存在私有配置中。Coordinator/Worker JSON 为 0600、单链接、 有界文件;Root 持有镜像、配置和二进制。运行前读回节点、服务和任务的 memory/swap/pids/CPU/文件/期限上限。 开发前端在 `frontend/` 运行 `pnpm dev`,默认 127.0.0.1:5173。 平台明确允许相同 Origin,`/api` 使用同源代理。生产入口还需 HTTPS、安全 Cookie、 SPA 回退和 API 反向代理。登录后在题面提交源码,按同一 submission UUID 查看状态; 等待、取消中和系统失败各有独立状态。 ### 停止与故障恢复 先向 Worker 发送 SIGTERM,使当前任务取消并清理,再停止 publisher/API/Coordinator, 最后停止工厂和本地依赖。强制死亡后的旧 attempt 必须撤销并取得停止证明。 `cleanup_failed` 保留未确认资源,管理员检查故障来源后再恢复工厂/Worker。 不要递归清空任务目录或强改结果。 broker 暂停期间,API 保留 queued/outbox;恢复后 publisher confirm 与 Coordinator 补偿重新投递。重启 Worker/Coordinator 时保留配置、工件、日志、DB 和 attempt。 旧租约不能覆盖新结果,基础设施重试有次数上限。 容量满时返回 429 或 503。先检查待执行和清理状态,再恢复服务或核对扩容。 `kf-artifacts --limit 100` 只回收达到年龄要求的无引用工件和已标记删除项, 保留仍被题目或提交引用的源码。Root 恢复还需核对 journal、inode 和实际排空。 ## 四语言节点的受控升级 `judge.admission.example.json` 是显式 v1/v2 注册表, `sandbox.managed.runtime.example.json` 使用一槽位共享 Root 配置。 原 v1 示例语义保留。示例中的 `aaaaaaaa…` 是无权限占位摘要,必须换成固定镜像 manifest.json 的实际 SHA-256,并逐文件验证版本、字节和权限。 准入注册表与 Root runtime_images 使用同一完整身份;Root 镜像路径仅放入私有配置。 1. 停止创建提交和消费投递,向普通 Worker 发 SIGTERM,保留 DB、清单、工件和 journal。 2. 确认工厂 stopped/cleanup_complete,读回空任务 cgroup 和 owned 资源。 发现未知资源或未排空时停止升级并保留证据。 3. 用独立账号执行 Backend Alembic upgrade。含新语言数据时旧迁移拒绝降级; 保留这些数据。平台与 Coordinator 使用各自最小权限角色,Worker 无 DB 账号。 4. 安装固定版本 Backend/Coordinator/Worker/Sandbox,同时更新普通服务的同一 `judge_admission`/`admission`。Root 配置单独保管,旧消费者不混用新协议。 5. 设置并读回节点、控制面、工厂和任务的 CPU/memory/swap/pids/文件/期限上限。 至少预留一份 512 MiB 编译任务和独立控制面;Java 运行示例总预算 384 MiB, Xmx 只是堆上限。并发 1、prefetch 1,使用共享 UID 池。 6. 在断网、无宿主共享挂载、无业务凭据的受限独立 VM 验证四语言、故障和排空, 再恢复服务。生产启用单独安排。 Python `main.py` 仅支持标准库。Java `Main.java` 使用默认包 Main,最多 256 个 有界编译 class。请求不在线安装依赖,不运行 pip/uvx,不接收解释器参数、JAR 或 agents。 题目版本决定语言和预算,旧题目、草稿和报告保留;Rust 暂不支持。 CE、RE、真实超限与 SystemError 的区别见 [判题说明](../judge/README.md#python--java-与明确协议版本)。 Worker 的 `control_timeout_ms` 可设为 1000 到 5000,缺省 2000。 按实际领取、续租和停止耗时选择,并核对租约与恢复流程。 该值不改变学生阶段/作业期限、重试次数、工件下载的固定 2 秒期限或 1 秒连接期限。 回退旧软件前先排空 v2 任务,保留 v2 不可变版本和报告供兼容消费者读取。 恢复时沿用停止与故障流程,保持 outbox 和有限重试。 公开示例不含业务令牌;数据库、AMQP 和服务令牌分别在仓库外生成,Root 不持有它们。 ## 验收与 CI 所有学生源码、隔离、超限和故障测试都在无 NIC、无宿主共享挂载、无业务凭据且 整体上限已读回的独立 VM 运行。配置解析、readiness 和存活探测不能替代执行验收。 仓库不自动部署生产。 需要 workflow 时,在 GitHub 私有仓库 `KiriAky107/KF-OnlineJudge` 设置有限并发执行。 Gitea 保存工程提交,不运行 workflow。