1#!/usr/bin/env bash 2# OWNER: make a release of the plugin, and publish it. 3# 4# tools/release.sh build the files, into dist/<version>/ (needs no key and no account) 5# tools/release.sh sign the signed manifest and the signed list of releases 6# tools/release.sh publish a GitHub release holding all of it 7# tools/release.sh refresh sign the list of releases again and replace it on the latest release 8# 9# A release is the plugin folder with the daemon already built in it, once for each Linux 10# architecture, so that installing needs no Rust: 11# 12# jevhooks-<version>-x86_64-linux.tar.gz 13# jevhooks-<version>-aarch64-linux.tar.gz 14# SHA256SUMS for someone who downloads by hand 15# packslip.sigstore.json the signed manifest: each file, its platform and its digest 16# packslip.json the signed list of releases, which mise looks for first 17# 18# The daemon is linked statically against musl (cargo-zigbuild), so one file runs on any Linux of 19# its architecture. The version is the workspace's, in Cargo.toml; the tag is `v<version>`. 20# 21# Signing needs JEVHOOKS_PACKSLIP_KEY, the secret key `packslip keygen` wrote (tools/release.pub is 22# its public half). It is signed with --no-log: nothing is sent to the public transparency log, 23# which is why whoever installs must say `allow_unlogged = true`. The repository is private on 24# GitHub and its release files reach everyone through code.lmjtfy.fun, which is the name signed. 25set -euo pipefail 26cd "$(dirname "$0")/.." 27 28PROJECT="code.lmjtfy.fun/jevhooks" 29REPO="deizel/jevhooks" 30# A year: the list must be signed again before this runs out (`refresh`), or installs stop. 31VALID_FOR="52w" 32ARCHES="x86_64 aarch64" 33 34version="$(sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml)" 35[ -n "$version" ] || { echo "release: no version in Cargo.toml's [workspace.package]" >&2; exit 1; } 36tag="v$version" 37out="dist/$version" 38base="https://code.lmjtfy.fun/jevhooks/releases" 39 40files() { for arch in $ARCHES; do echo "$out/jevhooks-$version-$arch-linux.tar.gz"; done; } 41 42build() { 43 # A release is of a commit: what is in the archive is what `git archive` gives, plus the daemon. 44 if ! git diff --quiet HEAD; then 45 echo "release: the tree has uncommitted changes; a release is made from a commit" >&2 46 exit 1 47 fi 48 rm -rf "$out" 49 mkdir -p "$out" 50 local targets=() 51 for arch in $ARCHES; do targets+=(--target "$arch-unknown-linux-musl"); done 52 cargo zigbuild --release --locked -p jevhooks-daemon "${targets[@]}" 53 # The commit's time on every file, so the same commit makes the same bytes. 54 local when 55 when="$(git log -1 --format=%cI)" 56 for arch in $ARCHES; do 57 local stage 58 stage="$(mktemp -d)" 59 git archive --format=tar --prefix=jevhooks/ HEAD:plugin | tar -x -C "$stage" 60 mkdir -p "$stage/jevhooks/bin" 61 install -m 0755 "target/$arch-unknown-linux-musl/release/jevhooks" "$stage/jevhooks/bin/jevhooks" 62 tar --sort=name --mtime="$when" --owner=0 --group=0 --numeric-owner -C "$stage" -cf - jevhooks \ 63 | gzip -n -9 > "$out/jevhooks-$version-$arch-linux.tar.gz" 64 rm -rf "$stage" 65 done 66 (cd "$out" && sha256sum jevhooks-*.tar.gz > SHA256SUMS) 67 echo "release: built $out" 68 ls -l "$out" 69} 70 71# Runs `packslip` with the secret key in a file only it can read, removed afterwards. 72signed() { 73 [ -n "${JEVHOOKS_PACKSLIP_KEY:-}" ] || { echo "release: JEVHOOKS_PACKSLIP_KEY is not set (fnox.toml declares it)" >&2; exit 1; } 74 local key status=0 75 key="$(umask 077 && mktemp)" 76 printf '%s\n' "$JEVHOOKS_PACKSLIP_KEY" > "$key" 77 packslip "$@" --key "$key" --no-log || status=$? 78 rm -f "$key" 79 return "$status" 80} 81 82# The list of every release, this one the latest. Each earlier release's manifest is fetched from 83# its GitHub release, because the list carries every manifest's digest. 84list() { 85 local releases=(--release "$base/$tag/packslip.sigstore.json=$out/packslip.sigstore.json") 86 local earlier 87 for earlier in $(gh release list -R "$REPO" --json tagName -q '.[].tagName'); do 88 [ "$earlier" = "$tag" ] && continue 89 mkdir -p "dist/earlier/$earlier" 90 gh release download "$earlier" -R "$REPO" -p packslip.sigstore.json -D "dist/earlier/$earlier" --clobber 91 releases+=(--release "$base/$earlier/packslip.sigstore.json=dist/earlier/$earlier/packslip.sigstore.json") 92 done 93 # The time as the sequence: it only has to rise, and then no counter is kept anywhere. 94 signed releases --project "$PROJECT" --sequence "$(date +%s)" --valid-for "$VALID_FOR" --latest "$version" \ 95 "${releases[@]}" --out "$out/packslip.json" 96} 97 98sign() { 99 local file 100 for file in $(files); do [ -f "$file" ] || { echo "release: $file is not built (tools/release.sh build)" >&2; exit 1; }; done 101 # shellcheck disable=SC2046 102 signed create --project "$PROJECT" --version "$version" --url-base "$base/$tag" \ 103 --bin jevhooks=jevhooks/bin/jevhooks --out "$out" $(files) 104 packslip verify "$out/packslip.sigstore.json" --pubkey tools/release.pub --allow-unlogged --artifact "$(files | head -1)" 105 list 106 echo "release: signed $out" 107} 108 109publish() { 110 local file 111 for file in $(files) "$out/SHA256SUMS" "$out/packslip.sigstore.json" "$out/packslip.json"; do 112 [ -f "$file" ] || { echo "release: $file is missing (build, then sign)" >&2; exit 1; } 113 done 114 # shellcheck disable=SC2046 115 gh release create "$tag" -R "$REPO" --title "jevhooks $version" \ 116 --notes "The plugin with the daemon built in, for Linux on x86_64 and arm64. How to install: https://hooks.lmjtfy.fun/" \ 117 $(files) "$out/SHA256SUMS" "$out/packslip.sigstore.json" "$out/packslip.json" 118} 119 120refresh() { 121 latest="$(gh release view -R "$REPO" --json tagName -q .tagName)" 122 [ "$latest" = "$tag" ] || { echo "release: the latest release is $latest and this tree is $tag; refresh from the latest release's commit" >&2; exit 1; } 123 mkdir -p "$out" 124 gh release download "$tag" -R "$REPO" -p packslip.sigstore.json -D "$out" --clobber 125 list 126 gh release upload "$tag" -R "$REPO" "$out/packslip.json" --clobber 127} 128 129case "${1:-}" in 130 build) build ;; 131 sign) sign ;; 132 publish) publish ;; 133 refresh) refresh ;; 134 *) sed -n '2,7p' "$0" >&2; exit 2 ;; 135esac