#!/usr/bin/env bash # 生成并签名一个镜像的发布清单:ci/release-manifest.sh <组件> <镜像仓库> <通道> <输出目录> # 签名私钥从环境变量 RELEASE_SIGNING_KEY(PEM 内容)读取,只写进一个 600 权限的临时文件,用完即删;私钥只放在 CI 密钥里,不进仓库、不上传面板。 # 换发布密钥:把新公钥(PEM)放进环境变量 RELEASE_NEXT_KEY,清单里就会带 "next_key"(SPKI DER 的 base64);这份清单仍由旧密钥签名, # 宿主机更新器验过之后改认新密钥。下一个版本起把 RELEASE_SIGNING_KEY 换成新私钥、去掉 RELEASE_NEXT_KEY,并把安装脚本和 release-key.pub.pem 里的公钥换成新的。 # 输出:<输出目录>/manifest.json(扁平 JSON,签名对象是它的原始字节)和 manifest.sig(base64 的 DER ECDSA 签名)。 set -euo pipefail component="${1:?usage: release-manifest.sh }" repository="${2:?}"; digest="${3:?}"; tag="${4:?}"; channel="${5:?}"; outdir="${6:?}" [[ "$component" =~ ^(agent|probe|backend)$ ]] || { echo "unsupported component: $component" >&2; exit 2; } [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || { echo "digest must look like sha256:<64 hex>" >&2; exit 2; } [[ "$repository" =~ ^[a-z0-9./_-]+$ && "$tag" =~ ^[A-Za-z0-9._-]+$ && "$channel" =~ ^[a-z0-9._-]+$ ]] || { echo "unexpected characters in repository, tag or channel" >&2; exit 2; } [[ -n "${RELEASE_SIGNING_KEY:-}" ]] || { echo "RELEASE_SIGNING_KEY is required" >&2; exit 2; } root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" version="$(sed -n 's#.*\(.*\).*#\1#p' "$root/backend/Directory.Build.props" | head -1)" commit="$(git -C "$root" rev-parse --short=10 HEAD 2>/dev/null || true)" built_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" seq="$(date +%s)" # 静态安装脚本的 SHA-256:面板给的命令会先核对它再执行;记在签名的清单里,面板和用户都能拿它和这份构建对照 install_sh="$(sha256sum "$root/packaging/install/install.sh" | cut -d" " -f1)" install_ps1="$(sha256sum "$root/packaging/install/install.ps1" | cut -d" " -f1)" next_key="" if [[ -n "${RELEASE_NEXT_KEY:-}" ]]; then next_key="$(printf '%s\n' "$RELEASE_NEXT_KEY" | openssl pkey -pubin -inform PEM -outform DER | base64 -w0 2>/dev/null || true)" [[ -n "$next_key" ]] || { echo "RELEASE_NEXT_KEY is not a public key" >&2; exit 2; } fi # 官方二进制的哈希(逗号分隔的小写十六进制):节点握手时上报正在运行的二进制的哈希,Backend 用这份签名的清单认「官方」 binaries="" if [[ -n "${RELEASE_BINARIES:-}" ]]; then [[ "$RELEASE_BINARIES" =~ ^[0-9a-f]{64}(,[0-9a-f]{64})*$ ]] || { echo "RELEASE_BINARIES must be comma-separated sha256 hashes" >&2; exit 2; } binaries=",\"binaries\":\"$RELEASE_BINARIES\"" fi mkdir -p "$outdir" printf '{"schema":1,"component":"%s","channel":"%s","image":"%s","digest":"%s","tag":"%s","version":"%s","commit":"%s","built_at":"%s","seq":%s,"install_sh":"%s","install_ps1":"%s"%s%s}' \ "$component" "$channel" "$repository" "$digest" "$tag" "$version" "$commit" "$built_at" "$seq" "$install_sh" "$install_ps1" "${next_key:+,\"next_key\":\"$next_key\"}" "$binaries" > "$outdir/manifest.json" key="$(mktemp)" trap 'rm -f "$key" "$outdir/manifest.sig.bin"' EXIT chmod 600 "$key" printf '%s\n' "$RELEASE_SIGNING_KEY" > "$key" openssl dgst -sha256 -sign "$key" -out "$outdir/manifest.sig.bin" "$outdir/manifest.json" base64 -w0 "$outdir/manifest.sig.bin" > "$outdir/manifest.sig" 2>/dev/null || base64 "$outdir/manifest.sig.bin" | tr -d '\n' > "$outdir/manifest.sig" echo "signed $component $tag ($digest) seq=$seq"