diff --git a/.hermetic/aqua-checksums.json b/.hermetic/aqua-checksums.json index 2a0fa5e..b76a240 100644 --- a/.hermetic/aqua-checksums.json +++ b/.hermetic/aqua-checksums.json @@ -66,28 +66,28 @@ "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.24.0/limen_0.24.0_darwin_arm64.tar.gz", - "checksum": "BB7F8386488E585FE59F871CD3D5A4F74E8DA17B51118A11FE6FABA1C91E6FEB", + "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_darwin_arm64.tar.gz", + "checksum": "7D2179EEEA49466B7B8EF834C94AF27273CBA1886E798E36894B17A4DD81EAEF", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.24.0/limen_0.24.0_linux_amd64.tar.gz", - "checksum": "8FD7C868E5C26E3E04C2F0F83D0904D271A9D7FD596E8D4303DC9689B8869209", + "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_linux_amd64.tar.gz", + "checksum": "0739BC4C12D7754CB95DA24F4F6143F0DABCB498047A26AB96BD1F619C7F2D05", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.24.0/limen_0.24.0_linux_arm64.tar.gz", - "checksum": "BDC58258E2B6280AFEF8AD7D8965D19B4AC3AB0C741C9FDC126403847F279E04", + "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_linux_arm64.tar.gz", + "checksum": "DD719BB5BCB231B42EA26172B90F91432AE2292EC88EA31752ECE1428A223242", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.24.0/limen_0.24.0_windows_amd64.tar.gz", - "checksum": "9B862D79FCBB33676F2304FDA902A39F7623BB543EB43693F1F8015B2531730B", + "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_windows_amd64.tar.gz", + "checksum": "826533D0573A75F61193F8C41B2156267BD9C2CBF709F3B9C0EC00BF24218675", "algorithm": "sha256" }, { - "id": "github_release/github.com/farcloser/limen/v0.24.0/limen_0.24.0_windows_arm64.tar.gz", - "checksum": "05F751B5F5DB7FDB8544ACC055D9330A6CFDA64897FE31339B210A64C5C1CC49", + "id": "github_release/github.com/farcloser/limen/v0.25.0/limen_0.25.0_windows_arm64.tar.gz", + "checksum": "0610256371832BF1616633F2A50787632CA35E8DBEED479E88B2FC17329DE100", "algorithm": "sha256" }, { diff --git a/.hermetic/hermetic.yaml b/.hermetic/hermetic.yaml index f52d9e6..8277fde 100644 --- a/.hermetic/hermetic.yaml +++ b/.hermetic/hermetic.yaml @@ -19,7 +19,7 @@ registries: packages: # The limen this repository runs, and with it every canonical file it # writes: the one canonical pin kept here, where Renovate bumps it. - - name: farcloser/limen@v0.24.0 # renovate: depName=farcloser/limen + - name: farcloser/limen@v0.25.0 # renovate: depName=farcloser/limen registry: local # The Go toolchain is the repository's own pin, seeded from limen's own # manifest once and Renovate's here afterwards: a standard-library fix diff --git a/.limen/just/apple.just b/.limen/just/apple.just index c948041..1188c47 100644 --- a/.limen/just/apple.just +++ b/.limen/just/apple.just @@ -63,8 +63,8 @@ apple-verify-notarized bundle: echo "apple-verify-notarized: $bundle carries a valid stapled ticket" # Submit the signed bundle to Apple and staple the ticket. The verdict is -# notarytool's own "status: Accepted": --wait's exit status is not it. Without -# an identity (a local build) there is nothing to notarize and the recipe says +# notarytool's own "status: Accepted", never an exit status. Without an +# identity (a local build) there is nothing to notarize and the recipe says # so; with one and no credentials it fails, since a Developer ID build that # leaves here unnotarized would be refused on every install. [doc('Notarize a signed bundle with notarytool and staple the ticket')] @@ -84,17 +84,46 @@ apple-notarize bundle: rm -f "$archive" ditto -c -k --sequesterRsrc --keepParent "$bundle" "$archive" key=(--key "${APPLE_NOTARY_KEY:?}" --key-id "${APPLE_NOTARY_KEY_ID:?}" --issuer "${APPLE_NOTARY_ISSUER_ID:?}") - submission=$(xcrun notarytool submit "$archive" --wait "${key[@]}" 2>&1 | tee /dev/stderr) || true + # Submit, wait and the verdict are separate calls, each retried on its + # own: a connection that drops during the wait loses the verdict on an + # upload Apple already holds. Only a submit that exits 0 has finished its + # upload, so a failed one is submitted again; once one has, its id is + # waited on and asked about, never submitted again. + for attempt in 1 2 3; do + submission=$(xcrun notarytool submit "$archive" --no-wait "${key[@]}" 2>&1 | tee /dev/stderr) && break + [ "$attempt" -lt 3 ] || { echo "apple-notarize: $archive could not be submitted" >&2; exit 1; } + sleep $((attempt * 15)) + done id=$(printf '%s\n' "$submission" | sed -n 's/^ *id: \([0-9a-f-]*\)$/\1/p' | head -n 1) - if ! printf '%s\n' "$submission" | grep -q '^ *status: Accepted$'; then - echo "apple-notarize: Apple did not accept the submission${id:+ $id}" >&2 - [ -z "$id" ] || xcrun notarytool log "$id" "${key[@]}" >&2 || true + [ -n "$id" ] || { echo "apple-notarize: notarytool submit returned no submission id" >&2; exit 1; } + # A "status:" line is the end of processing, whatever wait exits with; + # anything else is a wait cut short, resumed on the same id. + for attempt in 1 2 3 4 5; do + waited=$(xcrun notarytool wait "$id" "${key[@]}" 2>&1 | tee /dev/stderr) && break + printf '%s\n' "$waited" | grep -q '^ *status: ' && break + [ "$attempt" -lt 5 ] || { echo "apple-notarize: submission $id did not finish processing" >&2; exit 1; } + sleep $((attempt * 15)) + done + for attempt in 1 2 3; do + info=$(xcrun notarytool info "$id" "${key[@]}" 2>&1 | tee /dev/stderr) && break + [ "$attempt" -lt 3 ] || { echo "apple-notarize: the status of submission $id could not be read" >&2; exit 1; } + sleep $((attempt * 10)) + done + if ! printf '%s\n' "$info" | grep -q '^ *status: Accepted$'; then + echo "apple-notarize: Apple did not accept the submission $id" >&2 + xcrun notarytool log "$id" "${key[@]}" >&2 || true exit 1 fi - # staple validates what it writes; a second validate only asks the - # network again. - xcrun stapler staple "$bundle" - echo "apple-notarize: $bundle notarized (submission ${id:-?}), ticket stapled" + # staple fetches the ticket from Apple's ticket service and validates + # what it writes: a connection that drops on the runner fails it with the + # submission accepted, so it is tried five times, further apart each time, + # before the release stops. + for attempt in 1 2 3 4 5; do + xcrun stapler staple "$bundle" && break + [ "$attempt" -lt 5 ] || { echo "apple-notarize: the ticket for submission $id could not be stapled" >&2; exit 1; } + sleep $((attempt * 15)) + done + echo "apple-notarize: $bundle notarized (submission $id), ticket stapled" # ditto, not zip: the archiver that keeps a bundle's extended attributes and # code signature intact, so what a user unzips is what was signed. diff --git a/.limen/just/release.just b/.limen/just/release.just index ddc5db6..f8f68e1 100644 --- a/.limen/just/release.just +++ b/.limen/just/release.just @@ -218,3 +218,24 @@ release *args: fi ;; esac + +# goreleaser's sign command (.release-go.yaml `signs:`): one keyless +# signature over a release file, in CI. cosign gives up on Fulcio at the first +# dropped connection and retries Rekor once, so a reset on the runner fails +# the release with nothing wrong in it. Every attempt is a fresh key, a fresh +# certificate and a new log entry, so one that failed halfway cannot collide +# with the next; its partial bundle is removed first. +[doc("Sign a release file keyless with cosign into a Sigstore bundle (goreleaser's sign command)")] +[positional-arguments] +release-sign file bundle: + #!/usr/bin/env bash + set -euo pipefail + file="$1" bundle="$2" + for attempt in 1 2 3; do + rm -f "$bundle" + if cosign sign-blob --yes --bundle "$bundle" "$file" && [ -s "$bundle" ]; then + exit 0 + fi + [ "$attempt" -lt 3 ] || { echo "release-sign: $file could not be signed" >&2; exit 1; } + sleep $((attempt * 15)) + done diff --git a/.release-go.yaml b/.release-go.yaml index 0f70919..8217168 100644 --- a/.release-go.yaml +++ b/.release-go.yaml @@ -52,10 +52,14 @@ checksum: # ${COSIGN_KEY:-} survives for the shell to branch on. One line, not a block # scalar: yamlfmt corrupts folded scalars. signs: - - cmd: cosign + - cmd: just artifacts: checksum signature: "${artifact}.sigstore.json" - args: [sign-blob, --yes, --bundle, "${signature}", "${artifact}"] + args: + - do + - release-sign + - "${artifact}" + - "${signature}" release: # A prerelease-suffixed tag is published as a GitHub prerelease, never the