ci: all-platform node binaries in the signed release manifest; signed SHA256SUMS for the archives; web build once before the parallel backend builds; release 0.3.3 [CI SKIP]
This commit is contained in:
1 parent
cab505b837
commit
a4f3d1892a
9 files changed
+134
-10
No files matched your search
@@ -114,4 +114,40 @@ for pid in "${pids[@]}"; do
|
||||
wait "$pid"
|
||||
done
|
||||
|
||||
# 所有压缩包的校验和汇成一份 SHA256SUMS,用发布私钥签名(base64 的 DER ECDSA,和签名的发布清单是同一把密钥、同一种格式):
|
||||
# 不走容器的安装(直接下载压缩包)先验这份签名再核对压缩包的哈希,发布页被改也骗不到;没有配置签名密钥(例如 fork)就跳过。
|
||||
upload_local_asset() {
|
||||
local name="$1" file="$2" upload_response="$tmp/$1.upload.json" status
|
||||
if grep -Eq "\"name\"[[:space:]]*:[[:space:]]*\"$name\"" "$assets_json"; then
|
||||
echo "release asset $name already exists; keeping the existing asset"
|
||||
return 0
|
||||
fi
|
||||
status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
|
||||
--request POST --header 'Content-Type: application/octet-stream' --data-binary "@$file" \
|
||||
--output "$upload_response" --write-out '%{http_code}' "$api/releases/$release_id/assets?name=$name")"
|
||||
[[ "$status" == 2* ]] || { echo "Gitea Release asset upload failed for $name (HTTP $status)" >&2; cat "$upload_response" >&2; return 1; }
|
||||
echo "attached $name"
|
||||
}
|
||||
|
||||
if [[ -n "${RELEASE_SIGNING_KEY:-}" ]]; then
|
||||
sums="$tmp/SHA256SUMS"
|
||||
: > "$sums"
|
||||
for archive in "${assets[@]}"; do
|
||||
curl --fail --silent --show-error --retry 3 --netrc-file "$netrc" \
|
||||
"$gitea_base/api/packages/justme/generic/moshiro-node/$version/$archive.sha256" -o "$tmp/$archive.sha256"
|
||||
cat "$tmp/$archive.sha256" >> "$sums"
|
||||
done
|
||||
[[ "$(wc -l < "$sums")" -eq "${#assets[@]}" ]] || { echo "SHA256SUMS does not have one line per archive" >&2; exit 1; }
|
||||
signing_key="$(mktemp)"
|
||||
chmod 600 "$signing_key"
|
||||
printf '%s\n' "$RELEASE_SIGNING_KEY" > "$signing_key"
|
||||
openssl dgst -sha256 -sign "$signing_key" -out "$tmp/SHA256SUMS.sig.bin" "$sums"
|
||||
rm -f "$signing_key"
|
||||
base64 -w0 "$tmp/SHA256SUMS.sig.bin" > "$tmp/SHA256SUMS.sig" 2>/dev/null || base64 "$tmp/SHA256SUMS.sig.bin" | tr -d '\n' > "$tmp/SHA256SUMS.sig"
|
||||
upload_local_asset SHA256SUMS "$sums"
|
||||
upload_local_asset SHA256SUMS.sig "$tmp/SHA256SUMS.sig"
|
||||
else
|
||||
echo "release signing is not configured; SHA256SUMS is not signed"
|
||||
fi
|
||||
|
||||
echo "published Gitea Release $tag: https://git.oeo.one/justme/MoShiro/releases/tag/$tag"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# 一个组件在所有平台、所有架构的发布压缩包里的可执行文件的 SHA-256(逗号分隔,输出到标准输出)。
|
||||
# ci/release-binary-hashes.sh <agent|probe> <版本,不带 v> 环境变量:GITEA_BASE、NETRC(curl 的 netrc 文件)
|
||||
# 签名的发布清单里的 binaries 就是它:节点握手时上报正在运行的可执行文件的哈希,Backend 凭 CI 签名的清单认「官方二进制」——
|
||||
# 容器、Linux glibc/musl 压缩包、macOS、Windows 的 x64/arm64 都一样,不只是容器镜像里的那两个。
|
||||
set -euo pipefail
|
||||
component="${1:?usage: release-binary-hashes.sh <agent|probe> <version>}"; version="${2:?version}"
|
||||
[[ "$component" =~ ^(agent|probe)$ ]] || { echo "component must be agent or probe" >&2; exit 2; }
|
||||
gitea_base="${GITEA_BASE:?GITEA_BASE is required}"; gitea_base="${gitea_base%/}"
|
||||
netrc=(); [[ -n "${NETRC:-}" ]] && netrc=(--netrc-file "$NETRC")
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
# 和 ci/publish-gitea-release.sh 里发布的那 8 种一样(每个组件)
|
||||
targets=(linux-amd64 linux-arm64 linux-musl-amd64 linux-musl-arm64 macos-x64 macos-arm64 windows-amd64 windows-arm64)
|
||||
hashes=()
|
||||
for target in "${targets[@]}"; do
|
||||
if [[ "$target" == windows-* ]]; then archive="moshiro-$component-v$version-$target.zip"; member="moshiro-$component.exe"
|
||||
else archive="moshiro-$component-v$version-$target.tar.gz"; member="moshiro-$component"; fi
|
||||
curl --fail --silent --show-error --retry 3 ${netrc[@]+"${netrc[@]}"} "$gitea_base/api/packages/justme/generic/moshiro-node/$version/$archive" -o "$work/$archive"
|
||||
if [[ "$archive" == *.zip ]]; then
|
||||
hash="$(cd "$work" && python3 -c 'import hashlib,sys,zipfile; print(hashlib.sha256(zipfile.ZipFile(sys.argv[1]).read(sys.argv[2])).hexdigest())' "$archive" "$member")"
|
||||
else
|
||||
hash="$(tar -xzOf "$work/$archive" "$member" | sha256sum | cut -d' ' -f1)"
|
||||
fi
|
||||
[[ "$hash" =~ ^[0-9a-f]{64}$ ]] || { echo "could not hash $member in $archive" >&2; exit 1; }
|
||||
hashes+=("$hash")
|
||||
rm -f "$work/$archive"
|
||||
done
|
||||
# 不同平台的二进制哈希不会相同;去重只是防御
|
||||
printf '%s\n' "${hashes[@]}" | sort -u | paste -sd,
|
||||
@@ -67,8 +67,6 @@ build_arch_image() {
|
||||
"$gitea_base/api/packages/justme/generic/moshiro-node/$version/$filename" -o "$archive"
|
||||
mkdir -p "$context/app" "$context/config"
|
||||
tar -xzf "$archive" -C "$work" "moshiro-$component" fleet-enroll
|
||||
# 镜像里装的就是这个文件:它的哈希写进签名的发布清单(binaries),节点握手时上报正在运行的二进制的哈希,面板据此认「官方二进制」
|
||||
sha256sum "$work/moshiro-$component" | cut -d' ' -f1 >> "$work/$component.hashes"
|
||||
install -m 0755 "$work/fleet-enroll" "$context/app/fleet-enroll"
|
||||
install -m 0755 "$work/moshiro-$component" "$context/app/moshiro-$component"
|
||||
: > "$context/config/.keep"
|
||||
@@ -112,7 +110,6 @@ publish_index() {
|
||||
}
|
||||
|
||||
for component in agent probe; do
|
||||
rm -f "$work/$component.hashes"
|
||||
build_arch_image "$component" amd64 musl-amd64
|
||||
build_arch_image "$component" arm64 musl-arm64
|
||||
publish_index "$component"
|
||||
@@ -120,6 +117,8 @@ for component in agent probe; do
|
||||
# 没有配置签名密钥(例如 fork)就只发镜像、不发清单。
|
||||
if [[ -n "${RELEASE_SIGNING_KEY:-}" ]]; then
|
||||
digest="$(cat "$work/$component.digest")"
|
||||
RELEASE_BINARIES="$(paste -sd, "$work/$component.hashes")" bash "$root/ci/publish-release-offer.sh" "$component" "$image_root/moshiro-$component" "$digest" "$tag" release "$work/$component-release"
|
||||
# 清单里的 binaries:所有平台、所有架构的发布压缩包里的可执行文件(不只是镜像里的 musl 那两个),节点握手时上报的哈希对得上其中任何一个
|
||||
binaries="$(NETRC="$netrc" GITEA_BASE="$gitea_base" bash "$root/ci/release-binary-hashes.sh" "$component" "$version")"
|
||||
RELEASE_BINARIES="$binaries" bash "$root/ci/publish-release-offer.sh" "$component" "$image_root/moshiro-$component" "$digest" "$tag" release "$work/$component-release"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# release-binary-hashes.sh 的测试:用本地的假 Gitea 包仓库(python http.server),造 8 个平台的压缩包(tar.gz 和 zip),
|
||||
# 核对脚本算出来的哈希就是压缩包里可执行文件的 SHA-256。运行:bash ci/tests/binary-hashes-test.sh
|
||||
set -eu
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
T="$(mktemp -d)"
|
||||
SERVER=
|
||||
trap '[ -z "$SERVER" ] || kill "$SERVER" 2>/dev/null || true; rm -rf "$T" 2>/dev/null || true' EXIT
|
||||
V=9.9.9
|
||||
PKG="$T/srv/api/packages/justme/generic/moshiro-node/$V"
|
||||
mkdir -p "$PKG"
|
||||
fail=0
|
||||
expected=()
|
||||
for component in agent probe; do
|
||||
for target in linux-amd64 linux-arm64 linux-musl-amd64 linux-musl-arm64 macos-x64 macos-arm64 windows-amd64 windows-arm64; do
|
||||
stage="$T/stage-$component-$target"; mkdir -p "$stage"
|
||||
if [[ "$target" == windows-* ]]; then member="moshiro-$component.exe"; else member="moshiro-$component"; fi
|
||||
printf 'binary of %s for %s\n' "$component" "$target" > "$stage/$member"
|
||||
printf 'noise' > "$stage/fleet-enroll"; printf 'readme' > "$stage/README.md"
|
||||
if [[ "$target" == windows-* ]]; then
|
||||
(cd "$stage" && python3 -c "import zipfile,sys; z=zipfile.ZipFile('out.zip','w'); [z.write(n) for n in sys.argv[1:]]" "$member" fleet-enroll README.md && mv out.zip "$PKG/moshiro-$component-v$V-$target.zip")
|
||||
else
|
||||
tar -czf "$PKG/moshiro-$component-v$V-$target.tar.gz" -C "$stage" "$member" fleet-enroll README.md
|
||||
fi
|
||||
[[ "$component" == agent ]] && expected+=("$(sha256sum "$stage/$member" | cut -d' ' -f1)")
|
||||
done
|
||||
done
|
||||
want="$(printf '%s\n' "${expected[@]}" | sort -u | paste -sd,)"
|
||||
|
||||
(cd "$T/srv" && python3 -u -m http.server 0 --bind 127.0.0.1 >"$T/server.log" 2>&1 & echo $! > "$T/pid")
|
||||
SERVER="$(cat "$T/pid")"
|
||||
for _ in $(seq 1 50); do port="$(grep -o 'port [0-9]*' "$T/server.log" | head -1 | cut -d' ' -f2 || true)"; [[ -n "$port" ]] && break; sleep 0.2; done
|
||||
[[ -n "${port:-}" ]] || { echo "FAIL the fake package server did not start"; cat "$T/server.log"; exit 1; }
|
||||
|
||||
got="$(GITEA_BASE="http://127.0.0.1:$port" bash "$ROOT/ci/release-binary-hashes.sh" agent "$V")"
|
||||
if [[ "$got" == "$want" ]]; then echo "ok agent: all 8 platform binaries hashed ($(echo "$got" | tr ',' '\n' | wc -l) distinct)"; else echo "FAIL agent hashes differ"; echo " want $want"; echo " got $got"; fail=1; fi
|
||||
[[ "$(echo "$got" | tr ',' '\n' | wc -l)" -eq 8 ]] && echo "ok eight different binaries, none dropped" || { echo "FAIL expected 8 hashes"; fail=1; }
|
||||
|
||||
# 缺一个平台的压缩包:整个脚本失败,不能悄悄少签一个
|
||||
rm "$PKG/moshiro-probe-v$V-macos-arm64.tar.gz"
|
||||
if GITEA_BASE="http://127.0.0.1:$port" bash "$ROOT/ci/release-binary-hashes.sh" probe "$V" >/dev/null 2>&1; then echo "FAIL a missing archive was ignored"; fail=1; else echo "ok a missing archive fails the script instead of signing fewer binaries"; fi
|
||||
if bash "$ROOT/ci/release-binary-hashes.sh" other "$V" >/dev/null 2>&1; then echo "FAIL unknown component accepted"; fail=1; else echo "ok unknown component is refused"; fi
|
||||
exit "$fail"
|
||||
Reference in new issue
Block a user