mirror of
https://code.forgejo.org/actions/forgejo-release.git
synced 2025-11-28 02:01:58 +02:00
246 lines
7.4 KiB
Bash
Executable file
246 lines
7.4 KiB
Bash
Executable file
#!/bin/sh
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
# calling this directly via e.g. bash forgejo-release.sh will not set -e if it's in the shebang only
|
|
set -e
|
|
|
|
if ${VERBOSE:-false}; then set -x; fi
|
|
|
|
: "${FORGEJO:=https://codeberg.org}"
|
|
: "${REPO:=forgejo-integration/forgejo}"
|
|
: "${TITLE:=$TAG}"
|
|
: "${RELEASE_DIR:=dist/release}"
|
|
: "${DOWNLOAD_LATEST:=false}"
|
|
: "${TMP_DIR:=$(mktemp -d)}"
|
|
: "${GNUPGHOME:=$TMP_DIR}"
|
|
: "${TEA_BIN:=$TMP_DIR/tea}"
|
|
: "${TEA_VERSION:=0.10.1}"
|
|
: "${OVERRIDE:=false}"
|
|
: "${HIDE_ARCHIVE_LINK:=false}"
|
|
: "${RETRY:=1}"
|
|
: "${DELAY:=10}"
|
|
|
|
RELEASE_NOTES_ASSISTANT_VERSION=v1.4.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org
|
|
|
|
TAG_FILE="$TMP_DIR/tag$$.json"
|
|
TAG_URL=$(echo "$TAG" | sed 's/\//%2F/g')
|
|
|
|
export GNUPGHOME
|
|
|
|
setup_tea() {
|
|
if command -v tea >/dev/null 2>&1; then
|
|
TEA_BIN=$(command -v tea)
|
|
elif [ ! -f "$TEA_BIN" ]; then
|
|
ARCH=$(dpkg --print-architecture)
|
|
curl -sL "https://dl.gitea.io/tea/$TEA_VERSION/tea-$TEA_VERSION-linux-$ARCH" >"$TEA_BIN"
|
|
chmod +x "$TEA_BIN"
|
|
fi
|
|
}
|
|
|
|
get_tag() {
|
|
if [ -f "$TAG_FILE" ]; then
|
|
if api GET "repos/$REPO/tags/$TAG_URL" >"$TAG_FILE"; then
|
|
echo "tag $TAG exists"
|
|
else
|
|
echo "tag $TAG does not exist"
|
|
fi
|
|
fi
|
|
|
|
[ -s "$TAG_FILE" ]
|
|
}
|
|
|
|
matched_tag() {
|
|
get_tag && [ "$(jq --raw-output .commit.sha <"$TAG_FILE")" = "$SHA" ]
|
|
}
|
|
|
|
ensure_tag() {
|
|
if get_tag; then
|
|
if ! matched_tag; then
|
|
cat "$TAG_FILE"
|
|
echo "the tag SHA in the $REPO repository does not match the tag SHA that triggered the build: $SHA"
|
|
return 1
|
|
fi
|
|
else
|
|
create_tag
|
|
fi
|
|
}
|
|
|
|
create_tag() {
|
|
api POST "repos/$REPO/tags" --data-raw '{"tag_name": "'"$TAG"'", "target": "'"$SHA"'"}' >"$TAG_FILE"
|
|
}
|
|
|
|
delete_tag() {
|
|
if get_tag; then
|
|
api DELETE "repos/$REPO/tags/$TAG_URL"
|
|
rm -f "$TAG_FILE"
|
|
fi
|
|
}
|
|
|
|
upload_release() {
|
|
# assets is defined as a list of arguments, where values may contain whitespace and need to be quoted like this -a "my file.txt" -a "file.txt".
|
|
# It is built using "set --" and expanded with "$@", which preserves argument separation and whitespace splitting.
|
|
# For reference, see https://www.shellcheck.net/wiki/SC3030
|
|
# In this case, we do not need more than one array, so set -- is fine here.
|
|
for file in "$RELEASE_DIR"/*; do
|
|
set -- "$@" -a "$file"
|
|
done
|
|
|
|
if $PRERELEASE || echo "${TAG}" | grep -qi '\-rc'; then
|
|
releaseType="--prerelease"
|
|
echo "Uploading as Pre-Release"
|
|
else
|
|
echo "Uploading as Stable"
|
|
fi
|
|
|
|
ensure_tag
|
|
|
|
# shellcheck disable=SC2086
|
|
if ! $TEA_BIN release create "$@" --repo "$REPO" --note "$RELEASENOTES" --tag "$TAG" --title "$TITLE" --draft "$releaseType" > "$TMP_DIR"/tea.log 2>&1; then
|
|
if grep --quiet 'Unknown API Error: 500' "$TMP_DIR"/tea.log && grep --quiet services/release/release.go:194 "$TMP_DIR"/tea.log; then
|
|
echo "workaround v1.20 race condition https://codeberg.org/forgejo/forgejo/issues/1370"
|
|
sleep 10
|
|
$TEA_BIN release create "$@" --repo "$REPO" --note "$RELEASENOTES" --tag "$TAG" --title "$TITLE" --draft "$releaseType"
|
|
else
|
|
cat "$TMP_DIR"/tea.log
|
|
return 1
|
|
fi
|
|
fi
|
|
maybe_use_release_note_assistant
|
|
release_draft false
|
|
}
|
|
|
|
# $1 is the state
|
|
release_draft() {
|
|
_release_draft_id=$(api GET "repos/$REPO/releases/tags/$TAG_URL" | jq --raw-output .id)
|
|
|
|
api PATCH "repos/$REPO/releases/$_release_draft_id" --data-raw '{"draft": '"$1"', "hide_archive_links": '"$HIDE_ARCHIVE_LINK"'}'
|
|
}
|
|
|
|
maybe_use_release_note_assistant() {
|
|
if "$RELEASE_NOTES_ASSISTANT"; then
|
|
curl --fail -s -S -o rna https://code.forgejo.org/forgejo/release-notes-assistant/releases/download/$RELEASE_NOTES_ASSISTANT_VERSION/release-notes-assistant
|
|
chmod +x ./rna
|
|
mkdir -p "$RELEASE_NOTES_ASSISTANT_WORKDIR"
|
|
./rna --workdir="$RELEASE_NOTES_ASSISTANT_WORKDIR" --storage release --storage-location "$TAG" --token "$TOKEN" \
|
|
--forgejo-url "$SCHEME://$HOST" --repository "$REPO" --token "$TOKEN" release "$TAG"
|
|
fi
|
|
}
|
|
|
|
sign_release() {
|
|
if [ -s "$GPG_PASSPHRASE" ]; then
|
|
set -- "$@" --passphrase-file "$GPG_PASSPHRASE"
|
|
fi
|
|
gpg --import --no-tty --pinentry-mode loopback "$@" "$GPG_PRIVATE_KEY"
|
|
for asset in "$RELEASE_DIR"/*; do
|
|
case "$asset" in
|
|
*.sha256) continue ;;
|
|
esac
|
|
|
|
gpg --armor --detach-sign --no-tty --pinentry-mode loopback "$@" <"$asset" >"$asset".asc
|
|
done
|
|
}
|
|
|
|
maybe_sign_release() {
|
|
{ [ -s "$GPG_PRIVATE_KEY" ] && sign_release; } || true
|
|
}
|
|
|
|
maybe_override() {
|
|
if [ "$OVERRIDE" = "false" ]; then
|
|
return
|
|
fi
|
|
api DELETE "repos/$REPO/releases/tags/$TAG_URL" > /dev/null 2>&1 || true
|
|
if get_tag && ! matched_tag; then
|
|
delete_tag
|
|
fi
|
|
}
|
|
|
|
upload() {
|
|
setup_api
|
|
setup_tea
|
|
rm -f ~/.config/tea/config.yml
|
|
GITEA_SERVER_TOKEN=$TOKEN $TEA_BIN login add --url "$FORGEJO"
|
|
maybe_sign_release
|
|
maybe_override
|
|
upload_release
|
|
}
|
|
|
|
setup_api() {
|
|
needed="jq curl"
|
|
for cmd in $needed; do
|
|
command -v "$cmd" >/dev/null 2>&1 || missing="$missing $cmd"
|
|
done
|
|
|
|
# debian/ubuntu
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
apt-get -qq update
|
|
apt-get install -y -qq "$missing"
|
|
# arch
|
|
elif command -v pacman >/dev/null 2>&1; then
|
|
pacman -Syu --noconfirm "$missing"
|
|
# alpine
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
apk add --no-cache "$missing"
|
|
# gentoo
|
|
elif command -v emerge >/dev/null 2>&1; then
|
|
emerge -q "$missing"
|
|
fi
|
|
}
|
|
|
|
api() {
|
|
method=$1
|
|
shift
|
|
path=$1
|
|
shift
|
|
|
|
curl --fail -X "$method" -sS -H "Content-Type: application/json" -H "Authorization: token $TOKEN" "$@" "$FORGEJO/api/v1/$path"
|
|
}
|
|
|
|
wait_release() {
|
|
ready=false
|
|
for _ in $(seq "$RETRY"); do
|
|
# TODO: this is probably unnecessary; a direct comparison on the jq call is *probably* enough.
|
|
if api GET "repos/$REPO/releases/tags/$TAG_URL" | jq --raw-output .draft >"$TMP_DIR"/draft; then
|
|
if [ "$(cat "$TMP_DIR"/draft)" = "false" ]; then
|
|
ready=true
|
|
break
|
|
fi
|
|
echo "release $TAG is still a draft"
|
|
else
|
|
echo "release $TAG does not exist yet"
|
|
fi
|
|
echo "waiting $DELAY seconds"
|
|
sleep "$DELAY"
|
|
done
|
|
if ! $ready; then
|
|
echo "no release for $TAG"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
download() {
|
|
setup_api
|
|
(
|
|
mkdir -p "$RELEASE_DIR"
|
|
cd "$RELEASE_DIR"
|
|
if [ "${DOWNLOAD_LATEST}" = "true" ]; then
|
|
echo "Downloading the latest release"
|
|
api GET "repos/$REPO/releases/latest" >"$TMP_DIR"/assets.json
|
|
elif [ "${DOWNLOAD_LATEST}" = "false" ]; then
|
|
wait_release
|
|
echo "Downloading tagged release ${TAG}"
|
|
api GET "repos/$REPO/releases/tags/$TAG_URL" >"$TMP_DIR"/assets.json
|
|
fi
|
|
jq --raw-output '.assets[] | "\(.browser_download_url) \(.name)"' <"$TMP_DIR"/assets.json | while read -r url name; do # `name` may contain whitespace, therefore, it must be last
|
|
# shellcheck disable=SC2001
|
|
url=$(echo "$url" | sed "s#/download/${TAG}/#/download/${TAG_URL}/#")
|
|
curl --fail -H "Authorization: token $TOKEN" -o "$name" -L "$url"
|
|
done
|
|
)
|
|
}
|
|
|
|
missing() {
|
|
echo need upload or download argument got nothing
|
|
exit 1
|
|
}
|
|
|
|
"${@:-missing}"
|