Wazuh 外部依赖全平台重建流水线解析:从 `externals-all.tar.gz` 到 `make deps` 的发布闭环
2026/9/13 18:25:25 网站建设 项目流程

Wazuh 外部依赖全平台重建流水线解析:从externals-all.tar.gzmake deps的发布闭环

【免费下载链接】wazuhWazuh - The Open Source Security Platform. Unified XDR and SIEM protection for endpoints and cloud workloads.项目地址: https://gitcode.com/GitHub_Trending/wa/wazuh

导读

Wazuh 将 curl、openssl、rocksdb 等数十个上游第三方库以“vendor”方式随源码树分发,并针对 Linux/macOS/Windows 五大构建腿(leg)预先编译成二进制包,统一打包为externals-all.tar.gz发布到packages.wazuh.com/deps/<DEPS_VERSION>/,由make deps在构建时按需下载。本文以仓库文档 build-external-dependencies.md 为主体,结合 5_builderpackage_externals.yml、packages/externals/ 下的四个脚本以及 src/Makefile、src/external/CMakeLists.txt 的源码级实现,完整讲解这条“全平台重建 → 合并去重 → 冒烟验证 → 发布新 DEPS_VERSION”的流水线:读完你将掌握如何触发依赖重建、如何覆盖单个上游库版本、如何安全发布新版本,以及为什么“预编译包路径不能改”。

一、这条流水线解决什么问题

Wazuh 的构建系统(make deps)不从各上游项目拉取最新源码即时编译,而是依赖一份预先构建好的依赖树:每个 (系统, 架构) 组合对应一组预编译.a/.so归档,外加上游源码快照。这样做的核心收益是构建可复现且平台基线一致——二进制统一在固定的构建器镜像里产出(如 CentOS 6 时代的 glibc 2.12),从而保证产物能跑遍所有受支持的发行版。

本文档描述的 GitHub Actions 工作流负责跨全部发布平台重建这些 vendored 依赖,并产出合并后的externals-all.tar.gz,最终发布到packages.wazuh.com/deps/<DEPS_VERSION>/供后续版本消费。

何时需要使用它

按文档说明,主要两类场景:

  • 升级一个或多个上游库版本(例如 CVE 补丁、引入新特性);
  • 从当前 vendor 的源码出发整体刷新依赖包(例如工具链变更影响了所有库的编译方式)。

一次成功运行的产品就是你要上传到packages.wazuh.com/deps/<新-DEPS_VERSION>/的产物;随后需要另一个独立的 PR在 src/Makefile 中把DEPS_VERSION提升到新目录。

支撑脚本一览

文档给出了packages/externals/下四个脚本的职责分工,对应仓库实际文件:

文件用途
external_sources.sh依赖清单(manifest):为每个依赖登记上游 URL 模板、归档格式、解包目标目录
build_external.sh容器内构建脚本:先make deps EXTERNAL_SRC_ONLY=yes填充src/external/,再应用--dependencies覆盖,执行按腿构建,并“转售”两个不在此编译的预构建 blob(见第五节 Caveats)
generate_external.sh宿主侧包装器:选择 Docker 镜像、挂载工作树、运行build_external.sh,并按make deps期望的 S3 布局打包为externals-<leg>.tar.gz
smoke_build.sh冒烟检查:用刚合并出的依赖树从源码构建 agent/manager,确认预编译包确实可被消费

二、运行工作流:UI 方式与 CLI 方式

文档给出两种触发方式。UI 方式:在 Actions 页面选择5.X - Package - Build external dependencies,点击Run workflow,选择分支。

CLI 方式(使用 GitHub CLIgh):

# 从当前 vendored 源码干净重建所有依赖(无任何版本覆盖) gh workflow run 5_builderpackage_externals.yml --ref <branch> # 覆盖一个或多个依赖版本;裸上游源码按 external_sources.sh 中的 URL 拉取 gh workflow run 5_builderpackage_externals.yml --ref <branch> \ -f dependencies="curl:8.13.0;openssl:3.5.2"

两个输入参数

输入作用默认值
dependencies分号分隔的name:version覆盖列表。命名的依赖会按 external_sources.sh 中登记的上游 URL 重新拉取,并替换make deps已解包的内容;未列出的依赖则从 vendored 源码重建""(仅重建)
docker_image_tagLinux 腿使用的 GHCR 构建器镜像标签。auto从 VERSION.json 推导;developer使用分支名;其他任意值按字面标签处理auto

文档特别强调:没有 per-leg 的分发输入参数。依赖发布是“全有或全无”——部分输出会发布出某个平台缺腿、导致make deps在其上失败的包。若只需重跑失败的单个腿,用 GitHub 的Re-run failed jobs即可(详见第五节)。

三、执行矩阵:7 个固定腿

矩阵在 5_builderpackage_externals.yml 中写死为 7 项:

目标运行器说明
rpm-amd64agentwz-linux-amd64CentOS 6 agent 构建器镜像(glibc 2.12 基线)
rpm-arm64agentwz-linux-arm64CentOS 6 agent 构建器镜像
macos-intel64agentmacos-14-large原生 macOS 构建,仅 agent
macos-arm64agentmacos-14原生 macOS 构建,仅 agent
windows-i686agentwz-linux-amd64compile_windows_agent镜像(ubuntu:22.04,与官方 Windows agent 构建相同)内 MinGW 交叉编译,仅 agent
rpm-amd64managerwz-linux-amd64CentOS 7 manager 构建器镜像(glibc 2.17)
rpm-arm64managerwz-linux-arm64CentOS 7 manager 构建器镜像

为什么每个 Linux 架构要跑两次

文档解释得很清楚:manager 镜像(CentOS 7)能编译 agent 镜像(CentOS 6)工具链无法处理的个别依赖;但 agent 镜像更老的 glibc 才是其余所有依赖的安全基线。两条腿都构建完整的依赖集,consolidate作业在两者都存在时优先选择 agent 镜像的产物,从而尽可能发布 glibc-2.12 兼容的二进制。

为什么没有单独的 deb 腿

rpm 的 glibc 与 deb 向前兼容,所以不必单独构建 deb 腿。这一点在工作流头注释(rpm 是“oldest common denominator”)与 build_external.sh 的参数校验中都有印证——--system虽接受deb/rpm,但矩阵实际只跑 rpm。

macOS 腿的细节

macOS 腿在 macOS runner 上原生运行(无 Docker),因此 generate_external.sh 直接以环境变量方式调用build_external.sh。由于系统自带 BSD tar 不识别--owner/--group/--no-same-owner,工作流通过brew install bash automake gnu-tar安装 GNU tar(gtar),保证打包结果可复现(所有内部 tarball 统一 owner=0/group=0)。此外build_external.sh会检测 macOS 自带的 bash 3.2(无关联数组declare -A),自动用 Homebrew bash ≥4 重新执行自身。

四、三个作业的完整链路

文档给出作业依赖图:

build-externals (matrix, 7 jobs) │ └─► consolidate ──► smoke-build (5 jobs)

1.build-externals:按腿构建

每个矩阵项的执行步骤(见工作流源码):

  1. 解析镜像标签auto时从VERSION.jsonversion字段取值;Linux 腿容器名为pkg_<system>_<target>_builder_<arch>,windows 腿固定为compile_windows_agent
  2. 从 GHCR 拉取构建器镜像(rpm/windows 腿)。
  3. 运行bash externals/generate_external.sh --system … --architecture … --target … --tag … --dependencies … --jobs … --verbose
  4. 上传按腿产物externals-<leg>-<target>.tar.gz到内部 S3。

generate_external.sh在容器内执行的build_external.sh是核心,其主流程(与文档描述一致并可逐条对应源码):

  • 从 src/Makefile 提取DEPS_VERSION——这是所有“转售” blob 下载 URL 的唯一事实来源;
  • 安装运行时工具(zip/unzip、clang、libelf、pkg-config、expat、perl-IPC-Cmd、perl-Time-Piece 等,按 apt/yum/brew 分派);
  • 通过make -s print-EXTERNAL_RES TARGET=<MAKE_TARGET>读取该腿的依赖列表(Windows 腿用TARGET=winagent,只取 Windows 相关依赖);
  • make deps EXTERNAL_SRC_ONLY=yes填充src/external/源码目录;
  • 清理 AppleDouble._*文件与 sqliteVERSION大小写冲突文件(后者在大小写不敏感的 APFS 上会遮蔽 libc++ 的<version>标准头);
  • apply_updates处理--dependencies覆盖(见下节);
  • 预构建源码快照*_src.zip
  • staging 预编译 bloblibbpf-bootstrap(Linux 腿)、cpython(manager 腿,*.passthrough.tar.gz直通);
  • make build-external TARGET=…实际编译;
  • 恢复 pattern-B 归档:cJSON/sqlite/procps 这类“源码回退”型依赖,其编译产物位于 CMake build 树而非源码树,脚本显式把libcjson.a/libsqlite3.a/libproc.a拷回src/external/下 CMakeLists 探测的路径,否则下游会静默回退源码编译;
  • 构建后快照*_<system>_<arch>.zip

2.consolidate:合并去重,产出最终包

consolidate下载全部按腿 tarball,合并成规范的libraries/{linux,darwin,windows,sources}/布局,并产出最终要发布externals-all.tar.gz。合并逻辑完全输出驱动(不写死任何依赖名):

  • has_compiled_artifact判断某个<dep>.tar.gz内部是否真的带编译产物(.a/.so/.lib);
  • 优先级规则(should_replace):
    1. 编译产物版本优先于纯源码快照——这正是 manager 专属依赖(rocksdb、jemalloc 等,见 src/external/CMakeLists.txt 中if(NOT IS_AGENT ...)门控)能由 manager 腿供应的原因;
    2. 两个编译版本之间,agent 腿胜出(glibc 2.12 兼容性);
  • 处理顺序:先 manager 腿、后 agent 腿,后写覆盖先写;sources/下各腿字节一致,先写者胜。

打包使用tar -czf externals-all.tar.gz --owner=0 --group=0 --no-same-owner -C consolidated libraries,保证解包到任意 UID 下都安全。

3.smoke-build:冒烟验证

对 4 个 Linux 组合(amd64/arm64 × agent/manager)加一个 windows-i686 腿,共 5 个作业。关键机制:

  • 下载externals-all.tar.gz解包到本地libraries/树;
  • RESOURCES_URL=file://<DEPS_DIR>覆盖 src/Makefile 中的默认 URL,让make deps直接从本地树“下载”每个<dep>.tar.gzcurl原生支持file://),随后在匹配的构建器镜像内执行真实的make TARGET=…构建(Linux 用pkg_rpm_<target>_builder_<arch>,windows 用compile_windows_agent)。

这里的make TARGET有映射关系:agent→agent、manager→server(server 目标才引入 build_python 和 manager 专属 externals)、windows→winagent

“Analyze dependency usage”步骤是质量闸门:它 grep 构建日志,查找Using precompiled(被消费的预编译依赖)、Performing build step for '<dep>_external'/Creating directories for …(ExternalProject 型依赖回退源码编译)、以及Building … ext_<name>.dir/(pattern-B 型依赖回退源码编译),对每一个回退项发出::warning::——这意味着二进制被打包到了 src/external/CMakeLists.txt 不预期的路径。任何 smoke warning 都应按阻塞问题处理,因为依赖发布的意义就是“一切预编译”。

windows 腿的特殊价值:它专门捕获打包进libraries/windows/<dep>.tar.gz的宿主侧工具(例如 flatbuffers 的flatc,在make TARGET=winagent的 schema 代码生成阶段被调用)是否构建在比消费镜像更新的 glibc/libstdc++ 之上——没有这条腿,这类不匹配要等下游 Windows agent 构建时才会暴露。

五、关键 Caveats(发布红线)

不要在运行工作流的同一分支提升DEPS_VERSION

DEPS_VERSION(src/Makefile)是工作流下载所有 blob 的唯一事实来源,三处消费:

  • make deps EXTERNAL_SRC_ONLY=yes(源码种子)读取RESOURCES_URL = packages.wazuh.com/deps/$(DEPS_VERSION)/
  • cpython 直通块拉取…/deps/${DEPS_VERSION}/libraries/sources/cpython_<arch>.tar.gz
  • stage_precompiled拉取…/deps/${DEPS_VERSION}/libraries/linux/<arch>/libbpf-bootstrap.tar.gz

如果你的分支把DEPS_VERSION提升到了你正要产出的版本,上述三处全部 404,运行失败。正确姿势:用指向当前已发布依赖版本的DEPS_VERSION分发工作流,上传新 tarball 后,在后续 PR中再提升。

libbpf-bootstrapcpython是“转售”而非重建

  • libbpf-bootstrap需要 clang ≥ 7 且带 BPF 后端,以及 Linux UAPI 头 ≥ 4.13(linux/bpf_perf_event.h)——旧式 agent 构建器镜像(CentOS 6 / Debian wheezy 时代,glibc 2.12)两者皆无,源码构建会报linux/bpf_perf_event.h: No such file or directory。Wazuh 在独立的 centos:7 + clang-15-from-source 镜像中构建它(issue #28626)。仅 Linux 腿涉及;macOS/Windows agent 不包含。
  • cpython有专属流水线5_builderpackage_embedded-python.yml(运行 framework/cpython/compile.sh),本工作流只对 manager 腿做直通下载。agent 的EXTERNAL_RES中根本没有$(CPYTHON)

因此提升这两者不在本工作流范围内。标准流程:先跑专属流水线产出新 blob → 上传到packages.wazuh.com/deps/<新版本>/libraries/…→ 提升DEPS_VERSION(因为build_external.sh直接读 Makefile,这一处提升即可让下次运行拿到新 blob)。

macOS 与 Windows 仅限 agent

不存在 darwin/windows 的 manager 构建器镜像,矩阵如实反映;不要为这两个系统添加 manager 条目。generate_external.sh的参数校验也强制了这一点:macos/windows组合manager目标直接报错退出。

源码回退是“静默”的

src/Makefile 的预编译拉取规则是-@… || true——缺少预编译包非致命,CMake 会回退源码编译。因此打包路径错误(例如二进制放到了libraries/linux/amd64/make deps找的是libraries/linux/x86_64/)不会让make deps直接失败,只能靠 smoke-build 的依赖分析步骤抓出来。

consolidate 的决胜规则

Linux 上 agent 与 manager 腿都构建 agent 依赖集,因此每个 Linux 架构会得到每份 agent 依赖的两份拷贝。consolidate先处理 manager 腿、再处理 agent 腿,先写者胜——于是当两者都存在时发布的是 agent(更老 glibc)那份;manager 专属依赖(cpython、jemalloc、simdjson 等)只有 manager 一份候选,原样发布。源码 zip 各腿字节一致,先写者胜无副作用。

调试单个失败腿

Re-run failed jobs而不是重新分发一次完整运行:单腿重跑沿用相同的触发分支与输入,并归入同一次整体 run,因此当所有腿最终成功后consolidate仍能基于完整集合运行。

若某腿在本地持续失败,可按文档给出的方式在分支 checkout 上手动复现:

bash packages/externals/generate_external.sh \ --system <sys> --architecture <arch> --target <agent|manager> --verbose

该脚本会自动选择对应构建器镜像(pkg_<sys>_<target>_builder_<arch>compile_windows_agent)并挂载当前工作树。

六、依赖矩阵:谁在哪个平台编译、如何发布

文档提供的依赖矩阵可直接对照 src/Makefile 的EXTERNAL_RES条件累加逻辑与 src/external/CMakeLists.txt 的编译/链接门控。图例:✔ 编译并链接 · — 不使用。目标缩写:LaLinux agent ·LmLinux manager/server ·MamacOS agent ·WaWindows agent(MinGW)。

全平台通用(每个平台、每个目标)

依赖LaLmMaWa发布形态
cJSON预编译.a(可源码回退)
openssl预编译.a
zlib预编译.a(非 Windows 时内附 minizip)
sqlite预编译.a(可源码回退)
libyaml预编译.a
curl预编译.a
libpcre2预编译.a
flatbuffers预编译.a+flatc
nlohmann仅源码(头文件)

共享的构建期依赖(所有目标都下载,非 Windows 才链接)

shared.h在所有目标上都会引入shared/include/bzip2_op.h<bzlib.h>(bzip2 单测包装也需要头文件),因此源码处处都下载——包括 Windows agent;但只有非 Windows 目标链接libbz2(src/external/CMakeLists.txt 在NOT IS_WINDOWS下构建ext_bzip2):

依赖LaLmMaWa发布形态
bzip2预编译.a(仅非 Windows 链接)。agent/server 经shared/src/bzip2_op.clibwazuhext链接;server 另以WITH_BZ2构建 rocksdb

仅 Linux agent

消费方是data_provider/sysinfo、syscheckd(whodata)、rootcheck等 agent 专属子目录;server 的wazuh_modules构建的是 inventory_sync/vulnerability_scanner,因此不链接其中任何一项:

依赖LaLmMaWa发布形态
audit-userspace预编译.aENABLE_AUDIT门控,agent 专属)
procps预编译.a(可源码回退)
libdb预编译.a
popt预编译.a(rpm 依赖)
lua预编译.a(rpm 依赖)
rpm预编译.a
dbus预编译.a
libbpf-bootstrap转售预构建(见 Caveats)

仅 macOS agent

依赖LaLmMaWa发布形态
libplist预编译.a

仅 Linux server(manager)

依赖LaLmMaWa发布形态
cpython转售5_builderpackage_embedded-python.yml产出)
libffi预编译.a(cpython/ctypes)
jemalloc预编译.so
rocksdb预编译.so
simdjson预编译.a
abseil-cpp预编译.a
re2预编译.a(依赖 abseil)
spdlog预编译.a
yaml-cpp预编译.a
pugixml预编译.a
libmaxminddb预编译.a
protobuf预编译.a
date预编译.a(依赖 curl)
fmt预编译.a
minizip预编译.a——寄居于 zlib 树,由非 Windows 腿(含 agent)构建,随zlib.tar.gz发布
rapidjson仅源码(头文件)
RxCpp仅源码(头文件)
taskflow仅源码(头文件)
concurrentqueue仅源码(头文件)
fast_float仅源码(头文件)
cpp-httplib仅源码(头文件)
geo_db数据 blob(MaxMind GeoLite2),sources 桶
tzdata数据(IANA tz),sources 桶

头文件类依赖(nlohmann、cpp-httplib、rapidjson、RxCpp、taskflow、concurrentqueue、fast_float)不携带编译产物,只应存在于libraries/sources/。当前生成快照仍会把它们的(无二进制)目录复制进libraries/<os>/<arch>/,裁剪这些冗余按架构副本已作为后续工作跟踪(#36247)。

测试框架(所有目标都下载,仅编入测试二进制)

它们仅在设置UNIT_TEST/WAZUH_ENGINE_TEST编译,但无条件下载:deps 步骤(make deps TARGET=…)不传TEST=1,而单测 CI 消费的是同一份 per-(os,arch) 依赖包,若把下载门在 flag 后面,依赖包就缺了它们,所有测试构建都会失败:

依赖下载于编入发布形态
googletest所有目标agent + server 测试预编译.a
benchmark所有目标server 测试预编译.a

七、产出物与目录布局(路径不可协商)

按腿产物(externals-rpm-amd64-agentexternals-macos-arm64-agent等)保留 14 天供调试。要发布的是externals-allexternals-all.tar.gz,其内部布局与它被上传到的 S3 目录一一对应:

libraries/ ├── linux/{amd64,aarch64}/<dep>.tar.gz ← 预编译二进制 ├── darwin/{amd64,aarch64}/<dep>.tar.gz ├── windows/<dep>.tar.gz ← MinGW 无架构子目录 └── sources/<dep>.tar.gz ← 上游源码快照

make deps精确遍历这棵树——路径布局不可协商linux/aarch64darwin/aarch64的命名差异来自 generate_external.sh 的S3_PATH映射(rpm-arm64 →linux/aarch64,macos-arm64 →darwin/aarch64,windows-i686 →windows);如果你改了该映射,必须同步修改 src/Makefile 的PRECOMPILED_RES(反之亦然)。

冒烟构建日志(smoke-build-<target>-<arch>)同样保留 14 天,当下游构建意外开始从源码拉依赖时,这是第一个排查点。

八、发布新 DEPS_VERSION 的安全顺序

文档给出的五步流程,与源码逐条印证:

  1. 开一个分支,如需修改清单 URL 则编辑 external_sources.sh。
  2. dependencies="…"(或留空做干净重建)分发工作流。此分支不要提升DEPS_VERSION(见 Caveats)。
  3. 等待build-externalsconsolidate与全部 4 个smoke-build作业变绿。
  4. 下载externals-all.tar.gz,选定新DEPS_VERSION(团队惯例为99-<gh-run-id>或手工挑选的单调数字),把 tarball 内容上传到s3://…/deps/<新-DEPS_VERSION>/libraries/…
  5. 第二个 PR把 src/Makefile 中的DEPS_VERSION提升到新值。这一处修改就足够——build_external.sh直接从 Makefile 读取DEPS_VERSION,同时用于make deps种子和 cpython/libbpf 的转售 URL。

九、依赖清单的版本覆盖机制(源码视角)

若要通过dependencies输入覆盖某个依赖版本,其底层路径在 build_external.sh 中一目了然:

  • replace_dep_source依据 external_sources.sh 中登记的EXT_URL/EXT_FORMAT/EXT_STRIP/EXT_TARGET/EXT_LINUX_ONLY元数据,先resolve_url把 URL 模板展开为真实下载地址,再download(最多 4 次尝试、指数退避)并extract(支持 tar.gz/bz2/xz/zip,等效--strip-components)。
  • URL 模板支持三种占位符:{version}{version_us}(点转下划线)、{version_concat}(把3.51.1映射为 sqlite 的连写形式3510100,目前仅 sqlite 使用,模板中甚至硬编码了发布年份路径)。
  • 对 GitHub 系上游,支持gh:owner/repo:tag-template形式:调用 GitHub releases API,按声明格式选取第一个命名 release asset,无 asset 时回退到自动生成的tarball_url。用GITHUB_TOKEN认证;无认证时未登录 API 限额约 60 次/小时/IP,足够一次完整的 28 依赖运行。
  • apply_updates对每个失败的依赖只记录跳过并继续——这样一次运行能产出 manifest URL 模板的完整校验报告,而不是在第一个坏 URL 上中止;跳过的依赖回退到make deps已从 Wazuh 源码镜像解包的版本。
  • 单个依赖下载失败不影响整个运行,因为不命名在输入里的依赖本来就会从 vendored 源码重建

十、相关文档与进一步阅读

  • Package generation —generate_package.sh,把构建好的源码与依赖变为可发布的.rpm/.deb
  • src/Makefile —DEPS_VERSIONRESOURCES_URLPRECOMPILED_RESEXTERNAL_RES等关键变量(第 413-508 行)。
  • src/external/CMakeLists.txt — 消费端:决定哪些依赖短路使用预编译归档、哪些从源码构建(每处均有Using precompiled …if(EXISTS …)短路径)。
  • framework/cpython/compile.sh — cpython 嵌入式解释器的专属构建脚本。

掌握这条流水线,你就同时理解了 Wazuh“预编译依赖 + 固定构建基线 + 输出驱动合并 + 冒烟验证”的发布哲学:任何一次上游库升级,本质上都是一次对 7 个腿、28 个依赖、数千行构建脚本的受控排练。

【免费下载链接】wazuhWazuh - The Open Source Security Platform. Unified XDR and SIEM protection for endpoints and cloud workloads.项目地址: https://gitcode.com/GitHub_Trending/wa/wazuh

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询