| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | usage() { |
| 5 | echo "usage: $0 RELEASE_ASSETS_DIR OUTPUT_DIR [PKGREL]" >&2 |
| 6 | } |
| 7 | |
| 8 | if [[ $# -lt 2 || $# -gt 3 ]]; then |
| 9 | usage |
| 10 | exit 2 |
| 11 | fi |
| 12 | |
| 13 | assets_dir="$1" |
| 14 | output_dir="$2" |
| 15 | pkgrel="${3:-1}" |
| 16 | repo_root="$(cd "$(dirname "$0")/../.." && pwd)" |
| 17 | |
| 18 | if [[ ! -d "${assets_dir}" ]]; then |
| 19 | echo "release assets directory does not exist: ${assets_dir}" >&2 |
| 20 | exit 1 |
| 21 | fi |
| 22 | assets_dir="$(cd "${assets_dir}" && pwd)" |
| 23 | |
| 24 | if [[ -e "${output_dir}" && ! -d "${output_dir}" ]]; then |
| 25 | echo "output path is not a directory: ${output_dir}" >&2 |
| 26 | exit 1 |
| 27 | fi |
| 28 | mkdir -p "${output_dir}" |
| 29 | if [[ -n "$(find "${output_dir}" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then |
| 30 | echo "output directory must be empty: ${output_dir}" >&2 |
| 31 | exit 1 |
| 32 | fi |
| 33 | output_dir="$(cd "${output_dir}" && pwd)" |
| 34 | |
| 35 | workspace_version="$( |
| 36 | grep -E '^version = "' "${repo_root}/Cargo.toml" \ |
| 37 | | head -n 1 \ |
| 38 | | sed -E 's/^version = "([^"]+)".*/\1/' |
| 39 | )" |
| 40 | if [[ ! "${workspace_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then |
| 41 | echo "workspace version must be X.Y.Z, got: ${workspace_version:-<missing>}" >&2 |
| 42 | exit 1 |
| 43 | fi |
| 44 | if [[ ! "${pkgrel}" =~ ^[1-9][0-9]*(\.[1-9][0-9]*)?$ ]]; then |
| 45 | echo "PKGREL must be a positive integer or positive x.y value, got: ${pkgrel}" >&2 |
| 46 | exit 2 |
| 47 | fi |
| 48 | |
| 49 | artifact_manifest="${assets_dir}/codewhale-artifacts-sha256.txt" |
| 50 | bundle_manifest="${assets_dir}/codewhale-bundles-sha256.txt" |
| 51 | for manifest in "${artifact_manifest}" "${bundle_manifest}"; do |
| 52 | if [[ ! -f "${manifest}" ]]; then |
| 53 | echo "release assets are missing checksum manifest: ${manifest}" >&2 |
| 54 | exit 1 |
| 55 | fi |
| 56 | done |
| 57 | |
| 58 | sha256_file() { |
| 59 | local path="$1" |
| 60 | if command -v sha256sum >/dev/null 2>&1; then |
| 61 | sha256sum "${path}" | awk '{print $1}' |
| 62 | elif command -v shasum >/dev/null 2>&1; then |
| 63 | shasum -a 256 "${path}" | awk '{print $1}' |
| 64 | else |
| 65 | echo "sha256sum or shasum is required" >&2 |
| 66 | return 1 |
| 67 | fi |
| 68 | } |
| 69 | |
| 70 | manifest_sha() { |
| 71 | local manifest="$1" |
| 72 | local asset="$2" |
| 73 | local matches match_count checksum |
| 74 | matches="$(awk -v asset="${asset}" '$2 == asset { print $1 }' "${manifest}")" |
| 75 | match_count="$(printf '%s\n' "${matches}" | awk 'NF { count++ } END { print count + 0 }')" |
| 76 | if [[ "${match_count}" -ne 1 ]]; then |
| 77 | echo "$(basename "${manifest}") must contain exactly one checksum for ${asset}" >&2 |
| 78 | return 1 |
| 79 | fi |
| 80 | checksum="$(printf '%s\n' "${matches}" | awk 'NF { print; exit }')" |
| 81 | if [[ ! "${checksum}" =~ ^[0-9a-fA-F]{64}$ ]]; then |
| 82 | echo "invalid checksum for ${asset} in $(basename "${manifest}"): ${checksum}" >&2 |
| 83 | return 1 |
| 84 | fi |
| 85 | printf '%s' "${checksum}" | tr 'A-F' 'a-f' |
| 86 | } |
| 87 | |
| 88 | verified_archive_sha() { |
| 89 | local asset="$1" |
| 90 | local archive="${assets_dir}/${asset}" |
| 91 | if [[ ! -f "${archive}" ]]; then |
| 92 | echo "release assets are missing ${asset}" >&2 |
| 93 | return 1 |
| 94 | fi |
| 95 | |
| 96 | local artifact_sha bundle_sha actual_sha |
| 97 | artifact_sha="$(manifest_sha "${artifact_manifest}" "${asset}")" |
| 98 | bundle_sha="$(manifest_sha "${bundle_manifest}" "${asset}")" |
| 99 | if [[ "${artifact_sha}" != "${bundle_sha}" ]]; then |
| 100 | echo "checksum manifests disagree for ${asset}" >&2 |
| 101 | return 1 |
| 102 | fi |
| 103 | actual_sha="$(sha256_file "${archive}")" |
| 104 | if [[ "${artifact_sha}" != "${actual_sha}" ]]; then |
| 105 | echo "release asset checksum mismatch for ${asset}" >&2 |
| 106 | return 1 |
| 107 | fi |
| 108 | |
| 109 | local release_arch="${asset#codewhale-linux-}" |
| 110 | release_arch="${release_arch%.tar.gz}" |
| 111 | local listing |
| 112 | listing="$(tar -tzf "${archive}")" |
| 113 | for entry in \ |
| 114 | "codewhale-linux-${release_arch}/codewhale" \ |
| 115 | "codewhale-linux-${release_arch}/codew"; do |
| 116 | if ! grep -Fqx "${entry}" <<<"${listing}"; then |
| 117 | echo "${asset} is missing required archive entry: ${entry}" >&2 |
| 118 | return 1 |
| 119 | fi |
| 120 | done |
| 121 | |
| 122 | printf '%s' "${actual_sha}" |
| 123 | } |
| 124 | |
| 125 | x86_64_sha="$(verified_archive_sha 'codewhale-linux-x64.tar.gz')" |
| 126 | aarch64_sha="$(verified_archive_sha 'codewhale-linux-arm64.tar.gz')" |
| 127 | license_sha="$(sha256_file "${repo_root}/LICENSE")" |
| 128 | |
| 129 | render_template() { |
| 130 | local source="$1" |
| 131 | local destination="$2" |
| 132 | local content |
| 133 | content="$(<"${source}")" |
| 134 | content="${content//@PKGVER@/${workspace_version}}" |
| 135 | content="${content//@PKGREL@/${pkgrel}}" |
| 136 | content="${content//@LICENSE_SHA256@/${license_sha}}" |
| 137 | content="${content//@X86_64_SHA256@/${x86_64_sha}}" |
| 138 | content="${content//@AARCH64_SHA256@/${aarch64_sha}}" |
| 139 | printf '%s\n' "${content}" > "${destination}" |
| 140 | } |
| 141 | |
| 142 | render_template "${repo_root}/packaging/aur/PKGBUILD.template" "${output_dir}/PKGBUILD" |
| 143 | render_template "${repo_root}/packaging/aur/SRCINFO.template" "${output_dir}/.SRCINFO" |
| 144 | |
| 145 | for rendered in "${output_dir}/PKGBUILD" "${output_dir}/.SRCINFO"; do |
| 146 | if grep -Eq '@(PKGVER|PKGREL|LICENSE_SHA256|X86_64_SHA256|AARCH64_SHA256)@|SKIP' "${rendered}"; then |
| 147 | echo "rendered AUR metadata contains an unresolved or insecure checksum token: ${rendered}" >&2 |
| 148 | exit 1 |
| 149 | fi |
| 150 | done |
| 151 | bash -n "${output_dir}/PKGBUILD" |
| 152 | |
| 153 | if command -v makepkg >/dev/null 2>&1; then |
| 154 | generated_srcinfo="$(mktemp)" |
| 155 | trap 'rm -f "${generated_srcinfo}"' EXIT |
| 156 | (cd "${output_dir}" && makepkg --printsrcinfo) > "${generated_srcinfo}" |
| 157 | if ! cmp -s "${generated_srcinfo}" "${output_dir}/.SRCINFO"; then |
| 158 | echo "rendered .SRCINFO does not match makepkg --printsrcinfo" >&2 |
| 159 | diff -u "${output_dir}/.SRCINFO" "${generated_srcinfo}" >&2 || true |
| 160 | exit 1 |
| 161 | fi |
| 162 | fi |
| 163 | |
| 164 | echo "Rendered codewhale-bin ${workspace_version}-${pkgrel} from verified release archives:" |
| 165 | echo " ${output_dir}/PKGBUILD" |
| 166 | echo " ${output_dir}/.SRCINFO" |
| 167 |