docs: adopt no-CLA + DCO policy and fix copyright terms #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test Release Pipeline | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| paths: | |
| - ".github/goreleaser.yml" | |
| - ".github/nfpm.yml" | |
| - "Dockerfile.goreleaser" | |
| - "dockerscripts/download-static-curl.sh" | |
| - "dockerscripts/docker-entrypoint.sh" | |
| - "dockerscripts/docker-entrypoint_test.sh" | |
| - "silo.service" | |
| - "silo.env" | |
| - "silo.sysusers" | |
| - "buildscripts/package-release.sh" | |
| - "buildscripts/package/postinstall.sh" | |
| - "buildscripts/package/preremove.sh" | |
| - "buildscripts/package/lifecycle_test.sh" | |
| - "buildscripts/minio-upgrade.sh" | |
| - "buildscripts/sign-release-rpms.sh" | |
| - "buildscripts/verify-build-provenance.sh" | |
| - "buildscripts/verify-rebrand.sh" | |
| - "buildscripts/verify-helm-migration.sh" | |
| - "buildscripts/helm-migration-guard/**" | |
| - "helm/silo/**" | |
| - "buildscripts/rebrand-guard/**" | |
| - "buildscripts/gen-ldflags.go" | |
| - ".github/workflows/release.yml" | |
| - ".github/workflows/docker-release.yml" | |
| - ".github/workflows/test-release.yml" | |
| - ".gitignore" | |
| permissions: | |
| contents: read | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Go | |
| uses: actions/setup-go@v7 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Compute test variables | |
| run: | | |
| set -euo pipefail | |
| RELEASE_TAG="RELEASE.2026-02-14T12-00-00Z" | |
| VERSION_COLON="2026-02-14T12:00:00Z" | |
| PKG_VERSION="20260214120000.0.0" | |
| LDFLAGS="$(MINIO_RELEASE=RELEASE go run buildscripts/gen-ldflags.go "${VERSION_COLON}")" | |
| { | |
| echo "RELEASE_TAG=${RELEASE_TAG}" | |
| echo "PKG_VERSION=${PKG_VERSION}" | |
| echo "LDFLAGS=${LDFLAGS}" | |
| } >> "${GITHUB_ENV}" | |
| echo "PKG_VERSION: ${PKG_VERSION}" | |
| echo "LDFLAGS: ${LDFLAGS}" | |
| - name: GoReleaser config check | |
| uses: goreleaser/goreleaser-action@v7 | |
| with: | |
| version: "~> v2" | |
| args: check --config .github/goreleaser.yml | |
| - name: Validate Helm chart and legacy upgrade identity | |
| run: buildscripts/verify-helm-migration.sh | |
| - name: Install Syft | |
| uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| with: | |
| syft-version: v1.50.0 | |
| - name: Build snapshot artifacts | |
| uses: goreleaser/goreleaser-action@v7 | |
| with: | |
| version: "~> v2" | |
| # A pull-request snapshot has no trusted release identity. Exercise | |
| # the SBOM/checksum pipeline here, and reserve keyless signing for | |
| # the tag-triggered release workflow with GitHub OIDC. | |
| args: release --snapshot --clean --skip=publish,docker,sign --config .github/goreleaser.yml | |
| env: | |
| LDFLAGS: ${{ env.LDFLAGS }} | |
| PKG_VERSION: ${{ env.PKG_VERSION }} | |
| - name: Verify archive SBOM and checksum coverage | |
| run: | | |
| set -euo pipefail | |
| mapfile -t archives < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz' | sort) | |
| mapfile -t sboms < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz.sbom.json' | sort) | |
| test "${#archives[@]}" -eq 6 | |
| test "${#sboms[@]}" -eq 6 | |
| manifest="dist/silo_${PKG_VERSION}_checksums.txt" | |
| test -s "${manifest}" | |
| ( | |
| cd dist | |
| sha256sum --check "$(basename "${manifest}")" | |
| ) | |
| test "$(wc -l < "${manifest}" | tr -d ' ')" -eq 12 | |
| - name: Verify binary provenance stamps | |
| run: | | |
| set -euo pipefail | |
| buildscripts/verify-build-provenance.sh | |
| - name: Install package validation tools | |
| run: | | |
| set -euo pipefail | |
| go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.47.0 | |
| echo "$(go env GOPATH)/bin" >> "${GITHUB_PATH}" | |
| sudo apt-get update | |
| sudo apt-get install --yes rpm binutils | |
| - name: Package snapshot binaries with nFPM | |
| run: | | |
| set -euo pipefail | |
| buildscripts/package-release.sh | |
| - name: Validate package names, checksums, metadata, and payload | |
| run: | | |
| set -euo pipefail | |
| cd dist/packages | |
| # These are the public download names; a drift here breaks every | |
| # script that fetches packages by URL. | |
| expected=( | |
| "silo-${PKG_VERSION}-1.aarch64.rpm" | |
| "silo-${PKG_VERSION}-1.x86_64.rpm" | |
| "silo_${PKG_VERSION}_aarch64.apk" | |
| "silo_${PKG_VERSION}_amd64.deb" | |
| "silo_${PKG_VERSION}_arm64.deb" | |
| "silo_${PKG_VERSION}_x86_64.apk" | |
| ) | |
| for package in "${expected[@]}"; do | |
| test -s "${package}" | |
| test -s "${package}.sha256sum" | |
| sha256sum --check "${package}.sha256sum" | |
| done | |
| test "$(find . -maxdepth 1 -type f \( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | wc -l)" -eq 6 | |
| # The signing script asserts these same values, but it runs on the | |
| # maintainer's machine after the release workflow has already built | |
| # and uploaded. Take its expectations as the single source of truth | |
| # so nfpm.yml and the signing script cannot drift apart without | |
| # failing here first, while a fix is still cheap. | |
| # | |
| # This grep is deliberately limited to the seven identity variables, | |
| # all of which are single-line. That is what makes the eval safe: | |
| # should one ever become multi-line, the grep captures an | |
| # unterminated quote and the eval aborts on a syntax error under | |
| # set -e rather than quietly binding an empty value and comparing | |
| # against nothing. expected_payload is multi-line by design and must | |
| # stay out of this set for the same reason. | |
| eval "$(grep -E '^expected_(vendor|packager|url|summary|description|license|group)=' \ | |
| ../../buildscripts/sign-release-rpms.sh)" | |
| for value in "${expected_vendor}" "${expected_packager}" "${expected_url}" \ | |
| "${expected_summary}" "${expected_description}" \ | |
| "${expected_license}" "${expected_group}"; do | |
| test -n "${value}" | |
| done | |
| service_sha="$(sha256sum ../../silo.service | awk '{print $1}')" | |
| defaults_sha="$(sha256sum ../../silo.env | awk '{print $1}')" | |
| sysusers_sha="$(sha256sum ../../silo.sysusers | awk '{print $1}')" | |
| rpm_file="silo-${PKG_VERSION}-1.x86_64.rpm" | |
| test "$(rpm -qp --queryformat '%{VENDOR}' "${rpm_file}")" = "${expected_vendor}" | |
| test "$(rpm -qp --queryformat '%{PACKAGER}' "${rpm_file}")" = "${expected_packager}" | |
| test "$(rpm -qp --queryformat '%{URL}' "${rpm_file}")" = "${expected_url}" | |
| test "$(rpm -qp --queryformat '%{SUMMARY}' "${rpm_file}")" = "${expected_summary}" | |
| test "$(rpm -qp --queryformat '%{DESCRIPTION}' "${rpm_file}")" = "${expected_description}" | |
| test "$(rpm -qp --queryformat '%{LICENSE}' "${rpm_file}")" = "${expected_license}" | |
| test "$(rpm -qp --queryformat '%{GROUP}' "${rpm_file}")" = "${expected_group}" | |
| rpm -qpl "${rpm_file}" | grep -Fx '/usr/bin/silo' | |
| rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/systemd/system/silo.service' | |
| rpm -qpl "${rpm_file}" | grep -Fx '/etc/default/silo' | |
| rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/sysusers.d/silo.conf' | |
| test "$(rpm -qpl "${rpm_file}" | wc -l)" -eq 4 | |
| if rpm -qp --conflicts "${rpm_file}" | grep -qi minio; then | |
| echo "RPM must not declare a cross-name conflict with MinIO" >&2 | |
| exit 1 | |
| fi | |
| if rpm -qp --obsoletes "${rpm_file}" | grep -qi minio; then | |
| echo "RPM must not obsolete a MinIO package" >&2 | |
| exit 1 | |
| fi | |
| if rpm -qp --provides "${rpm_file}" | grep -qi minio; then | |
| echo "RPM must not provide a MinIO package alias" >&2 | |
| exit 1 | |
| fi | |
| deb_file="silo_${PKG_VERSION}_amd64.deb" | |
| test "$(dpkg-deb --field "${deb_file}" Maintainer)" = "${expected_packager}" | |
| test "$(dpkg-deb --field "${deb_file}" Version)" = "${PKG_VERSION}" | |
| test "$(dpkg-deb --field "${deb_file}" License)" = "${expected_license}" | |
| test "$(dpkg-deb --field "${deb_file}" Section)" = "utils" | |
| test "$(dpkg-deb --field "${deb_file}" Homepage)" = "${expected_url}" | |
| test "$(dpkg-deb --field "${deb_file}" Description)" = "${expected_description}" | |
| dpkg-deb --contents "${deb_file}" | grep -E 'usr/bin/silo$' | |
| dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/systemd/system/silo\.service$' | |
| dpkg-deb --contents "${deb_file}" | grep -E 'etc/default/silo$' | |
| dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/sysusers\.d/silo\.conf$' | |
| test "$(dpkg-deb --contents "${deb_file}" | awk '$1 !~ /^d/ { count++ } END { print count + 0 }')" -eq 4 | |
| test -z "$(dpkg-deb --field "${deb_file}" Conflicts)" | |
| test -z "$(dpkg-deb --field "${deb_file}" Replaces)" | |
| test -z "$(dpkg-deb --field "${deb_file}" Provides)" | |
| apk_info="$(tar -xOzf "silo_${PKG_VERSION}_x86_64.apk" .PKGINFO)" | |
| grep -Fx "pkgver = ${PKG_VERSION}" <<< "${apk_info}" | |
| grep -Fx "url = ${expected_url}" <<< "${apk_info}" | |
| grep -Fx "maintainer = ${expected_packager}" <<< "${apk_info}" | |
| grep -Fx "license = ${expected_license}" <<< "${apk_info}" | |
| grep -Fx "pkgdesc = ${expected_description}" <<< "${apk_info}" | |
| tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/bin/silo' | |
| tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/systemd/system/silo.service' | |
| tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'etc/default/silo' | |
| tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/sysusers.d/silo.conf' | |
| test "$(tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | awk '$0 !~ /^\./ && $0 !~ /\/$/ { count++ } END { print count + 0 }')" -eq 4 | |
| if grep -Ei '^provides = .*minio' <<< "${apk_info}"; then | |
| echo "APK must not provide a MinIO package alias" >&2 | |
| exit 1 | |
| fi | |
| for arch in amd64 arm64; do | |
| if [ "${arch}" = amd64 ]; then | |
| rpm_arch=x86_64 | |
| deb_arch=amd64 | |
| apk_arch=x86_64 | |
| else | |
| rpm_arch=aarch64 | |
| deb_arch=arm64 | |
| apk_arch=aarch64 | |
| fi | |
| test "$(rpm -qp --queryformat '%{ARCH}' "silo-${PKG_VERSION}-1.${rpm_arch}.rpm")" = "${rpm_arch}" | |
| test "$(dpkg-deb --field "silo_${PKG_VERSION}_${deb_arch}.deb" Architecture)" = "${deb_arch}" | |
| grep -Fx "arch = ${apk_arch}" <<< "$(tar -xOzf "silo_${PKG_VERSION}_${apk_arch}.apk" .PKGINFO)" | |
| # Accepted weakness: this takes the first match, unsorted, where | |
| # find_binary in package-release.sh demands exactly one. It cannot | |
| # be reached with an ambiguous match today, because packaging runs | |
| # earlier in this same job and hard-fails on one. Revisit if | |
| # goamd64 gains a second level, or if find_binary's exactly-one | |
| # contract is ever relaxed -- at that point this weak copy would be | |
| # the only one left choosing silently. | |
| source_binary="$(find .. -maxdepth 2 -type f -path "../silo_linux_${arch}*/silo" | head -n 1)" | |
| source_sha="$(sha256sum "${source_binary}" | awk '{print $1}')" | |
| # Do not pipe rpm2cpio here: Debian's build exits non-zero even when | |
| # it writes a correct payload, which trips `set -o pipefail`. Use | |
| # rpm's own digests instead -- -K checks the payload against the | |
| # header, and FILEDIGESTS is the sha256 rpm itself verifies on | |
| # install. | |
| rpm -K "silo-${PKG_VERSION}-1.${rpm_arch}.rpm" | |
| rpm_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \ | |
| "silo-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/bin/silo" { print $2 }')" | |
| rpm_service_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \ | |
| "silo-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/systemd/system/silo.service" { print $2 }')" | |
| rpm_defaults_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \ | |
| "silo-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/etc/default/silo" { print $2 }')" | |
| rpm_sysusers_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \ | |
| "silo-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/sysusers.d/silo.conf" { print $2 }')" | |
| deb_sha="$(ar p "silo_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/bin/silo | sha256sum | awk '{print $1}')" | |
| deb_service_sha="$(ar p "silo_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')" | |
| deb_defaults_sha="$(ar p "silo_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./etc/default/silo | sha256sum | awk '{print $1}')" | |
| deb_sysusers_sha="$(ar p "silo_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')" | |
| apk_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/bin/silo | sha256sum | awk '{print $1}')" | |
| apk_service_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')" | |
| apk_defaults_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" etc/default/silo | sha256sum | awk '{print $1}')" | |
| apk_sysusers_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')" | |
| test "${source_sha}" = "${rpm_sha}" | |
| test "${source_sha}" = "${deb_sha}" | |
| test "${source_sha}" = "${apk_sha}" | |
| test "${service_sha}" = "${rpm_service_sha}" | |
| test "${service_sha}" = "${deb_service_sha}" | |
| test "${service_sha}" = "${apk_service_sha}" | |
| test "${defaults_sha}" = "${rpm_defaults_sha}" | |
| test "${defaults_sha}" = "${deb_defaults_sha}" | |
| test "${defaults_sha}" = "${apk_defaults_sha}" | |
| test "${sysusers_sha}" = "${rpm_sysusers_sha}" | |
| test "${sysusers_sha}" = "${deb_sysusers_sha}" | |
| test "${sysusers_sha}" = "${apk_sysusers_sha}" | |
| done | |
| find . -maxdepth 1 -type f | sort | |
| - name: Build release runtime image and verify graceful shutdown | |
| run: | | |
| set -euo pipefail | |
| # docker-release.yml is workflow_dispatch only, so this is the only | |
| # automated build of the release runtime layer and entrypoint before a | |
| # real publish. Assemble a minimal image from the linux/amd64 binary | |
| # goreleaser already produced; the mcli-download build stage is skipped | |
| # on purpose to keep this gate offline and deterministic. | |
| ctx="$(mktemp -d)" | |
| tar -xzf "dist/silo_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" silo | |
| cp dockerscripts/docker-entrypoint.sh "${ctx}/docker-entrypoint.sh" | |
| { | |
| echo "FROM registry.access.redhat.com/ubi9/ubi-micro:latest" | |
| echo "COPY silo /usr/bin/silo" | |
| echo "COPY docker-entrypoint.sh /usr/bin/docker-entrypoint.sh" | |
| echo "RUN mkdir -p /data && chmod 0777 /data && chmod +x /usr/bin/silo /usr/bin/docker-entrypoint.sh" | |
| echo 'ENV HOME=/tmp' | |
| echo 'ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]' | |
| echo 'CMD ["silo"]' | |
| } > "${ctx}/Dockerfile" | |
| docker build -t silo-runtime-test:snapshot "${ctx}" | |
| # PID 1 must be silo, not the entry shell, on every privilege path, so | |
| # a SIGTERM from docker stop reaches the server and it exits gracefully | |
| # instead of being killed at the stop timeout. Regression guard for the | |
| # exec-into-chroot entrypoint fix. | |
| assert_graceful() { | |
| name="$1"; shift | |
| docker rm -f "${name}" >/dev/null 2>&1 || true | |
| docker run -d --name "${name}" \ | |
| -e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \ | |
| "$@" silo-runtime-test:snapshot silo server /data --address :9000 >/dev/null | |
| up="" | |
| for _ in $(seq 1 60); do | |
| if docker logs "${name}" 2>&1 | grep -q "API:"; then up=1; break; fi | |
| if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != "true" ]; then break; fi | |
| sleep 1 | |
| done | |
| if [ -z "${up}" ]; then echo "server did not start (${name}):"; docker logs "${name}" | tail -5; exit 1; fi | |
| pid1="$(docker exec "${name}" cat /proc/1/comm 2>/dev/null || echo '?')" | |
| start="$(date +%s)"; docker stop -t 15 "${name}" >/dev/null; end="$(date +%s)" | |
| code="$(docker inspect -f '{{.State.ExitCode}}' "${name}")" | |
| elapsed=$((end - start)) | |
| echo "${name}: pid1=${pid1} stop=${elapsed}s exit=${code}" | |
| graceful=0; docker logs "${name}" 2>&1 | grep -q "Exiting on signal" && graceful=1 | |
| docker rm -f "${name}" >/dev/null 2>&1 || true | |
| [ "${graceful}" = "1" ] || { echo "no graceful-shutdown log (${name}) - signal not forwarded"; exit 1; } | |
| [ "${code}" = "0" ] || { echo "non-zero exit (${name}): ${code}"; exit 1; } | |
| [ "${elapsed}" -lt 10 ] || { echo "shutdown too slow (${name}): ${elapsed}s - signal not forwarded"; exit 1; } | |
| } | |
| assert_graceful silo-rt-default | |
| assert_graceful silo-rt-dropuser -e MINIO_USERNAME=silo-user -e MINIO_GROUPNAME=silo-group | |
| assert_graceful silo-rt-rootless --user 1001:1001 | |
| # The compatibility shim translates only the legacy first argv token; | |
| # the image contains no /usr/bin/minio file. | |
| docker run --rm silo-runtime-test:snapshot sh -c 'test ! -e /usr/bin/minio' | |
| docker run --rm -d --name silo-rt-legacy \ | |
| -e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \ | |
| silo-runtime-test:snapshot minio server /data --address :9000 >/dev/null | |
| sleep 2 | |
| test "$(docker exec silo-rt-legacy cat /proc/1/comm)" = silo | |
| docker rm -f silo-rt-legacy >/dev/null | |
| - name: Validate release scripts | |
| run: | | |
| set -euo pipefail | |
| bash -n buildscripts/package-release.sh | |
| bash -n buildscripts/minio-upgrade.sh | |
| bash -n buildscripts/sign-release-rpms.sh | |
| bash -n buildscripts/verify-build-provenance.sh | |
| bash -n buildscripts/verify-rebrand.sh | |
| bash -n buildscripts/verify-helm-migration.sh | |
| sh -n buildscripts/package/postinstall.sh | |
| sh -n buildscripts/package/preremove.sh | |
| bash -n buildscripts/package/lifecycle_test.sh | |
| buildscripts/package/lifecycle_test.sh | |
| bash -n dockerscripts/docker-entrypoint_test.sh | |
| bash -n dockerscripts/download-static-curl.sh | |
| dockerscripts/docker-entrypoint_test.sh | |
| go run ./buildscripts/rebrand-guard | |
| buildscripts/verify-rebrand.sh | |
| test -x buildscripts/package-release.sh | |
| test -x buildscripts/sign-release-rpms.sh | |
| test -x buildscripts/verify-build-provenance.sh | |
| test -x buildscripts/verify-rebrand.sh | |
| test -x buildscripts/verify-helm-migration.sh | |
| test -x buildscripts/package/postinstall.sh | |
| test -x buildscripts/package/preremove.sh | |
| test -x buildscripts/package/lifecycle_test.sh | |
| test -x dockerscripts/docker-entrypoint_test.sh |