mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 07:43:29 +03:00
aa51393694
nFPM placed minio.service at /lib/systemd/system. On merged-usr EL/Fedora the
file lands correctly through the symlink, but the RPM database records /lib/...,
so `rpm -qf` disowns it and - more importantly - systemd's
%transfiletriggerin watches /usr/lib/systemd/system, so the path recorded as
/lib/... never fires the automatic `daemon-reload` on install or removal. Move
the unit to the canonical /usr/lib/systemd/system, which both restores that
trigger and makes the package own the path it ships.
The destination is asserted verbatim in several places, all updated in lockstep
so the packaging gate still passes: the six name/payload/sha checks in
test-release.yml (rpm/deb/apk) and expected_payload in sign-release-rpms.sh.
Verified by building a real deb with the new config: the unit is at
./usr/lib/systemd/system/minio.service.
Also drop the `# Built for ${project.name}...` line from minio.service. nFPM
expands variables in content src paths, not file bodies, so that pkger-era
placeholder was being written verbatim into every installed unit.
Not addressed here, deliberately: the package still does not create the
minio-user account the unit references. That matches upstream MinIO's own
packages (both rely on the documented manual useradd) and is not a fork
regression, so it stays a documentation step rather than a scriptlet.
Co-authored-by: Claude <noreply@anthropic.com>
283 lines
9.0 KiB
Bash
Executable File
283 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
# These are the single source of truth for the package identity: .github/nfpm.yml
|
|
# must agree with them, and test-release.yml asserts that it does. Drift the
|
|
# other way round would only surface here, on the maintainer's machine, after
|
|
# the build has already run and uploaded.
|
|
expected_fingerprint="9592A7BC7A682E7333376E09E7935D8DB9BD8B20"
|
|
expected_vendor="PGSTY"
|
|
expected_packager="Ruohang Feng (@Vonng) <rh@vonng.com>"
|
|
expected_url="https://silo.pgsty.com"
|
|
expected_summary="S3-Interface Libre Object Storage, Community-maintained MinIO server fork."
|
|
expected_description="S3-Interface Libre Object Storage, Community-maintained MinIO server fork."
|
|
expected_license="AGPL-3.0-or-later"
|
|
expected_group="Applications/File"
|
|
expected_payload="/usr/lib/systemd/system/minio.service
|
|
/usr/local/bin/minio"
|
|
repository="${GH_REPO:-pgsty/minio}"
|
|
container="${DNFUPDATE_CONTAINER:-dnfupdate}"
|
|
upload=false
|
|
release_tag=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: buildscripts/sign-release-rpms.sh RELEASE.TAG [--upload] [--repo OWNER/REPO] [--container NAME]
|
|
|
|
Downloads the two unsigned RPMs from a Draft GitHub Release, signs them with
|
|
the expected Pigsty key in the local dnfupdate container, verifies the result,
|
|
and regenerates their .sha256sum files. Nothing is uploaded unless --upload is
|
|
provided.
|
|
EOF
|
|
}
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--upload)
|
|
upload=true
|
|
;;
|
|
--repo)
|
|
shift
|
|
if [ "$#" -eq 0 ]; then
|
|
echo "--repo requires OWNER/REPO" >&2
|
|
exit 1
|
|
fi
|
|
repository="$1"
|
|
;;
|
|
--container)
|
|
shift
|
|
if [ "$#" -eq 0 ]; then
|
|
echo "--container requires a name" >&2
|
|
exit 1
|
|
fi
|
|
container="$1"
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-*)
|
|
echo "Unknown option: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [ -n "${release_tag}" ]; then
|
|
echo "Only one release tag may be specified" >&2
|
|
exit 1
|
|
fi
|
|
release_tag="$1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${release_tag}" ]; then
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
|
|
for command in docker gh; do
|
|
if ! command -v "${command}" >/dev/null 2>&1; then
|
|
echo "${command} is required" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
version_hyphen="${release_tag#RELEASE.}"
|
|
package_version="$(printf '%s\n' "${version_hyphen}" | sed -E \
|
|
's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
|
if [ "${package_version}" = "${version_hyphen}" ]; then
|
|
echo "Invalid release tag: ${release_tag}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$(gh release view "${release_tag}" --repo "${repository}" --json isDraft --jq .isDraft)" != "true" ]; then
|
|
echo "Refusing to sign: ${release_tag} is not a Draft release" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$(docker inspect --format '{{.State.Running}}' "${container}" 2>/dev/null || true)" != "true" ]; then
|
|
echo "Signing container is not running: ${container}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
secret_fingerprints="$(docker exec "${container}" \
|
|
gpg --batch --with-colons --list-secret-keys 2>/dev/null |
|
|
awk -F: '$1 == "fpr" { print toupper($10) }')"
|
|
if ! printf '%s\n' "${secret_fingerprints}" | grep -Fxq "${expected_fingerprint}"; then
|
|
echo "Expected signing key is not available in ${container}: ${expected_fingerprint}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
|
work_root="${SIGN_WORKDIR:-${repo_dir}/.release-sign}"
|
|
mkdir -p "${work_root}"
|
|
work_dir="$(mktemp -d "${work_root}/${release_tag}.XXXXXX")"
|
|
unsigned_dir="${work_dir}/unsigned"
|
|
signed_dir="${work_dir}/signed"
|
|
mkdir -p "${unsigned_dir}" "${signed_dir}"
|
|
chmod 700 "${work_dir}" "${unsigned_dir}" "${signed_dir}"
|
|
|
|
rpm_files=(
|
|
"minio-${package_version}-1.x86_64.rpm"
|
|
"minio-${package_version}-1.aarch64.rpm"
|
|
)
|
|
|
|
download_patterns=()
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
download_patterns+=(--pattern "${rpm_file}" --pattern "${rpm_file}.sha256sum")
|
|
done
|
|
|
|
echo "Downloading RPMs from Draft release ${repository}@${release_tag}"
|
|
gh release download "${release_tag}" --repo "${repository}" \
|
|
--dir "${unsigned_dir}" "${download_patterns[@]}"
|
|
|
|
sha256_digest() {
|
|
local file="$1"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "${file}" | awk '{print $1}'
|
|
else
|
|
shasum -a 256 "${file}" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
rpm_path="${unsigned_dir}/${rpm_file}"
|
|
checksum_path="${rpm_path}.sha256sum"
|
|
test -s "${rpm_path}"
|
|
test -s "${checksum_path}"
|
|
|
|
actual_line="$(sha256_digest "${rpm_path}") ${rpm_file}"
|
|
published_line="$(tr -d '\n' < "${checksum_path}")"
|
|
if [ "${actual_line}" != "${published_line}" ]; then
|
|
echo "Checksum mismatch for ${rpm_file}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
safe_tag="$(printf '%s' "${release_tag}" | tr -c 'A-Za-z0-9._-' '_')"
|
|
container_dir="/tmp/minio-sign-${safe_tag}-$$"
|
|
docker exec "${container}" mkdir -p "${container_dir}"
|
|
|
|
cleanup_container() {
|
|
local rpm_file
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
docker exec "${container}" rm -f "${container_dir}/${rpm_file}" >/dev/null 2>&1 || true
|
|
done
|
|
docker exec "${container}" rmdir "${container_dir}" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup_container EXIT
|
|
|
|
assert_rpm_tag() {
|
|
local rpm_path="$1"
|
|
local tag="$2"
|
|
local expected="$3"
|
|
local actual
|
|
|
|
actual="$(docker exec "${container}" rpm -qp --queryformat "%{${tag}}" "${rpm_path}")"
|
|
if [ "${actual}" != "${expected}" ]; then
|
|
echo "Unexpected RPM ${tag}: ${actual}" >&2
|
|
echo "Expected RPM ${tag}: ${expected}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
case "${rpm_file}" in
|
|
*.x86_64.rpm)
|
|
expected_arch="x86_64"
|
|
;;
|
|
*.aarch64.rpm)
|
|
expected_arch="aarch64"
|
|
;;
|
|
*)
|
|
echo "Unexpected RPM filename: ${rpm_file}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "Signing ${rpm_file} with ${expected_fingerprint}"
|
|
docker cp "${unsigned_dir}/${rpm_file}" "${container}:${container_dir}/${rpm_file}" >/dev/null
|
|
container_rpm="${container_dir}/${rpm_file}"
|
|
|
|
assert_rpm_tag "${container_rpm}" NAME minio
|
|
assert_rpm_tag "${container_rpm}" VERSION "${package_version}"
|
|
assert_rpm_tag "${container_rpm}" RELEASE 1
|
|
assert_rpm_tag "${container_rpm}" ARCH "${expected_arch}"
|
|
assert_rpm_tag "${container_rpm}" VENDOR "${expected_vendor}"
|
|
assert_rpm_tag "${container_rpm}" PACKAGER "${expected_packager}"
|
|
assert_rpm_tag "${container_rpm}" URL "${expected_url}"
|
|
assert_rpm_tag "${container_rpm}" LICENSE "${expected_license}"
|
|
assert_rpm_tag "${container_rpm}" GROUP "${expected_group}"
|
|
assert_rpm_tag "${container_rpm}" SUMMARY "${expected_summary}"
|
|
assert_rpm_tag "${container_rpm}" DESCRIPTION "${expected_description}"
|
|
|
|
rpm_payload="$(docker exec "${container}" rpm -qpl "${container_rpm}")"
|
|
if [ "${rpm_payload}" != "${expected_payload}" ]; then
|
|
echo "Unexpected RPM payload for ${rpm_file}:" >&2
|
|
printf '%s\n' "${rpm_payload}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
docker exec "${container}" rpmsign \
|
|
--define "_gpg_name ${expected_fingerprint}" \
|
|
--addsign "${container_rpm}"
|
|
|
|
signature_output="$(docker exec "${container}" rpmkeys --checksig --verbose "${container_rpm}")"
|
|
printf '%s\n' "${signature_output}"
|
|
if ! printf '%s\n' "${signature_output}" | tr '[:upper:]' '[:lower:]' | grep -q 'key id b9bd8b20: ok'; then
|
|
echo "Signature verification failed for ${rpm_file}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
docker cp "${container}:${container_dir}/${rpm_file}" "${signed_dir}/${rpm_file}" >/dev/null
|
|
signed_digest="$(sha256_digest "${signed_dir}/${rpm_file}")"
|
|
printf '%s %s' "${signed_digest}" "${rpm_file}" > "${signed_dir}/${rpm_file}.sha256sum"
|
|
|
|
docker exec "${container}" rpm -qp --queryformat \
|
|
$'Name: %{NAME}\nVersion: %{VERSION}-%{RELEASE}\nArch: %{ARCH}\nVendor: %{VENDOR}\nPackager: %{PACKAGER}\nURL: %{URL}\n' \
|
|
"${container_rpm}"
|
|
echo "SHA256: ${signed_digest}"
|
|
done
|
|
|
|
if [ "${upload}" = true ]; then
|
|
upload_files=()
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
upload_files+=("${signed_dir}/${rpm_file}" "${signed_dir}/${rpm_file}.sha256sum")
|
|
done
|
|
|
|
echo "Replacing RPMs in Draft release ${release_tag}"
|
|
gh release upload "${release_tag}" --repo "${repository}" --clobber "${upload_files[@]}"
|
|
|
|
for rpm_file in "${rpm_files[@]}"; do
|
|
for asset in "${rpm_file}" "${rpm_file}.sha256sum"; do
|
|
local_digest="sha256:$(sha256_digest "${signed_dir}/${asset}")"
|
|
remote_digest=""
|
|
for attempt in 1 2 3 4 5; do
|
|
remote_digest="$(gh release view "${release_tag}" --repo "${repository}" --json assets \
|
|
--jq ".assets[] | select(.name == \"${asset}\") | .digest")"
|
|
if [ "${local_digest}" = "${remote_digest}" ]; then
|
|
break
|
|
fi
|
|
if [ "${attempt}" -lt 5 ]; then
|
|
sleep 2
|
|
fi
|
|
done
|
|
if [ "${local_digest}" != "${remote_digest}" ]; then
|
|
echo "GitHub asset digest mismatch for ${asset}" >&2
|
|
echo "Local: ${local_digest}" >&2
|
|
echo "Remote: ${remote_digest}" >&2
|
|
exit 1
|
|
fi
|
|
echo "Verified GitHub asset: ${asset} ${remote_digest}"
|
|
done
|
|
done
|
|
else
|
|
echo
|
|
echo "Signed RPMs are ready for review in: ${signed_dir}"
|
|
echo "Re-run with --upload to replace the RPM assets in the Draft release."
|
|
fi
|