rootHytx/NervCTF

GitHub: rootHytx/NervCTF

NervCTF 是一套基于 CTFd 的 CTF 赛事管理工具链,通过 YAML 一键部署题目、分配容器并检测 flag 共享。

Stars: 3 | Forks: 0

# NervCTF 一个用于在 CTFd 上运行 CTF 比赛的 CLI + 服务器工具链。只需一条命令,即可从 YAML 部署题目、为每支队伍分配临时的容器,并检测 flag 共享行为,全程无需手动调用 CTFd API。 ## 工作原理 NervCTF 包含两个组件,部署一次后即可保持运行: ``` Your machine CTFd host (single-machine mode) ───────────── ───────────────────────────────────────────────── ┌─ Docker Compose stack ────────────────────────┐ nervctf CLI ─── Token ──▶ │ remote-monitor:33133 ─── SQL ──▶ MariaDB │ │ │ └──▶ uploads dir │ │ instance manager │ │ (docker daemon, local) │ │ CTFd (nginx+gunicorn)│ │ nervctf plugin │ └───────────────────────────────────────────────┘ ``` **`nervctf` (CLI)** — 在你的本地计算机上运行。负责读取 `challenge.yml` 文件,对其进行校验,并同步到远程的 monitor。它还可以运行 `nervctf setup` 来初始化服务器。 **`remote-monitor` (server)** — 在 CTFd 宿主机的 Docker 中运行。它直接写入 CTFd 的 MariaDB(无需 CTFd API key),为每支队伍的题目管理容器实例,并提供管理仪表板。 **`nervctf_instance` (CTFd 插件)** — 由 `nervctf setup` 部署。接入 CTFd 的题目系统,使得玩家可以直接在 CTFd 界面中请求、续期和停止容器。 在**分离式机器模式**下,容器将在单独的 worker 节点上运行。CLI 会将题目文件直接 rsync 到 runner 上;monitor 则通过 SSH 控制容器。 ``` Your machine CTFd host Runner node ──────────── ────────────────────────────── ────────────────── nervctf CLI ──▶ remote-monitor ─── SSH ──────▶ docker daemon │ │ └── rsync ──────┘ (challenge files) ``` ## 安装说明 ### 预编译二进制文件(推荐) 从 [GitHub Releases](https://github.com/rootHytx/NervCTF/releases) 下载: - `nervctf-linux-x86_64-static` — 适用于 Linux 的 CLI(静态编译,无依赖) - `nervctf-linux-aarch64` — 适用于 ARM64 的 CLI - `nervctf-windows-x86_64.exe` — 适用于 Windows 的 CLI - `remote-monitor-linux-x86_64-static` — 服务器二进制文件(部署到 CTFd 宿主机) 将它们重命名为 `nervctf` 和 `remote-monitor`,并放置在你的 `PATH` 路径中。 ### 从源码构建 ``` # 使用 Nix(提供所有依赖) nix develop .# --command cargo build --release # 不使用 Nix (Debian/Ubuntu) sudo apt install build-essential pkg-config libssl-dev cargo build --release # 静态 musl 构建(releases 所使用的方式) nix develop .# --command cargo build --release --target x86_64-unknown-linux-musl ``` 交叉编译目标:`make release-musl`(静态编译),`make release-arm64`,`make release-windows`。运行 `make help` 查看所有目标。 ## 快速开始 ``` # 1. 运行 setup 向导 — 在远程主机上配置 Docker、CTFd、plugin、monitor nervctf setup # 2. 编写你的 challenges mkdir -p challenges/web/sqli challenges/pwn/overflow cat > challenges/web/sqli/challenge.yml <<'EOF' name: SQL Injection 101 category: web value: 100 type: standard description: Find the flag in the database. flags: - flag{sql_is_fun} EOF # 3. 本地验证(无需网络) nervctf validate # 4. 部署到 CTFd nervctf deploy # 5. 检查与线上 CTFd 实例的兼容性 nervctf probe ``` ## 配置说明 ### `.nervctf.yml` 由 `nervctf setup` 创建。会从工作目录开始向上搜索(一直查找到文件系统根目录)。你可以将其放在仓库根目录下,然后在任何子目录中运行 `nervctf`。 ``` # ── Monitor 连接 ───────────────────────────────────────────────────────── monitor_ip: 1.2.3.4 # IP of the CTFd/monitor host monitor_port: 33133 # default: 33133 # ── 认证 ──────────────────────────────────────────────────────────── monitor_token: # 64-char hex token; auto-generated by nervctf setup # This is the token for the remote-monitor, NOT CTFd. # ── 部署(仅由 nervctf setup / setup --upgrade 使用)───────────────── monitor_user: root # SSH user on the CTFd host (must have sudo/docker) monitor_ctfd_path: /home/root/CTFd # CTFd install path on the remote host # default: /home//CTFd ssh_key_path: ~/.ssh/id_rsa # private key for SSH access to monitor + runner hosts # ── 本地 challenges ────────────────────────────────────────────────── challenges_path: ./challenges # where nervctf looks for challenge.yml files # ── 调优(在 setup 时内置;更改需要运行 setup --upgrade)──────────── max_concurrent_provisions: 4 # max parallel container provisions max_instances_per_team: 3 # max active instances per team across all challenges # 0 = unlimited # ── 显示 ─────────────────────────────────────────────────────────── ctfd_domain: ctfd.example.com # CTFd URL shown in admin dashboard links # defaults to http:// # ── 分机模式(可选)──────────────────────────────────────────────────── # 设置 runner_ip 以在单独的节点上运行 containers。 # 留空则在与 CTFd 相同的机器上运行 containers。 runner_ip: 192.168.1.50 runner_user: docker runner_domain: challenges.example.com # hostname shown to players in connection strings # defaults to runner_ip ``` ### 配置优先级(优先级从高到低) ``` CLI flags > environment variables > .nervctf.yml ``` | CLI flag | 环境变量 | 描述 | |---|---|---| | `--monitor-url` | `MONITOR_URL` | 远程 monitor 的完整 URL (`http://host:port`) | | `--monitor-token` | `MONITOR_TOKEN` | Monitor 认证 token | `--monitor-url` 标志会同时覆盖配置文件中的 `monitor_ip` 和 `monitor_port`。 ## 命令 所有命令均接受位于子命令名称**之前**的全局标志: ``` nervctf [GLOBAL FLAGS] [SUBCOMMAND FLAGS] ``` ### 全局标志 | 标志 | 默认值 | 描述 | |---|---|---| | `-c, --challenges-dir ` | `.`(当前目录) | 搜索 `challenge.yml` 文件的根目录 | | `-v, --verbose` | false | 开启详细输出模式 | | `--monitor-url ` | 取自配置/环境变量 | 覆盖本次运行的 monitor URL | | `--monitor-token ` | 取自配置/环境变量 | 覆盖本次运行的 monitor token | ### `nervctf setup` 在远程主机上配置完整的服务器环境。每场比赛只需运行一次。 ``` nervctf setup nervctf setup --upgrade ``` **执行内容(首次运行):** 1. 以交互方式提示输入:主机 IP、SSH 用户名、CTFd 路径、monitor 端口、token、SSH 密钥 2. 将所有信息保存到 `.nervctf.yml` 3. 运行内嵌的 Ansible playbook,执行以下操作: - 安装 Docker + Docker Compose(如果尚未安装) - 克隆 CTFd 3.7.3 并启动 - 构建并启动 `remote-monitor` 容器 - 将 `nervctf_instance` 插件 Rsync 到 CTFd 的插件目录 - 将带有 monitor 环境变量的配置写入 `docker-compose.override.yml` - 重启 CTFd 以加载插件 - 轮询健康检查 endpoint 直到所有服务就绪 4. 打印管理仪表板的 URL 和 token **`--upgrade` 的作用:** - 将新的 `remote-monitor` 二进制文件和插件文件推送到服务器 - 重新构建 monitor Docker 镜像 - 重启 CTFd 和 monitor - 检查已安装的 CTFd 版本是否与已测试版本 (3.7.3) 一致 - **不会**重新配置 CTFd 或重新生成 token | 标志 | 描述 | |---|---| | `--upgrade` | 在现有部署上升级插件 + monitor 二进制文件 | ### `nervctf deploy` 在本地校验所有题目,将其与 CTFd 上的当前状态进行比对,并应用更改。 ``` nervctf deploy nervctf deploy --dry-run nervctf deploy --recreate nervctf deploy --recreate --prune ``` **Deploy 分为四个有序阶段执行:** | 阶段 | 执行内容 | |---|---| | 1 — 核心 | 创建新题目并更新已更改的题目。上传 flag、tag、topic、hint。检测更改的文件(标记为第 2 阶段处理)。 | | 2 — 文件 | 为新增/更改的题目上传附件文件。 | | 3 — 前置要求 | 将前置题目的名称解析为 CTFd ID 并修补 `requirements`。在第 1 阶段之后执行,以确保所有 ID 均已存在。 | | 4 — Next 指针 | 修补 `next_id` 链接。原因与第 3 阶段相同。 | 在第 1 阶段之前,CLI 会获取已存储的兼容性探针结果,并执行以下操作: - 如果动态题目计分处于损坏状态(CTFd 迁移不完整),则**中止部署** - 如果 CTFd 处于 user-mode,则发出**警告**(玩家实例认证将失败) - **记录**任何降级的功能(例如 Redis 缓存未失效) - 如果当前的 CTFd 版本与已测试版本 (3.7.3) 不同,则发出**警告** 如果某题目的以下任何内容与远端不同,则被视为已**更改**:`category`、`description`、`state`、`connection_info`、`attempts`、`extra` 字段、flag `(content, type, data)`、hint `(content, cost)`、tag、topic 或文件。 **类型变更**(例如 `standard` → `dynamic`)始终会触发删除 + 重建,因为 CTFd 的 PATCH endpoint 不会创建所需的关联表行。 | 标志 | 描述 | |---|---| | `-d, --dry-run` | 仅打印将要执行的更改,而不实际应用 | | `--recreate` | 强制重新部署所有题目(即使已是最新);将文件重新同步到 runner 并重新构建镜像 | | `--prune` | 删除本地不再存在的远端题目(在第 4 阶段后执行) | ### `nervctf validate` 在不连接服务器的情况下校验题目 YAML 文件。如果发现任何错误,则以退出代码 1 退出。 ``` nervctf validate nervctf validate --debug ``` 校验会检查每种题目类型的所有字段以及跨题目的规则(例如:名称重复、自引用的 `requirements`)。请参阅 [题目规范](#challenge-specification) 获取完整的规则表。 | 标志 | 描述 | |---|---| | `--debug` | 打印每个题目的完整解析字段字典 | ### `nervctf probe` 查询远程 monitor 当前的 CTFd 兼容性状态,并显示能力矩阵。 ``` nervctf probe nervctf probe --refresh nervctf probe --json ``` Monitor 在启动时会存储一次探针结果,并支持按需刷新。探针会查询 CTFd 的 MariaDB 以检测已安装的版本、团队模式还是用户模式、存在哪些可选 schema 列,以及是否安装了插件数据表。 **输出示例:** ``` NervCTF <-> CTFd Compatibility Report ══════════════════════════════════════ CTFd version : 3.7.3 (source: configs_table) CTFd mode : team Probed at : 2026-05-26 14:32:11 UTC Capability Status ───────────────────────── ────────── Challenge CRUD [ok] Dynamic scoring [ok] Player authentication [ok] Instance flag lifecycle [ok] Redis cache invalidation [DEGRADED] Schema fingerprint: challenges : attribution, category, connection_info, decay, description, function, id, initial, logic, max_attempts, minimum, name, next_id, position, requirements, state, type, value dynamic_challenge: id (stub-only — scoring is inline in challenges) Warnings: • Direct MariaDB writes bypass CTFd Redis cache — stale data may be served until CTFd restart or TTL expiry ``` **能力状态:** | 状态 | 含义 | |---|---| | `[ok]` | 完全支持,无任何限制 | | `[DEGRADED]` | 在已知限制下可用 — 会向运维人员发出警告 | | `[BROKEN]` | 无法工作;部署被阻止或严重受损 | **退出代码:** 如果所有能力均为 `ok` 或 `degraded` 则为 0;如果任何能力为 `broken` 则为 1。适用于 CI 门禁。 注意:`Redis cache invalidation` 始终为 `DEGRADED` — NervCTF 直接写入 MariaDB,无法清除 CTFd 的 Redis 缓存。在进行大批量部署后,请重启 CTFd 或调整 `CACHE_DEFAULT_TIMEOUT`。 | 标志 | 描述 | |---|---| | `--refresh` | 强制从 MariaDB 发起全新探测(忽略 monitor 上的缓存结果) | | `--json` | 输出原始 JSON 而非格式化的表格 | ### `nervctf list` 列出在 `--challenges-dir` 下找到的所有题目。 ``` nervctf list nervctf list --detailed ``` | 标志 | 描述 | |---|---| | `-d, --detailed` | 显示每个题目的完整字段值 | ### `nervctf scan` 扫描题目目录并打印统计信息(按类型、类别统计的数量,总分数等)。 ``` nervctf scan nervctf scan --detailed ``` | 标志 | 描述 | |---|---| | `-d, --detailed` | 显示每个题目的细分数据 | ### `nervctf fix` 以交互方式扫描题目 YAML 文件,并提供修补常见缺失字段(如 `state`、`version`、`description` 等)的选项。 ``` nervctf fix nervctf fix --dry-run ``` | 标志 | 描述 | |---|---| | `-d, --dry-run` | 仅显示将要修补的内容,而不修改文件 | ## 题目规范 题目存放在名为 `challenge.yml`(或 `challenge.yaml`)的文件中。NervCTF 会在 `--challenges-dir` 下递归搜索,最大深度为 5。目录结构是任意的 — 使用适合你比赛的任何布局即可。 ``` challenges/ ├── web/ │ └── sqli/ │ ├── challenge.yml │ └── dist/source.py ← referenced in files: └── pwn/ └── overflow/ └── challenge.yml ``` ### 标准题目 固定分数的题目。玩家提交一个 flag,结果要么匹配要么不匹配。 ``` # ── 身份 ────────────────────────────────────────────────────────── name: SQL Injection 101 # required; must be unique across all challenges category: web # required version: "0.3" # optional; local metadata only, not sent to CTFd author: alice # optional; local metadata only # ── 计分 ─────────────────────────────────────────────────────────── type: standard # standard | dynamic | instance value: 100 # required for standard; must be > 0 state: visible # visible (default) | hidden # ── 内容 ─────────────────────────────────────────────────────────── description: | Find the vulnerability and retrieve the flag. The server is at http://challenge.example.com connection_info: "http://challenge.example.com" # optional; shown on the challenge page attempts: 5 # max wrong guesses; 0 or omitted = unlimited # ── Flags(非 instance challenges 至少需要一个)──────────────── flags: - flag{simple_string} # shorthand: static type, case-sensitive - type: static content: "flag{alternate}" data: case_insensitive # optional modifier; default: case-sensitive - type: regex content: "flag\\{[a-z]+\\}" # regex pattern # ── 组织 ────────────────────────────────────────────────────────────── tags: [web, sql-injection, beginner] topics: [owasp-top-10, database] # freeform topic labels (separate from tags in CTFd) # ── 提示 ───────────────────────────────────────────────────────────── hints: - "Try single-quote injection" # free hint - content: "The login form is vulnerable" cost: 50 # costs 50 points to unlock # ── 文件 ───────────────────────────────────────────────────────────── files: - dist/source.py # path relative to challenge.yml - dist/Dockerfile # ── 前置条件 ───────────────────────────────────────────────────────────── requirements: - "Warmup" # other challenge name; must exist locally or on CTFd - "Web Intro" # ── 导航 ──────────────────────────────────────────────────────────── next: "Advanced SQLi" # name of the challenge to show as "next" in CTFd UI ``` ### 动态计分题目 随着更多队伍解出题目,其分数会逐渐降低。 ``` name: Crypto Hard category: crypto type: dynamic value: 0 # ignored for dynamic; set initial/minimum below extra: initial: 500 # starting point value (required, must be > 0) decay: 50 # number of solves at which value reaches minimum (required, must be > 0) minimum: 100 # floor value (optional; defaults to 0) decay_function: linear # linear (default) | logarithmic flags: - flag{crypto_hard} ``` ### 实例题目 为每个队伍分配一个临时容器。玩家在 CTFd 中点击“Request Instance”,即可获得一个主机/端口,并直接与他们相互隔离的环境进行交互。 ``` name: Pwn Me category: pwn type: instance value: 0 extra: # optional dynamic scoring on top of instance initial: 500 decay: 50 minimum: 100 decay_function: linear description: | Connect to your instance and get root. instance: # ── Backend ───────────────────────────────────────────────────────────────── backend: docker # docker | compose | lxc | vagrant # ── Docker backend ─────────────────────────────────────────────────────────── image: . # "." = build from Dockerfile in challenge dir # or a registry image: "ubuntu:22.04" # ── Compose backend (mutually exclusive with docker image) ─────────────────── # compose_file: docker-compose.yml # compose_service: app # which service exposes the port # ── LXC backend ────────────────────────────────────────────────────────────── # lxc_image: ubuntu:22.04 # ── Ports ──────────────────────────────────────────────────────────────────── # Single port (most common): internal_ports: [1337] # Multi-port: each gets a separately allocated random host port. # The first port is the "primary" shown in the connection string. # internal_ports: [80, 443] # Compose multi-service (mutually exclusive with internal_ports): # service_ports: # app: [80] # admin: [8080] connection: nc # nc | http | ssh # Controls the connection string shown to players. # ── Lifecycle ──────────────────────────────────────────────────────────────── timeout_minutes: 45 # instance auto-expires after this many minutes max_renewals: 3 # how many times players can extend the timer # ── Flag ───────────────────────────────────────────────────────────────────── flag_mode: random # random | static # random: a unique flag is generated per instance and # registered in CTFd's flags table at provision time. # static: uses the flags: list above; one flag shared by all teams. flag_prefix: "CTF{" # optional; wraps the random part flag_suffix: "}" random_flag_length: 16 # characters in the random part (default: 16) # How the flag is delivered into the container: flag_delivery: env # env (default) | file # env: injected as $FLAG environment variable # file: written to a bind-mounted file at flag_file_path # flag_file_path: /challenge/flag # required when flag_delivery: file # flag_service: app # compose only: which service gets the file mount # ── Optional ───────────────────────────────────────────────────────────────── command: null # override the container's CMD/entrypoint ``` #### 静态 flag 实例题目 使用 `flag_mode: static`(或省略该项)并在顶层的 `flags:` 列表中定义 flag。所有队伍共享同一个 flag。 ``` type: instance instance: backend: docker image: . internal_ports: [1337] connection: nc flag_mode: static flags: - flag{shared_static_flag} ``` #### 实例题目的校验规则 | 检查项 | 严重程度 | |---|---| | 必须包含 `instance` 块 | Error | | `instance.internal_ports` 必须至少包含一个条目(除非设置了 `service_ports`) | Error | | 端口值必须为 1–65535 | Error | | `service_ports` 和 `internal_ports` 互斥 | Warning | | `instance.connection` 不能为空 | Error | | `instance.timeout_minutes == 0` | Warning | | Docker 后端:必须包含 `image` | Error | | Docker 后端:本地路径缺少 Dockerfile | Warning | | Compose 后端:磁盘上找不到 `compose_file` | Warning | | LXC 后端:必须包含 `lxc_image` | Error | | `flag_mode: random` 缺少 `flag_prefix`/`flag_suffix` | — (使用默认值) | | `flag_delivery: file` 缺少 `flag_file_path` | Error | | `flag_file_path` 不是绝对路径 | Warning | | `flag_mode: static` 未定义任何 flag | Error | ## 校验规则(所有类型) | 字段 | 检查项 | 严重程度 | |---|---|---| | `name` | 为空或全是空格 | Error | | `name` | 在不同题目文件中重复 | Error | | `category` | 为空或全是空格 | Error | | `value` | `standard` 类型下为 `== 0` | Error | | `extra` | `dynamic` 类型下缺失 | Error | | `extra.initial` | `dynamic` 类型下缺失或为 `== 0` | Error | | `extra.decay` | `dynamic` 类型缺失或为 `== 0` | Error | | `extra.minimum` | `dynamic` 类型下未设置 | Warning | | `extra.decay_function` | 设置时非 `"linear"` 或 `"logarithmic"` | Error | | `flags` | 必须至少包含一个 flag (非实例题目且非随机 flag) | Error | | `flags[].content` | 为空或全是空格 | Error | | `flags` | 重复的 content 值 | Warning | | `hints[].content` | 为空或全是空格 | Error | | `files` | 引用的文件在磁盘上未找到 | Error | | `requirements` | 题目列表包含自身 | Error | | `requirements` | 在本地未找到指定的题目名称 | Warning | | `next` | 指向自身 | Error | | `next` | 在本地未找到指定的题目名称 | Warning | | `attempts` | 设置为 `0`(等同于无限次 — 很可能是笔误) | Warning | | 未知的 YAML 键 | 不在规范中 | Warning | ## 远程 Monitor Monitor 作为 Docker 容器在 CTFd 宿主机上运行,由 `nervctf setup` 写入的 `docker-compose.override.yml` 进行管理。 ### 管理仪表板 ``` http://:/admin?token= ``` 或者也可以在 `http://:/` 登录一次,会话 cookie 即会被设置。 仪表板会显示: - 每支队伍的所有活动容器实例 - Flag 提交尝试(包括 flag 共享警报) - 正确的解答日志 - 运行时配置(公共主机、runner 模式、基础目录等) - CTFd 兼容性探针结果 ### 环境变量 这些变量由 `nervctf setup` 在 `docker-compose.override.yml` 中设置,通常不需要手动编辑。 | 变量 | 必填 | 默认值 | 描述 | |---|---|---|---| | `CTFD_DB_URL` | **是** | — | MariaDB URL: `mysql://user:pass@host/db` | | `MONITOR_TOKEN` | **是** | — | 管理 token(启动时计算 SHA-256 哈希;绝不存储明文) | | `PUBLIC_HOST` | **是** | — | 在连接字符串中返回给玩家的主机名或 IP | | `MONITOR_PORT` | 否 | `33133` | 监听的 TCP 端口 | | `MONITOR_BIND` | 否 | `0.0.0.0` | 绑定的地址 | | `DB_PATH` | 否 | `./monitor.db` | SQLite 数据库路径 | | `CHALLENGES_BASE_DIR` | 否 | `/opt/nervctf/challenges` | 服务器上解压题目文件的存放位置 | | `CTFD_UPLOADS_DIR` | 否 | `""` | CTFd 上传目录的绝对路径(用于写入文件附件) | | `RUNNER_SSH_TARGET` | 否 | `""` | 分离式机器模式下的 SSH 目标,例如 `docker@192.168.1.50` | | `MAX_CONCURRENT_PROVISIONS` | 否 | `4` | 并发容器分配数 (信号量) | | `MAX_INSTANCES_PER_TEAM` | 否 | `0` | 每支队伍在所有题目中允许的最大活动实例数 (`0` = 无限制) | | `CTFD_DB_SYNC_INTERVAL` | 否 | `30` | CTFd 解答/用户同步周期的间隔秒数 | | `CTFD_DOMAIN` | 否 | `http://` | 管理仪表板链接中显示的 CTFd URL | ### 后台任务 Monitor 运行两个独立于 HTTP 流量的后台循环: **同步循环**(每隔 `CTFD_DB_SYNC_INTERVAL` 秒): - 读取 CTFd `submissions` 并将正确的解答缓存到 SQLite 中 - 读取 CTFd `teams` 和 `users` 用于名称显示 - 如果 CTFd 管理员删除了解答记录,则将实例恢复为运行状态 - 清除过期的 flag 尝试记录 **过期 + 健康检查循环**(每 30 秒): - 销毁已过 `expires_at` 时间的实例 - 清理超过 30 分钟卡住的分配存根 - 检测并销毁未在 SQLite 中记录的孤立 Docker Compose 项目 - 对已跟踪的实例进行健康检查,并清理被外部强行终止的容器 ### Flag 共享检测 当玩家提交 flag 时,CTFd 插件会调用 monitor 的 `/api/v1/plugin/attempt` endpoint。Monitor 会检查提交的 flag 是否最初分配给了**另一支队伍**。如果是,它会记录一条 flag 共享警报,该警报可在管理仪表板的 "Attempts (alerts only)" 下查看。 Flag 的归属权永久存储在 `team_flags` SQLite 表中 — 即使实例过期后,monitor 也会记住哪支队伍拥有哪个 flag。 ## 分离式机器模式 在 `.nervctf.yml` 中设置 `runner_ip` 和 `runner_user`,即可在单独的节点上运行容器。 **工作原理:** 1. `nervctf deploy` 会使用 `ssh_key_path`,将每个题目的构建上下文从你的计算机直接 rsync 到 `runner_user@runner_ip:~/challenges//` 2. Monitor 通过 SSH 连接到 runner 以构建 Docker 镜像并启动容器 3. Monitor 在设置阶段会生成一对专用的 SSH 密钥对 (`monitor_ssh_key`),并 bind-mount 到 monitor 容器中 **对 runner 节点的要求:** - 已安装 Docker + Docker Compose - 允许 monitor 进行 SSH 访问(公钥需添加到 `~/.ssh/authorized_keys`) - 无需 CTFd — 它被隔离在 CTFd 宿主机上 ## 故障排除 | 症状 | 原因 | 解决方法 | |---|---|---| | 未找到题目 | 文件名不是 `challenge.yml` 或层级超过了 5 个目录 | 检查路径;最大深度为 5 | | `state: Field may not be null` | CTFd 需要 `state` 字段 | 运行 `nervctf fix` | | 文件上传出现 500 错误 | 上传目录的所有权不正确 | `chown -R 1001:1001 /.data/CTFd/uploads` | | Monitor 返回 401 | CLI 和服务器之间的 token 不匹配 | 检查 `.nervctf.yml` 中的 `monitor_token` 是否与服务器上的 `MONITOR_TOKEN` 一致 | | `ansible-playbook: not found` | 工具不在 PATH 中 | 在 `nix develop .#` 环境中运行或安装 ansible | | 玩家请求实例时遇到 403 | CTFd 处于 user-mode | 将 CTFd 切换为 team mode;可通过 `nervctf probe` 确认 | | 动态题目在 CTFd 管理界面报 500 错误 | CTFd schema 迁移不完整 | 运行 `nervctf probe --refresh` 进行诊断 | | rsync 权限拒绝 | 未将 SSH 密钥传递给 rsync | 在 `.nervctf.yml` 中设置 `ssh_key_path` | | 实例向玩家显示了错误的主机 | `runner_domain` 或 `PUBLIC_HOST` 错误 | 在 `.nervctf.yml` 中设置 `runner_domain` 并重新运行 `nervctf setup --upgrade` | | 提交 flag 显示 "Incorrect" 但 flag 匹配 | CTFd `attempt()` 返回类型不匹配 | 升级 NervCTF;已在 v2.4.0 版本中修复 | ## 开发说明 ``` # 所有开发命令使用 Nix devshell nix develop .# --command cargo check nix develop .# --command cargo test nix develop .# --command cargo fmt # 构建静态 release binaries nix develop .# --command cargo build --release --target x86_64-unknown-linux-musl # 构建后复制到 dist/(CLAUDE.md 所需) cp target/x86_64-unknown-linux-musl/release/remote-monitor dist/remote-monitor-linux-x86_64-static cp target/x86_64-unknown-linux-musl/release/nervctf dist/nervctf-linux-x86_64-static ``` 有关各个模块的文档,请参阅 [`docs/`](docs/): | 文档 | 内容 | |---|---| | [`docs/instance-challenges.md`](docs/instance-challenges.md) | 完整的实例题目 YAML 参考、后端详细信息、多端口设置 | | [`docs/remote-monitor.md`](docs/remote-monitor.md) | 所有 API 路由、环境变量、schema 检测、兼容性探针 | | [`docs/challenge_manager.md`](docs/challenge_manager.md) | 同步逻辑、needs_update 字段、子资源策略 | | [`docs/validator.md`](docs/validator.md) | 所有包含严重级别的校验规则 | | [`docs/ctfd_api.md`](docs/ctfd_api.md) | HTTP API 概览、版本兼容性表 | | [`docs/ctfd-dependency-audit.md`](docs/ctfd-dependency-audit.md) | 完整的 CTFd 依赖关系图、风险登记表 | | [`docs/dev-notes.md`](docs/dev-notes.md) | 构建环境、交叉编译、架构说明 | 完整的系统参考说明请查阅 [`ARCHITECTURE.md`](ARCHITECTURE.md)。 ## 开源协议 MIT License。详情请参阅 [LICENSE](LICENSE)。
标签:CTFd, Docker, 内存分配, 可视化界面, 安全防御评估, 版权保护, 特权提升, 系统提示词, 自动化部署, 请求拦截, 运维工具, 通知系统