文档状态:有效
适用范围:前端生成/构建、内嵌打包、开发进程、版本脚本与 Release workflow 行为
最后核对:2026-09-30(新增容器镜像交付:独立 container workflow、Dockerfile 与部署示例;同批次拆分镜像流水线 job 并修正镜像启动探针)
核对基线:
bfac363;本轮核对镜像 workflow、Dockerfile 与本地可执行的配置/文档检查,未执行 Actions、镜像构建或容器内运行,历史运行结果按原日期和基线解释
版本由 mise.toml 与各 manifest 声明;工具来源、调用、安装审批和缓存位置唯一维护于环境规则。本文说明脚本真实行为,不授权安装、启动服务、提交 tag、push 或发布。
以下命令是操作入口示例,不是本轮执行记录。Rust/仓库脚本从根目录运行,pnpm 从 frontend/ 运行;本地配置和运行文件遵循本地测试规则。
frontend/package.json 定义 dev、generate:api、typecheck、test 和 build。generate:api 执行 generate:api:v2:唯一 v2 OpenAPI 生成 generated-v2.ts。认证与业务共用该契约;生成文件不人工编辑,v1 schema/类型和旧页面 fixture 已删除。
test:contract:v2 用 Node 自带 test runner 运行 OpenAPI 3.1 schema 校验,与 Rust 消费同一个 JSON 夹具。它独立于 Vitest UI suite;test 不会自动包含该检查,契约变化必须额外执行。该检查不启动服务,不验证真实 HTTP/WS 或完整配置引用语义。
P0 依赖核定(2026-09-07):@redocly/ajv 8.11.2、js-yaml 4.3.1 原已在锁文件作为间接依赖,本次按 D-08 显式加入 devDependencies,复用现有版本进行 JSON Schema/YAML 校验,不依赖隐式 hoist,不引入生产包。后端仅为既有 ipnet 2.12.1、url 2.5.8 开启 serde feature,避免复制 IP/URL 序列化逻辑,无 crate 版本升级。许可证及版本已按本地 package manifest 核对;前端两项为 MIT,后端两项为 MIT OR Apache-2.0。新增前端测试依赖不进入生产 bundle;Rust serde feature 可能增加编译产物,未测量其单独字节增量。未升级或安装工具链。
P1 文件事务依赖核定(2026-09-07):既有 windows-sys 0.61.2(MIT OR Apache-2.0)仅增加 Win32_Security feature,用于创建文件时保留 owner/group/DACL 和禁止默认继承扩大访问;没有新增 crate 或升级锁定版本。OS 锁复用 Rust 标准库 File::try_lock,不添加锁库;未测量安全 API feature 的独立产物字节增量。Windows junction 回归调用项目既有 PowerShell 7,不安装测试工具。
P1 壳层依赖核定(2026-09-08):新增锁定的 lucide-react 1.41.0 生产依赖,复用图稿采用的 Lucide 图标体系,供 12 个导航入口及折叠、移动菜单、登出控件使用,避免维护手绘 SVG。许可证为 ISC;registry 报告的完整包 unpacked size 为 32,023,893 bytes,实际只静态导入 16 个图标并由 Vite tree-shake,本轮不把完整包大小当成生产 bundle 增量,也未单独测量依赖增量。未增加构建脚本或工具链。
P1 指标采样依赖核定(2026-09-08):未新增 crate 或升级锁定版本。Windows 复用既有 windows-sys 0.61.2(MIT OR Apache-2.0),增加 Win32_Foundation、Win32_System_Diagnostics_ToolHelp、Win32_System_ProcessStatus 和 Win32_System_Threading feature;Linux 使用 Rust 标准库读取 procfs。新增 feature 只参与对应目标编译,未测量其独立构建物字节增量;Linux 条件编译代码本批未实测。
P4 WS 依赖核定(2026-09-09):为锁定的 axum 0.8.9 启用 ws feature,并将 futures-util 0.3.34 作为直接生产依赖,用于拆分有界 WS sink/stream;tokio-tungstenite 0.29.0 仅作为 dev-dependency 驱动真实 socket 测试。axum 与 tokio-tungstenite 为 MIT,futures-util 为 MIT OR Apache-2.0。未升级 Rust/Node/pnpm,也未新增前端图表库;服务状态图表使用现有 React/SVG。单独 feature/依赖体积增量未测量。
vite.config.ts 在开发时把 /api 代理到 http://127.0.0.1:8080;浏览器仍请求同源相对路径。VITE_USE_MOCK_API=true 只在 DEV bootstrap 启用 MSW,生产构建不携带 mock worker 或 source map。完整生成与验证命令见前端 README。
从仓库根目录、准备好项目工具链后执行:
pwsh -File script/package-embedded.ps1package-embedded.ps1 先检查 PowerShell、当前 OS/架构和必需命令,只支持 Windows/Linux x86_64;不支持本地 macOS/跨架构调用。之后按三阶段执行:
pnpm install --frozen-lockfile与pnpm run build,保留frontend/dist/。- 默认 feature 的 Cargo locked release,保留
backend/target/release/。 - 检查
webui-embed与当前平台 target,构建 target-specific release,将最终 binary 复制到deploy/。
| 平台 | 内嵌 target 构建物 | 本地发布物 |
|---|---|---|
| Windows x86_64 | backend/target/x86_64-pc-windows-msvc/release/fluxdns.exe |
deploy/fluxdns-windows-x86_64.exe |
| Linux x86_64 | backend/target/x86_64-unknown-linux-gnu/release/fluxdns |
deploy/fluxdns-linux-x86_64 |
脚本不移动或重定向独立构建物,不自动安装 target/linker。最终 feature/target 检查失败时前两阶段产物仍保留;不能因此把已有旧发布物当成本次成功产物。脚本先检查 dist/index.html,management/assets.rs 的 RustEmbed 派生在 feature 启用时内嵌资源并在启动时检查 index;默认非 embed 构建仍可提供 Management API,但不含 SPA。
后端本机契约验证使用 test-backend-contracts.ps1,不通过 dev.ps1 启停个人实例。该入口的 Local/Connections 模式、watchdog、证据目录及实际运行边界唯一维护于后台服务;不是构建/发布或目标环境性能验收入口。
独立跨平台负载 binary contract-load 通过 Cargo example 显式构建,不加入 FluxDNS 默认发布产物。构建、配置、停止语义和结果限制见跨平台负载驱动;它不修改既有目标平台矩阵,也不自动安装交叉编译工具链。
dev.ps1 的 start 必须显式提供 -ConfigPath,可用 -BinaryPath 指定 binary;不回退到 _fluxdns/config.yaml 或 CLI 默认配置。以下示例要求本地配置和当前平台发布物已经存在:
pwsh -File script/dev.ps1 start -ConfigPath ./_fluxdns/config.yaml
pwsh -File script/dev.ps1 status
pwsh -File script/dev.ps1 stop状态保存在 _fluxdns/dev-process.json,stdout/stderr 在 _fluxdns/logs/dev.stdout.log 与 dev.stderr.log。status 返回 0 表示运行、3 表示未运行;参数错误、损坏状态或无法安全核验进程为非零错误。
脚本根据 PID、启动时间和 executable 身份检查进程,stop 只停止匹配进程,不仅凭陈旧 PID 操作其他进程。不要手工把共享或生产进程写入状态文件。
VERSION 是发布版本权威,保存不带 v 的单行 SemVer。set-version.ps1 在 main 接受带或不带 v 的版本参数,同步 VERSION、Cargo manifest/lock 与前端 package,然后以 chore(release): 发布 v<version> 提交这四个文件并创建本地 tag。该脚本有 Git 写副作用,只有明确准备发布版本时才运行。
已有 tag、版本未变或默认工作树不干净时停止。-IgnoreUncommittedChanges 只绕过工作树保护,不扩大四文件提交范围,但这四文件中已有修改会一并进入版本提交。脚本不会 push。另行获准推送时应先推 main,再推该 tag,确保远端 main 已包含版本提交。
.github/workflows/release.yml 由 v* tag 触发,流程按依赖拆成 prepare、frontend、rust-quality、create-release、build 和 finalize-release:
prepare只校验 tag 提交属于main、工具版本和 tag/VERSION/Cargo/前端 package 版本,不再为元数据校验提前安装 Rust 或 Node。frontend依赖prepare,完成 pnpm 安装、测试和构建后上传webui-dist。Vitest 的单个用例预算由vite.config.ts的testTimeout统一设为 15000ms:全应用渲染加 MSW 的用例在 2 vCPU runner 上比本地慢一倍以上,沿用 5000ms 默认值会在 CI 偶发超时。rust-quality依赖prepare和frontend,在 Clippy/测试前把同一 artifact 下载到frontend/dist/:--all-features会启用webui-embed,RustEmbed 在编译时就需要真实 WebUI,不能与该产物的生成完全并行。pnpm 依赖仍只安装一次,WebUI 仍只构建一次,Rust 质量门禁和所有平台共用同一产物。- Rust job 使用按 runner OS 和
Cargo.lock哈希命名的actions/cache复用 Cargo registry/git 源;平台 target 的target/仍按 target 独立缓存,因为不同 OS/target 的编译产物不可安全混用。Linux 的 GNU 与 musl job 可以复用同一份依赖源缓存,Windows/macOS 仍使用各自 runner 的缓存空间。 create-release在两类质量门禁都成功后创建 draft Release。四项buildmatrix 只依赖共享门禁和这个草稿,max-parallel: 4、fail-fast: false允许 Windows x86_64、Linux x86_64、OpenWrt x86_64、macOS ARM64 同时执行;实际调度仍受 GitHub runner 可用性与账户并发配额限制。- 每个平台完成打包和
--version校验后立即通过gh release upload上传自己的 archive,使用--clobber支持失败重跑。平台上传不再等待其他二进制完成,因此 Release 草稿可以逐步看到已完成的资产;OpenWrt 仍在 Ubuntu runner 上使用 musl,其他三项保持各自现有 target。 finalize-release只在四项 matrix 全部成功后下载四个 archive,生成并上传checksums.txt,再把 draft Release 发布。也就是说,上传资产不必等待四个平台全部完成,但正式发布仍保留“四个平台完整且校验和齐全”的门禁。
| 平台 | Rust target | Release 归档 |
|---|---|---|
| Windows x86_64 | x86_64-pc-windows-msvc |
fluxdns_<version>_windows_x86_64.zip |
| Linux x86_64(glibc) | x86_64-unknown-linux-gnu |
fluxdns_<version>_linux_x86_64.tar.gz |
| OpenWrt x86_64(musl 静态链接) | x86_64-unknown-linux-musl |
fluxdns_<version>_openwrt_x86_64.tar.gz |
| macOS ARM64 | aarch64-apple-darwin |
fluxdns_<version>_macos_arm64.tar.gz |
OpenWrt 构建项在临时 Ubuntu runner 安装 musl-tools 与 binutils,为 cc 指定 CC_x86_64_unknown_linux_musl=musl-gcc,并为 Cargo 指定对应 target 的 linker;现有 SQLite bundled 与 ring C 代码随该工具链构建,不增加 Rust 依赖。该项显式启用 -C target-feature=+crt-static,打包前通过 readelf 拒绝 ELF INTERP 和动态 NEEDED 依赖,再复用公共的 --version 检查与打包流程。OpenWrt 安装端应选择 _openwrt_x86_64.tar.gz,不能回退使用 GNU/Linux 包;归档中的程序仍命名为 fluxdns。
上述工具安装属于该 CI 构建项;本地工具安装仍遵循环境规则,本地 package-embedded.ps1 仍只支持原有 Windows/Linux x86_64。workflow 配置、静态链接检查与版本检查不等同 OpenWrt 上的真实 DNS/DoH 验收;新增平台尚待 GitHub Actions 构建和目标设备运行验证。
镜像由独立的 .github/workflows/container.yml 构建并推送到 GitHub Container Registry(GHCR)。它是新增文件,与 release.yml 互不依赖:不由发布流程触发,不等待其成功,也不使用其归档或 checksums.txt。
workflow 由 v* tag 推送或 workflow_dispatch(输入已存在的 tag)触发,并按失败可定位性拆成 5 个 job;每个 job 只在上一环节通过后运行:
| job | 职责 | 产出 |
|---|---|---|
prepare |
解析 tag(push 用 GITHUB_REF_NAME,手动触发用 inputs.tag);校验合法 SemVer、等于 VERSION、等于 Cargo manifest 与前端 package 版本、指向属于 main 的提交,然后检出该提交,保证版本文件与 tag 一致;从 mise.toml 读取 Rust/Node/pnpm 版本 |
job 输出 tag、release_version 与三个工具版本 |
frontend |
setup-pnpm/setup-node、pnpm install --frozen-lockfile,pnpm run test 与 pnpm run build 分成两步 |
webui-dist artifact(webui-embed 编译期需要) |
rust-quality |
cargo fmt --check、cargo clippy --all-targets --all-features -- -D warnings、cargo test --all-features;--all-features 会启用 webui-embed,因此先下载 webui-dist |
门禁结论 |
binary |
cargo build --manifest-path backend/Cargo.toml --locked --release --features webui-embed;校验 --version 等于 fluxdns <version> 后暂存 deploy/fluxdns |
fluxdns-linux-x86_64 artifact |
image |
复核下载的二进制、docker/build-push-action 构建并推送 linux/amd64 |
GHCR 镜像 |
镜像标签为 <version>、<major>.<minor>、sha-<短哈希>,非预发布 tag 额外推送 latest;标签由 workflow 显式计算,不依赖 metadata-action 对 GITHUB_REF 的解释。
镜像级验证在 image job 内各自成步:docker run --rm <image> --version;挂载夹具配置执行 validate 并断言未生成第二份 config.yaml;以 --cap-drop ALL --cap-add NET_BIND_SERVICE --security-opt no-new-privileges:true 启动并验证 DNS 服务;docker compose config 校验部署示例。
启动冒烟的判据:用宿主到映射端口的 TCP 连接(45s 窗口)判定就绪,用 docker inspect --format '{{.State.Status}}' 判定存活;<work.path>/logs/fluxdns.log 出现 service_ready 只作为第二项证据(30s 窗口),因为生产日志经 TelemetryWriter 队列按 5s 周期刷盘,日志文件不代表监听已就绪(v0.3.6 的真实运行即因该误判失败)。停机判据是 docker stop 后退出码为 0 且耗时不超过 15s(服务自身宽限期为 5s)。冒烟夹具的 listener 绑定 0.0.0.0:53,容器内只听回环时宿主映射端口无法到达。
| 镜像契约 | 行为 |
|---|---|
| 基础镜像 | debian:trixie-slim 加 ca-certificates;不含编译工具链、init 进程或 shell 诊断工具 |
| 内容 | 只有 /usr/local/bin/fluxdns 与运行期依赖;迁移 SQL 与 SPA 已在编译期内嵌,镜像不携带 backend/migrations 或 frontend/dist |
| 用户 | 默认 root(uid 0),以便绑定配置中的 53/443;能力与 no-new-privileges 由部署层收紧 |
| 工作目录 | /etc/fluxdns,入口为 fluxdns run --config /etc/fluxdns/config.yaml;配置里的 work.path 必须同为 /etc/fluxdns,否则启动会生成第二份配置快照 |
| 卷 | 整目录挂载宿主机目录到 /etc/fluxdns;不要只挂 config.yaml,因为 Management 的配置提交依赖同目录原子替换 |
| 许可标签 | 不声明 org.opencontainers.image.licenses,因为仓库没有 LICENSE |
部署示例是 docker/compose-example.yaml 与 docker/.env.example:bridge 网络与端口映射(53 UDP/TCP、443、仅回环的 8080)、整目录卷、env_file 注入 SecretRef、cap_drop: ALL 加 NET_BIND_SERVICE、no-new-privileges、stop_grace_period: 20s,并以二进制自身的 validate 作为配置级健康检查。docker/.env 已在 .gitignore 中忽略;首次启动前宿主工作目录必须存在且包含 config.yaml。镜像构建使用的冒烟夹具是 docker/container-smoke-config.yaml,只用于验证,不代表生产配置。
已知边界:
cap_drop: ALL会让 root 失去CAP_DAC_OVERRIDE,因此宿主工作目录必须归 root 所有;CI 冒烟步骤同样先chown 0:0再启动容器。- bridge 模式下 DNS 客户端的
peer地址是 docker 网关地址,clients[].match.ips无法区分真实局域网客户端,ECSclient模式的前缀推导同样失真;需要真实客户端地址时可改用network_mode: host,或对 DoH 使用forwarded_header/proxy_protocol并配置trusted_proxies。 - 该流水线自带门禁:前端
pnpm run test、cargo clippy --all-features与cargo test --all-features与release.yml各跑一遍,重复的 CI 时间属已接受的代价;镜像本身仍只做编译产物校验与启动冒烟。 - 镜像内二进制与 Release 归档各自编译,字节不保证一致;两者都由同一 tag 源码与
--version校验约束。 - 健康检查只证明配置可解析、路径合法,不代表 listener 正在服务,也不校验 SecretRef。
| 能力 | 代码实现 | 正式入口接线 | 验证证据 | 已知限制 |
|---|---|---|---|---|
| 前端构建 | package scripts | 唯一 v2 类型生成与 Vite production build | 本轮 Windows:24 文件 98 项 Vitest、typecheck/build 通过 | 不以 mock 代替真实后端;本轮未重跑独立 schema suite |
| 本地打包 | package-embedded 三阶段 | 仓库根脚本、Windows target 与 deploy | 完整三阶段成功,deploy 与 target SHA-256 相同 | 未执行 Actions/Linux/macOS 发布 |
| Release 多平台发布 | release.yml 的 WebUI artifact 依赖、草稿 Release 与四项 matrix | Rust 质量门禁和平台构建复用一次生成的 WebUI;平台完成即上传,四项齐全后生成 checksums 并发布 | 本轮 YAML/DAG 检查、Windows 全 feature Clippy 和 Cargo 测试通过(795 项通过、4 项忽略) | 未执行真实 runner 调度、musl 编译、GitHub Release API 或 OpenWrt 设备运行 |
| 显式启动/身份检查 | dev start/status/stop | 最终 release embed 与独立 ConfigV2 | 新目录启动、受控重启、文件摘要不变、FDCS 恢复及旧分片 ID 可读 | 原生触控和外部 HTTPS 代理限制见联合验收 |
| 本地 HTTP/WS 验收 | test-webui-http.mjs、test-webui-events.ps1 | loopback 夹具与管理账号 | 四种 DNS 请求、配置/文件/安全、WS replay 与撤销 | 仅使用独立测试配置,不访问生产或公网 |
| 版本与远端发布 | set-version、release.yml | main + tag gates | 本轮不执行 | 没有 tag、push、Actions 或 Release 授权 |
| 容器镜像交付 | container.yml(5 个 job)、docker/Dockerfile、compose 与 env 示例 | tag 推送或手动触发的独立 workflow,推送 GHCR | 本轮本地:debug binary validate 通过新增冒烟夹具(listener 绑定 0.0.0.0:53)、根模板与既有启动夹具;实跑服务后 service_ready 在 2s 内写入 <work.path>/logs/fluxdns.log;workflow 通过 YAML 解析、job/步骤结构检查与 17 个 bash -n 语法校验;文档检查通过 |
v0.3.6 的真实运行在镜像启动冒烟失败(日志探针误判,已改为 TCP 探针);修正后的 Actions 运行、镜像构建/推送与容器内运行仍未验证;镜像体积、容器内性能与 latest 可见性未验证 |
当前命令、运行产物、29 图映射、E2E 矩阵与平台边界以 WebUI 联合验收 为准。本地脚本的夹具和凭据准备遵循本地测试规范;测试报告和密码留在忽略目录,不复制进 Git。历史批次日志由 Git 与原任务记录追溯,不把旧 debug 样本当作当前 release 的证明。