diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ae7ecd935d..00a02e87e6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: - - name: MinIO Community Support - url: https://slack.min.io - about: Please ask and answer questions here. \ No newline at end of file + - name: Silo Documentation + url: https://silo.pgsty.com/ + about: Client reference, downloads, and release notes for this fork. + - name: Report a Vulnerability + url: https://silo.pgsty.com/about/security/ + about: Please report security issues privately instead of opening a public issue. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..444520e0cf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 + +updates: + # Workflow actions are pinned to commit SHAs so a moving tag cannot change + # what runs in the release pipeline. Dependabot keeps those pins current. + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: + - "*" + commit-message: + prefix: ci diff --git a/.github/goreleaser.yml b/.github/goreleaser.yml index bdc933c30b..9a199641d4 100644 --- a/.github/goreleaser.yml +++ b/.github/goreleaser.yml @@ -10,8 +10,8 @@ before: hooks: - make clean - go generate ./... - - go mod tidy - go mod download + - go mod verify builds: - id: mcli diff --git a/.github/nfpm.yml b/.github/nfpm.yml index c51c08e40a..129584a1a4 100644 --- a/.github/nfpm.yml +++ b/.github/nfpm.yml @@ -4,15 +4,13 @@ platform: linux version: ${PKG_VERSION} version_schema: none release: ${NFPM_RELEASE} -section: "" +section: utils priority: optional maintainer: "Ruohang Feng (@Vonng) " -description: |- - S3-Interface Libre Object Storage - A MinIO fork maintained by the Pigsty community. +description: S3-Interface Libre Object Storage, Community-maintained MinIO client fork. vendor: PGSTY homepage: https://silo.pgsty.com -license: AGPLv3 +license: AGPL-3.0-or-later contents: - src: ${NFPM_SOURCE} @@ -30,4 +28,4 @@ rpm: deb: compression: gzip fields: - License: AGPLv3 + License: AGPL-3.0-or-later diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index c0a36a6f69..a03bb78ffb 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -54,7 +54,7 @@ jobs: } >> "${GITHUB_ENV}" - name: Checkout release tag - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: ref: ${{ inputs.tag }} fetch-depth: 0 @@ -93,21 +93,21 @@ jobs: echo "RELEASE_REVISION=$(git rev-parse HEAD)" >> "${GITHUB_ENV}" - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 with: platforms: arm64 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push amd64 image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: docker-release/amd64 file: docker-release/amd64/Dockerfile.goreleaser @@ -122,7 +122,7 @@ jobs: org.opencontainers.image.revision=${{ env.RELEASE_REVISION }} - name: Build and push arm64 image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: docker-release/arm64 file: docker-release/arm64/Dockerfile.goreleaser diff --git a/.github/workflows/go-cross.yml b/.github/workflows/go-cross.yml index 3c03b0a478..df22f85415 100644 --- a/.github/workflows/go-cross.yml +++ b/.github/workflows/go-cross.yml @@ -23,8 +23,8 @@ jobs: go-version: [1.26.5] os: [ubuntu-latest] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ matrix.go-version }} check-latest: true diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 35092d8f57..b3dea275fa 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,6 +11,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref }} cancel-in-progress: true +permissions: + contents: read + jobs: build: name: Test on Go ${{ matrix.go-version }} and ${{ matrix.os }} @@ -23,13 +26,13 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }} - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: ${{ matrix.go-version }} id: go - name: Check out code into the Go module directory - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Build on ${{ matrix.os }} if: matrix.os == 'windows-latest' @@ -73,12 +76,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: 1.26.5 - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + # The release build no longer runs `go mod tidy`, so drift has to be + # caught here instead of being silently applied while cutting a release. + - name: Check go.mod and go.sum are tidy + run: | + go mod tidy + git diff --exit-code -- go.mod go.sum - name: Test 386 run: GOOS=linux GOARCH=386 go test -short ./... diff --git a/.github/workflows/issues.yaml b/.github/workflows/issues.yaml deleted file mode 100644 index d91950aeb2..0000000000 --- a/.github/workflows/issues.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# @format - -name: Issue Workflow - -on: - issues: - types: - - opened - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v0.5.0 - with: - project-url: https://github.com/orgs/miniohq/projects/2 - github-token: ${{ secrets.BOT_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99fc4e561d..6ddddb69d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,16 +13,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: go.mod cache: true + - name: Verify clean checkout + run: | + set -euo pipefail + if [ -n "$(git status --porcelain)" ]; then + echo "Refusing to release from a dirty working tree:" >&2 + git status --porcelain >&2 + exit 1 + fi + - name: Compute release variables run: | set -euo pipefail @@ -47,7 +56,7 @@ jobs: echo "LDFLAGS: ${LDFLAGS}" - name: Build Draft release with GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: version: v2.17.1 args: release --clean --skip=validate --config .github/goreleaser.yml @@ -56,6 +65,11 @@ jobs: LDFLAGS: ${{ env.LDFLAGS }} PKG_VERSION: ${{ env.PKG_VERSION }} + - name: Verify binary provenance stamps + run: | + set -euo pipefail + buildscripts/verify-build-provenance.sh + - name: Install nFPM run: | set -euo pipefail @@ -82,7 +96,7 @@ jobs: - name: Upload dist artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist path: dist/ diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index 8f674ed9f6..77b86294ae 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -9,9 +9,13 @@ on: - "Dockerfile.goreleaser" - "buildscripts/package-release.sh" - "buildscripts/sign-release-rpms.sh" + - "buildscripts/verify-build-provenance.sh" + - "buildscripts/gen-ldflags.go" - ".github/workflows/docker-release.yml" - ".github/workflows/release.yml" - ".github/workflows/test-release.yml" + - ".gitignore" + - "Makefile" permissions: contents: read @@ -21,12 +25,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: go.mod cache: true @@ -47,13 +51,13 @@ jobs: echo "LDFLAGS: ${LDFLAGS}" - name: GoReleaser config check - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: version: v2.17.1 args: check --config .github/goreleaser.yml - name: Build snapshot artifacts - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: version: v2.17.1 args: release --snapshot --clean --skip=publish --config .github/goreleaser.yml @@ -61,13 +65,18 @@ jobs: LDFLAGS: ${{ env.LDFLAGS }} PKG_VERSION: ${{ env.PKG_VERSION }} + - 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 cpio rpm + sudo apt-get install --yes rpm binutils - name: Package snapshot binaries with nFPM run: | @@ -95,21 +104,45 @@ jobs: done test "$(find . -maxdepth 1 -type f \( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | wc -l)" -eq 6 - test "$(rpm -qp --queryformat '%{VENDOR}' "mcli-${PKG_VERSION}-1.x86_64.rpm")" = "PGSTY" - test "$(rpm -qp --queryformat '%{PACKAGER}' "mcli-${PKG_VERSION}-1.x86_64.rpm")" = "Ruohang Feng (@Vonng) " - test "$(rpm -qp --queryformat '%{URL}' "mcli-${PKG_VERSION}-1.x86_64.rpm")" = "https://silo.pgsty.com" - test "$(rpm -qp --queryformat '%{GROUP}' "mcli-${PKG_VERSION}-1.x86_64.rpm")" = "Applications/File" - rpm -qpl "mcli-${PKG_VERSION}-1.x86_64.rpm" | grep -Fx '/usr/local/bin/mcli' - - test "$(dpkg-deb --field "mcli_${PKG_VERSION}_amd64.deb" Maintainer)" = "Ruohang Feng (@Vonng) " - test "$(dpkg-deb --field "mcli_${PKG_VERSION}_amd64.deb" Version)" = "${PKG_VERSION}" - test "$(dpkg-deb --field "mcli_${PKG_VERSION}_amd64.deb" License)" = "AGPLv3" - test "$(dpkg-deb --field "mcli_${PKG_VERSION}_amd64.deb" Homepage)" = "https://silo.pgsty.com" - dpkg-deb --contents "mcli_${PKG_VERSION}_amd64.deb" | grep -E 'usr/local/bin/mcli$' + + # sign-release-rpms.sh refuses to sign an RPM whose metadata does not + # match these values, and it runs on a maintainer laptop *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. + 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 + + rpm_file="mcli-${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/local/bin/mcli' + + deb_file="mcli_${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/local/bin/mcli$' apk_info="$(tar -xOzf "mcli_${PKG_VERSION}_x86_64.apk" .PKGINFO)" grep -Fx "pkgver = ${PKG_VERSION}" <<< "${apk_info}" - grep -Fx 'url = https://silo.pgsty.com' <<< "${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 "mcli_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/local/bin/mcli' for arch in amd64 arm64; do @@ -123,9 +156,21 @@ jobs: apk_arch=aarch64 fi + test "$(rpm -qp --queryformat '%{ARCH}' "mcli-${PKG_VERSION}-1.${rpm_arch}.rpm")" = "${rpm_arch}" + test "$(dpkg-deb --field "mcli_${PKG_VERSION}_${deb_arch}.deb" Architecture)" = "${deb_arch}" + grep -Fx "arch = ${apk_arch}" <<< "$(tar -xOzf "mcli_${PKG_VERSION}_${apk_arch}.apk" .PKGINFO)" + source_binary="$(find .. -maxdepth 2 -type f -path "../mcli_linux_${arch}*/mcli" | head -n 1)" source_sha="$(sha256sum "${source_binary}" | awk '{print $1}')" - rpm_sha="$(rpm2cpio "mcli-${PKG_VERSION}-1.${rpm_arch}.rpm" | cpio --quiet -i --to-stdout /usr/local/bin/mcli | sha256sum | 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` and made + # this whole step unrunnable on ubuntu-latest. 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 "mcli-${PKG_VERSION}-1.${rpm_arch}.rpm" + rpm_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \ + "mcli-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/local/bin/mcli" { print $2 }')" deb_sha="$(ar p "mcli_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/local/bin/mcli | sha256sum | awk '{print $1}')" apk_sha="$(tar -xzOf "mcli_${PKG_VERSION}_${apk_arch}.apk" usr/local/bin/mcli | sha256sum | awk '{print $1}')" @@ -134,8 +179,28 @@ jobs: test "${source_sha}" = "${apk_sha}" done + # docker-release.yml is workflow_dispatch only, so Dockerfile.goreleaser is + # otherwise never built until a real publish. Assemble the same context it + # builds and exercise the image here, without pushing anything. + - name: Build release container image (no push) + run: | + set -euo pipefail + context="$(mktemp -d)" + tar -xzf "dist/mcli_${PKG_VERSION}_linux_amd64.tar.gz" -C "${context}" mcli + mv "${context}/mcli" "${context}/mc" + cp Dockerfile.goreleaser LICENSE CREDITS "${context}/" + docker build --tag mcli-release-test:snapshot \ + --file "${context}/Dockerfile.goreleaser" "${context}" + docker run --rm mcli-release-test:snapshot --version + docker image inspect mcli-release-test:snapshot \ + --format '{{ index .Config.Labels "org.opencontainers.image.vendor" }}' | grep -Fx PGSTY + - name: Validate release scripts run: | set -euo pipefail bash -n buildscripts/package-release.sh bash -n buildscripts/sign-release-rpms.sh + bash -n buildscripts/verify-build-provenance.sh + test -x buildscripts/package-release.sh + test -x buildscripts/sign-release-rpms.sh + test -x buildscripts/verify-build-provenance.sh diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml index 6e28109764..cd4bb3636f 100644 --- a/.github/workflows/vulncheck.yml +++ b/.github/workflows/vulncheck.yml @@ -6,15 +6,19 @@ on: push: branches: - master + +permissions: + contents: read + jobs: vulncheck: name: Analysis runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: 1.26.5 - name: Get official govulncheck diff --git a/.gitignore b/.gitignore index 4828118ce9..23966974c1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ mc.gz .vscode/ .bin/ .release-sign/ +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 3c25891a7c..0000000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,139 +0,0 @@ -version: 2 - -project_name: mc - -env: - - CGO_ENABLED=0 - - GO111MODULE=on - -before: - hooks: - - make clean - - go generate ./... - - go mod tidy - - go mod download - -builds: - - id: mc - main: . - binary: mc - goos: - - linux - - darwin - - windows - goarch: - - amd64 - - arm64 - goamd64: - - v1 - flags: - - -tags=kqueue - - -trimpath - ldflags: - - "{{ .Env.LDFLAGS }}" - -archives: - - id: mc - ids: - - mc - name_template: "mc_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}" - -nfpms: - - id: mcli - ids: - - mc - package_name: mcli - vendor: Pigsty - homepage: https://pigsty.io - maintainer: Pigsty Maintainers - description: MinIO Client community fork maintained by Pigsty. - license: AGPL-3.0 - bindir: /usr/bin - formats: - - deb - - rpm - - apk - contents: - - src: NOTICE - dst: /usr/share/mcli/NOTICE - - src: CREDITS - dst: /usr/share/mcli/CREDITS - - src: LICENSE - dst: /usr/share/mcli/LICENSE - - src: README.md - dst: /usr/share/mcli/README.md - -dockers: - - id: mc-amd64 - ids: - - mc - goos: linux - goarch: amd64 - dockerfile: Dockerfile.goreleaser - use: buildx - image_templates: - - "pgsty/mc:{{ .Tag }}-amd64" - - "pgsty/mc:latest-amd64" - build_flag_templates: - - "--platform=linux/amd64" - - "--label=org.opencontainers.image.version={{ .Tag }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - extra_files: - - LICENSE - - CREDITS - - - id: mc-arm64 - ids: - - mc - goos: linux - goarch: arm64 - dockerfile: Dockerfile.goreleaser - use: buildx - image_templates: - - "pgsty/mc:{{ .Tag }}-arm64" - - "pgsty/mc:latest-arm64" - build_flag_templates: - - "--platform=linux/arm64" - - "--label=org.opencontainers.image.version={{ .Tag }}" - - "--label=org.opencontainers.image.created={{ .Date }}" - - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - extra_files: - - LICENSE - - CREDITS - -docker_manifests: - - name_template: "pgsty/mc:{{ .Tag }}" - image_templates: - - "pgsty/mc:{{ .Tag }}-amd64" - - "pgsty/mc:{{ .Tag }}-arm64" - - name_template: "pgsty/mc:latest" - image_templates: - - "pgsty/mc:latest-amd64" - - "pgsty/mc:latest-arm64" - -checksum: - name_template: "mc_{{ .Env.PKG_VERSION }}_checksums.txt" - algorithm: sha256 - -release: - github: - owner: pgsty - name: mc - draft: false - prerelease: false - mode: replace - replace_existing_artifacts: true - name_template: "{{ .Tag }}" - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" - - "Merge pull request" - - "Merge branch" - -announce: - skip: true diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser index 2b1014d64f..55998d2962 100644 --- a/Dockerfile.goreleaser +++ b/Dockerfile.goreleaser @@ -13,8 +13,9 @@ LABEL maintainer="Ruohang Feng (@Vonng) " \ org.opencontainers.image.vendor="PGSTY" \ org.opencontainers.image.url="https://silo.pgsty.com" \ org.opencontainers.image.source="https://github.com/pgsty/mc" \ - org.opencontainers.image.description="S3-Interface Libre Object Storage - A MinIO fork maintained by the Pigsty community." \ - description="S3-Interface Libre Object Storage - A MinIO fork maintained by the Pigsty community." + org.opencontainers.image.description="S3-Interface Libre Object Storage, Community-maintained MinIO client fork." \ + org.opencontainers.image.licenses="AGPL-3.0-or-later" \ + description="S3-Interface Libre Object Storage, Community-maintained MinIO client fork." COPY --from=certs /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY mc /usr/bin/mc diff --git a/buildscripts/package-release.sh b/buildscripts/package-release.sh index 58e64c967f..0f9a48522f 100755 --- a/buildscripts/package-release.sh +++ b/buildscripts/package-release.sh @@ -45,16 +45,27 @@ sha256_file() { find_binary() { local goarch="$1" - local binary + local matches + local count - binary="$(find "${dist_dir}" -maxdepth 2 -type f \ - -path "${dist_dir}/mcli_linux_${goarch}*/mcli" | sort | head -n 1)" - if [ -z "${binary}" ]; then + # Must resolve to exactly one binary. Picking the first of several build + # variants (extra goamd64/goarm64 levels, stale dist entries) would silently + # ship a package whose contents do not match its name. + matches="$(find "${dist_dir}" -maxdepth 2 -type f \ + -path "${dist_dir}/mcli_linux_${goarch}*/mcli" | sort)" + count="$(printf '%s' "${matches}" | grep -c . || true)" + + if [ "${count}" -eq 0 ]; then echo "Missing GoReleaser binary for linux/${goarch}" >&2 exit 1 fi + if [ "${count}" -ne 1 ]; then + echo "Expected exactly one GoReleaser binary for linux/${goarch}, found ${count}:" >&2 + printf '%s\n' "${matches}" >&2 + exit 1 + fi - printf '%s\n' "${binary}" + printf '%s\n' "${matches}" } build_arch() { diff --git a/buildscripts/sign-release-rpms.sh b/buildscripts/sign-release-rpms.sh index 6dd27bfbd4..2e8401210b 100755 --- a/buildscripts/sign-release-rpms.sh +++ b/buildscripts/sign-release-rpms.sh @@ -6,8 +6,10 @@ expected_fingerprint="9592A7BC7A682E7333376E09E7935D8DB9BD8B20" expected_vendor="PGSTY" expected_packager="Ruohang Feng (@Vonng) " expected_url="https://silo.pgsty.com" -expected_summary="S3-Interface Libre Object Storage" -expected_description=$'S3-Interface Libre Object Storage\nA MinIO fork maintained by the Pigsty community.' +expected_summary="S3-Interface Libre Object Storage, Community-maintained MinIO client fork." +expected_description="S3-Interface Libre Object Storage, Community-maintained MinIO client fork." +expected_license="AGPL-3.0-or-later" +expected_group="Applications/File" repository="${GH_REPO:-pgsty/mc}" container="${DNFUPDATE_CONTAINER:-dnfupdate}" upload=false @@ -202,8 +204,8 @@ for rpm_file in "${rpm_files[@]}"; do 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 AGPLv3 - assert_rpm_tag "${container_rpm}" GROUP Applications/File + 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}" diff --git a/buildscripts/verify-build-provenance.sh b/buildscripts/verify-build-provenance.sh new file mode 100755 index 0000000000..d9568476b6 --- /dev/null +++ b/buildscripts/verify-build-provenance.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Asserts that every binary GoReleaser produced is stamped by the Go toolchain +# as built from this exact commit with a clean working tree. A stray untracked +# file (for example an un-ignored dist/) silently turns every release binary +# into a "+dirty" pseudo-version, which destroys the link between a published +# artifact and its tag. Catch that here instead of after publishing. + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_dir="$(cd "${script_dir}/.." && pwd)" +dist_dir="${DIST_DIR:-${repo_dir}/dist}" +expected_count="${EXPECTED_BINARY_COUNT:-6}" + +if ! command -v go >/dev/null 2>&1; then + echo "go is required" >&2 + exit 1 +fi + +if [ ! -d "${dist_dir}" ]; then + echo "Missing GoReleaser dist directory: ${dist_dir}" >&2 + exit 1 +fi + +revision="$(git -C "${repo_dir}" rev-parse HEAD)" + +count=0 +while IFS= read -r binary; do + count=$((count + 1)) + info="$(go version -m "${binary}")" + + if ! grep -qF "vcs.revision=${revision}" <<< "${info}"; then + echo "Unexpected vcs.revision in ${binary} (expected ${revision})" >&2 + grep -F 'vcs.' <<< "${info}" >&2 || true + exit 1 + fi + + if ! grep -qF 'vcs.modified=false' <<< "${info}"; then + echo "Binary was built from a dirty working tree: ${binary}" >&2 + grep -F 'vcs.' <<< "${info}" >&2 || true + exit 1 + fi +done < <(find "${dist_dir}" -maxdepth 2 -type f \( -name 'mcli' -o -name 'mcli.exe' \) | sort) + +if [ "${count}" -ne "${expected_count}" ]; then + echo "Expected ${expected_count} release binaries, found ${count}" >&2 + exit 1 +fi + +echo "Verified ${count} binaries built from ${revision} with a clean tree"