From 8db77c274ba845eb050ca6fca9ca5adeed5df0b2 Mon Sep 17 00:00:00 2001 From: JustMe Date: Mon, 5 Oct 2026 18:35:24 +0000 Subject: [PATCH] fix(ci): trigger fastlane on merged main PRs Run the Linux amd64 fast-lane after a PR is merged to main, while leaving unmerged PRs as no-ops. Co-authored-by: JustMe --- .woodpecker/main-fastlane.yml | 3 +++ ci/main-fastlane.py | 3 +++ ci/release-linux.sh | 5 +++-- ci/release-main-images.sh | 41 +++++++++++++++++++++++++++-------- docs/deployment.md | 2 +- 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.woodpecker/main-fastlane.yml b/.woodpecker/main-fastlane.yml index bcd4079..ce989e2 100644 --- a/.woodpecker/main-fastlane.yml +++ b/.woodpecker/main-fastlane.yml @@ -11,6 +11,8 @@ clone: when: - event: push branch: main + - event: pull_request_closed + branch: main concurrency: 1 @@ -19,6 +21,7 @@ steps: image: bash environment: NUGET_PACKAGES: /home/justme/.nuget/packages + GITEA_BASE: http://127.0.0.1:3100 ZOT_REGISTRY: registry.oeo.one ZOT_USERNAME: justme ZOT_SECRET: diff --git a/ci/main-fastlane.py b/ci/main-fastlane.py index 0fe6fdb..f3347f9 100644 --- a/ci/main-fastlane.py +++ b/ci/main-fastlane.py @@ -35,6 +35,9 @@ IGNORED_PARTS = {".git", ".release", "artifacts", "bin", "obj", "TestResults", " def read_changed_files() -> list[str]: raw = os.environ.get("CI_PIPELINE_FILES") + if os.environ.get("FASTLANE_FILES_FROM_PR_API") == "true": + value = json.loads(raw or "[]") + return [str(path).replace("\\", "/") for path in value] if raw: try: value = json.loads(raw) diff --git a/ci/release-linux.sh b/ci/release-linux.sh index 6ad5f63..f29ad97 100644 --- a/ci/release-linux.sh +++ b/ci/release-linux.sh @@ -12,8 +12,9 @@ if [[ -n "$tag" ]]; then } version="${tag#v}" else - [[ "${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main ]] || { - echo "untagged image builds are restricted to pushes on main" >&2 + [[ ("${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main) || \ + ("${CI_PIPELINE_EVENT:-}" == pull_request_closed && "${CI_COMMIT_BRANCH:-}" == main && "${FASTLANE_MERGED_PR:-}" == true) ]] || { + echo "untagged image builds are restricted to main pushes and merged PRs" >&2 exit 2 } version=main diff --git a/ci/release-main-images.sh b/ci/release-main-images.sh index 047eedc..6188ab9 100644 --- a/ci/release-main-images.sh +++ b/ci/release-main-images.sh @@ -2,15 +2,6 @@ # Build and push only the linux/amd64 OCI images affected by a main push. set -euo pipefail -[[ "${CI_PIPELINE_EVENT:-}" == push && "${CI_COMMIT_BRANCH:-}" == main ]] || { - echo "fast-lane images can only be published from a push to main" >&2 - exit 2 -} -[[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || { - echo "Zot credentials are required" >&2 - exit 2 -} - root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" registry="${ZOT_REGISTRY:-registry.oeo.one}" work="$root/.release/main-fastlane" @@ -20,6 +11,38 @@ export HOME="$buildah_home" export BUILDAH_HOME="$buildah_home" export REGISTRY_AUTH_FILE="$work/zot-auth.json" +if [[ "${CI_PIPELINE_EVENT:-}" == pull_request_closed ]]; then + [[ "${CI_COMMIT_TARGET_BRANCH:-}" == main && -n "${CI_COMMIT_PULL_REQUEST:-}" ]] || { + echo "ignoring closed pull request outside main" >&2 + exit 0 + } + gitea_base="${GITEA_BASE:-http://127.0.0.1:3100}" + api="$gitea_base/api/v1/repos/justme/MyMonitor" + pr_json="$(curl --fail-with-body --silent --show-error "$api/pulls/$CI_COMMIT_PULL_REQUEST")" + read -r merged base_branch <<< "$(python3 -c 'import json,sys; p=json.load(sys.stdin); print(str(p.get("merged", False)).lower(), p.get("base", {}).get("ref", ""))' <<< "$pr_json")" + if [[ "$merged" != true || "$base_branch" != main ]]; then + echo "pull request $CI_COMMIT_PULL_REQUEST was closed without merging to main; skipping images" + exit 0 + fi + + export FASTLANE_MERGED_PR=true + export FASTLANE_FILES_FROM_PR_API=true + export CI_PIPELINE_FILES="$(curl --fail-with-body --silent --show-error \ + "$api/pulls/$CI_COMMIT_PULL_REQUEST/files?limit=500" \ + | python3 -c 'import json,sys; print(json.dumps([name for item in json.load(sys.stdin) for name in (item["filename"], item.get("previous_filename")) if name]))')" + git fetch "$gitea_base/justme/MyMonitor.git" main + git checkout --detach FETCH_HEAD + export CI_COMMIT_BRANCH=main +elif [[ "${CI_PIPELINE_EVENT:-}" != push || "${CI_COMMIT_BRANCH:-}" != main ]]; then + echo "fast-lane images can only be published from a main push or a merged PR into main" >&2 + exit 2 +fi + +[[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || { + echo "Zot credentials are required" >&2 + exit 2 +} + targets="$(python3 "$root/ci/main-fastlane.py")" if [[ -z "$targets" ]]; then echo "no runtime image changes detected; skipping container builds" diff --git a/docs/deployment.md b/docs/deployment.md index a31d500..67b60a2 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -38,7 +38,7 @@ Compose 将容器端口只绑定到宿主机 `127.0.0.1:8080`,适合由同机 Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上,由 Windows runner 使用 AotAnywhere 并行构建 Agent/Probe 的 Linux glibc、Linux musl、macOS 和 Windows 产物。Agent 包还包含一次性注册工具 `fleet-enroll`。Linux 归档带有安装脚本和 systemd/OpenWrt 单元;Windows 产物为 `.zip`,Linux/macOS 产物为 `.tar.gz`。Linux glibc 的 Agent 与 Probe 二进制还会打包为 OCI 镜像,支持 `linux/amd64` 和 `linux/arm64`;musl、macOS、Windows 仍通过对应平台归档分发。所有平台产物和 OCI 镜像发布成功后,CI 会创建对应的 Gitea Release,并附上压缩包和 SHA-256 校验文件。macOS 产物目前未签名/公证。安装说明见 [`../backend/agent/README.md`](../backend/agent/README.md) 与 [`../backend/probe/README.md`](../backend/probe/README.md)。 -推送到 `main` 会触发 Linux/amd64 快车道;feature 分支的 PR 合并到 `main` 后才会更新这些镜像。CI 根据变更文件所在项目及 `.csproj` 的传递引用,只构建受影响的 Backend、Agent 或 Probe 镜像;Agent 镜像也包含 `fleet-enroll`。例如只改 Probe 时只构建 Probe;改动 `Monitor.Protocol` 等被多个程序引用的库时,会构建所有受影响镜像。结果推送到对应镜像的 `main` 和 `main-amd64` 标签,不改写 release 版本和 `latest`。只改 Web、文档或部署配置时不会构建容器。完整多架构和跨平台构建仍由版本 tag 流水线负责。 +推送到 `main` 或 feature PR 合入 `main` 后会触发 Linux/amd64 快车道;关闭但未合入的 PR 会跳过。CI 根据变更文件所在项目及 `.csproj` 的传递引用,只构建受影响的 Backend、Agent 或 Probe 镜像;Agent 镜像也包含 `fleet-enroll`。例如只改 Probe 时只构建 Probe;改动 `Monitor.Protocol` 等被多个程序引用的库时,会构建所有受影响镜像。结果推送到对应镜像的 `main` 和 `main-amd64` 标签,不改写 release 版本和 `latest`。只改 Web、文档或部署配置时不会构建容器。完整多架构和跨平台构建仍由版本 tag 流水线负责。 Agent 和 Probe 镜像分别发布为 `registry.oeo.one/monitor/monitor-agent:` 与 `registry.oeo.one/monitor/monitor-probe:`,包含 amd64、arm64 多架构索引;也可指定 `:-amd64` 或 `:-arm64`。稳定版本同时更新 `latest`。镜像以非 root 用户(UID/GID 1654)运行,默认读取 `/etc/monitor/agent.json` 或 `/etc/monitor/probe.json`。运行容器时挂载对应配置目录;Probe 还会在该目录写入 `agents.json`,因此目录需可由 1654 用户写入。Agent、Probe 需使用宿主网络以保持节点端口和测量流量行为,并按需添加 `NET_RAW` 能力以执行 ICMP 测量: