The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally. Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
238 lines
9.8 KiB
YAML
238 lines
9.8 KiB
YAML
name: promote-release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: Signed internal Gitea alpha tag to promote, such as v0.2.9.4
|
|
required: true
|
|
type: string
|
|
receipt_sha256:
|
|
description: SHA-256 of the tested internal files.sha256 receipt
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
promote:
|
|
runs-on: release-trusted
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Verify selected internal release tag
|
|
id: release
|
|
env:
|
|
PROMOTE_TAG: ${{ github.event.inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
TAG=$PROMOTE_TAG
|
|
[[ "$TAG" =~ ^v[0-9]+(\.[0-9]+){2,3}([.-][0-9A-Za-z]+)*$ ]]
|
|
[[ "$TAG" != */* ]]
|
|
git check-ref-format "refs/tags/$TAG"
|
|
|
|
TOKEN_FILE=/srv/release-trusted/.secrets/gitea-redflag-release-token
|
|
test -r "$TOKEN_FILE"
|
|
GIT_ASKPASS="$PWD/scripts/git-askpass-token.sh" \
|
|
GIT_TOKEN_FILE="$TOKEN_FILE" \
|
|
GIT_TOKEN_USERNAME=release-redflag \
|
|
GIT_TERMINAL_PROMPT=0 \
|
|
git fetch --force origin "refs/tags/$TAG:refs/tags/$TAG"
|
|
|
|
git config gpg.format ssh
|
|
git config gpg.ssh.allowedSignersFile .gitea/allowed_signers
|
|
test "$(git cat-file -t "refs/tags/$TAG")" = tag
|
|
git tag -v "$TAG"
|
|
|
|
SOURCE_SHA=$(git rev-parse "${TAG}^{commit}")
|
|
VERSION=${TAG#v}
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "source_sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Fetch accepted internal Gitea release bytes
|
|
env:
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
VERSION: ${{ steps.release.outputs.version }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
ACCEPTED_RECEIPT_SHA256: ${{ github.event.inputs.receipt_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$ACCEPTED_RECEIPT_SHA256" =~ ^[0-9a-f]{64}$ ]]
|
|
command -v jq >/dev/null
|
|
|
|
API="${GITHUB_SERVER_URL}/api/v1"
|
|
TOKEN_FILE=/srv/release-trusted/.secrets/gitea-redflag-release-token
|
|
test -r "$TOKEN_FILE"
|
|
GITEA_RELEASE_TOKEN=$(<"$TOKEN_FILE")
|
|
test -n "$GITEA_RELEASE_TOKEN"
|
|
|
|
gitea_curl() {
|
|
curl --config <(printf 'silent\nshow-error\nheader = "Authorization: token %s"\n' "$GITEA_RELEASE_TOKEN") "$@"
|
|
}
|
|
|
|
gitea_curl -f "$API/repos/${GITHUB_REPOSITORY}/releases/tags/$TAG" \
|
|
>/tmp/gitea-release.json
|
|
test "$(jq -r .tag_name /tmp/gitea-release.json)" = "$TAG"
|
|
test "$(jq -r .draft /tmp/gitea-release.json)" = false
|
|
RELEASE_ID=$(jq -er .id /tmp/gitea-release.json)
|
|
gitea_curl -f \
|
|
"$API/repos/${GITHUB_REPOSITORY}/releases/$RELEASE_ID/assets?limit=100" \
|
|
>/tmp/gitea-assets.json
|
|
|
|
asset_url() {
|
|
jq -er --arg name "$1" \
|
|
'[.[] | select(.name == $name)] | if length == 1 then .[0].browser_download_url else error("asset count is not one") end' \
|
|
/tmp/gitea-assets.json
|
|
}
|
|
|
|
rm -rf artifacts/internal
|
|
mkdir -p artifacts/internal
|
|
gitea_curl -fL -o artifacts/internal/files.sha256 \
|
|
"$(asset_url files.sha256)"
|
|
RECEIPT_SHA256=$(sha256sum artifacts/internal/files.sha256 | awk '{print $1}')
|
|
test "$RECEIPT_SHA256" = "$ACCEPTED_RECEIPT_SHA256"
|
|
|
|
: >/tmp/expected-release-assets
|
|
echo files.sha256 >>/tmp/expected-release-assets
|
|
while read -r expected name extra; do
|
|
test -z "${extra:-}"
|
|
[[ "$expected" =~ ^[0-9a-f]{64}$ ]]
|
|
case "$name" in
|
|
''|/*|*'..'*|*/*)
|
|
echo "::error::unsafe internal release filename: $name"
|
|
exit 1
|
|
;;
|
|
esac
|
|
echo "$name" >>/tmp/expected-release-assets
|
|
gitea_curl -fL -o "artifacts/internal/$name" "$(asset_url "$name")"
|
|
printf '%s %s\n' "$expected" "$name" |
|
|
(cd artifacts/internal && sha256sum -c -)
|
|
done <artifacts/internal/files.sha256
|
|
|
|
LC_ALL=C sort /tmp/expected-release-assets >/tmp/expected-release-assets.sorted
|
|
test "$(uniq -d /tmp/expected-release-assets.sorted | wc -l)" -eq 0
|
|
jq -r '.[].name' /tmp/gitea-assets.json | LC_ALL=C sort \
|
|
>/tmp/actual-release-assets.sorted
|
|
diff -u /tmp/expected-release-assets.sorted /tmp/actual-release-assets.sorted
|
|
|
|
python3 scripts/release-contract.py verify artifacts/internal "$VERSION" "$SOURCE_SHA" "$TAG"
|
|
echo "Accepted internal alpha $TAG receipt $RECEIPT_SHA256"
|
|
|
|
- name: Promote the same bytes to Forgejo
|
|
env:
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
VERSION: ${{ steps.release.outputs.version }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
ACCEPTED_RECEIPT_SHA256: ${{ github.event.inputs.receipt_sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
FORGE_URL='https://forge.caseytunturi.com/Fimeg/RedFlag.git'
|
|
FORGE_API='https://forge.caseytunturi.com/api/v1/repos/Fimeg/RedFlag'
|
|
TOKEN_FILE=/srv/release-trusted/.secrets/forge-redflag-release-token
|
|
test -r "$TOKEN_FILE"
|
|
PUBLIC_FORGE_TOKEN=$(<"$TOKEN_FILE")
|
|
test -n "$PUBLIC_FORGE_TOKEN"
|
|
|
|
forge_curl() {
|
|
curl --config <(printf 'silent\nshow-error\nheader = "Authorization: token %s"\n' "$PUBLIC_FORGE_TOKEN") "$@"
|
|
}
|
|
|
|
git fetch --force --no-tags "$FORGE_URL" \
|
|
refs/heads/public:refs/remotes/public-forge/public
|
|
if ! git merge-base --is-ancestor "$SOURCE_SHA" refs/remotes/public-forge/public; then
|
|
echo "::error::Forgejo public branch does not contain release commit $SOURCE_SHA"
|
|
exit 1
|
|
fi
|
|
|
|
LOCAL_TAG_OBJECT=$(git rev-parse "refs/tags/$TAG")
|
|
remote_tag=$(git ls-remote --refs "$FORGE_URL" "refs/tags/$TAG" | awk '{print $1}')
|
|
if [ -n "$remote_tag" ]; then
|
|
git fetch --force --no-tags "$FORGE_URL" \
|
|
"refs/tags/$TAG:refs/public-forge/release-tag"
|
|
test "$(git cat-file -t refs/public-forge/release-tag)" = tag
|
|
test "$(git rev-parse refs/public-forge/release-tag)" = "$LOCAL_TAG_OBJECT"
|
|
test "$(git rev-parse 'refs/public-forge/release-tag^{commit}')" = "$SOURCE_SHA"
|
|
else
|
|
GIT_ASKPASS="$PWD/scripts/git-askpass-token.sh" \
|
|
GIT_TOKEN_FILE="$TOKEN_FILE" \
|
|
GIT_TOKEN_USERNAME=publisher-redflag \
|
|
GIT_TERMINAL_PROMPT=0 \
|
|
git push "$FORGE_URL" "refs/tags/$TAG:refs/tags/$TAG"
|
|
fi
|
|
|
|
STABLE_FLOOR='0.3.0'
|
|
if [ "$(printf '%s\n%s\n' "$VERSION" "$STABLE_FLOOR" | sort -V | head -1)" = "$VERSION" ] \
|
|
&& [ "$VERSION" != "$STABLE_FLOOR" ]; then
|
|
PRERELEASE=true
|
|
else
|
|
PRERELEASE=false
|
|
fi
|
|
|
|
release_status=$(forge_curl -o /tmp/forgejo-release.json -w '%{http_code}' \
|
|
"$FORGE_API/releases/tags/$TAG")
|
|
case "$release_status" in
|
|
200)
|
|
;;
|
|
404)
|
|
payload=$(jq -nc \
|
|
--arg tag "$TAG" \
|
|
--arg name "$TAG" \
|
|
--arg body "Promoted from tested internal receipt SHA-256 $ACCEPTED_RECEIPT_SHA256" \
|
|
--argjson prerelease "$PRERELEASE" \
|
|
'{tag_name:$tag,name:$name,body:$body,draft:false,prerelease:$prerelease}')
|
|
forge_curl -f -X POST "$FORGE_API/releases" \
|
|
-H 'Content-Type: application/json' \
|
|
--data "$payload" >/tmp/forgejo-release.json
|
|
;;
|
|
*)
|
|
echo "::error::Forgejo release lookup failed with HTTP $release_status"
|
|
exit 1
|
|
;;
|
|
esac
|
|
RELEASE_ID=$(jq -er .id /tmp/forgejo-release.json)
|
|
|
|
forge_curl -f \
|
|
"$FORGE_API/releases/$RELEASE_ID/assets?limit=100" >/tmp/forgejo-assets.json
|
|
mapfile -d '' release_files < <(
|
|
find artifacts/internal -maxdepth 1 -type f -print0 | LC_ALL=C sort -z
|
|
)
|
|
test "${#release_files[@]}" -gt 1
|
|
|
|
for file in "${release_files[@]}"; do
|
|
name=$(basename "$file")
|
|
asset_count=$(jq -r --arg name "$name" \
|
|
'[.[] | select(.name == $name)] | length' /tmp/forgejo-assets.json)
|
|
case "$asset_count" in
|
|
0)
|
|
forge_curl -f -X POST \
|
|
"$FORGE_API/releases/$RELEASE_ID/assets?name=$name" \
|
|
-F "attachment=@$file" >/tmp/forgejo-upload.json
|
|
download_url=$(jq -er .browser_download_url /tmp/forgejo-upload.json)
|
|
;;
|
|
1)
|
|
download_url=$(jq -r --arg name "$name" \
|
|
'.[] | select(.name == $name) | .browser_download_url' \
|
|
/tmp/forgejo-assets.json)
|
|
;;
|
|
*)
|
|
echo "::error::duplicate Forgejo release assets named $name"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
expected_hash=$(sha256sum "$file" | awk '{print $1}')
|
|
public_hash=$(curl -fsSL "$download_url" | sha256sum | awk '{print $1}')
|
|
test "$public_hash" = "$expected_hash"
|
|
echo "Verified public release asset: $name $expected_hash"
|
|
done
|
|
|
|
anonymous_tag=$(curl -fsS "$FORGE_API/releases/tags/$TAG" | jq -r .tag_name)
|
|
test "$anonymous_tag" = "$TAG"
|
|
public_tag_object=$(git ls-remote --refs "$FORGE_URL" "refs/tags/$TAG" | awk '{print $1}')
|
|
test "$public_tag_object" = "$LOCAL_TAG_OBJECT"
|
|
echo "Forgejo release $TAG is anonymous and byte-exact"
|