komikku/.forgejo/workflows/build_release.yml
littlecodedragon f91af6fc72 ci(forgejo): shorten release notes to since-last-tag + cap
- Build What's Changed from previous v* tag..HEAD only (not upstream..HEAD).
- List at most 50 newest commits with pointer to Full Changelog compare.
- Drop fork-delta footer block to reduce scrolling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 06:57:58 +02:00

733 lines
29 KiB
YAML
Executable file

name: Forgejo Release Builder
# NOTE: keep Forgejo action refs node20-compatible for runner v3.5.1.
on:
push:
tags:
- v*
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
runs-on: desktop-release
steps:
- name: Clone repository
shell: bash
env:
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
rm -rf .git
git init .
origin_url="${GITHUB_SERVER_URL#https://}"
origin_url="${origin_url#http://}"
git remote add origin "https://copilot:${FORGEJO_TOKEN}@${origin_url}/${GITHUB_REPOSITORY}.git"
git -c http.sslVerify=false fetch --tags --prune origin "${GITHUB_SHA}"
git checkout --force FETCH_HEAD
- name: Set up JDK
shell: bash
run: |
set -euo pipefail
pkg_install() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
echo "Need root or sudo to install packages."
exit 1
fi
}
if command -v java >/dev/null 2>&1; then
java -version
else
echo "PATH=$PATH"
echo "pkg managers: dnf=$(command -v dnf || true) apt=$(command -v apt-get || true) apk=$(command -v apk || true) pacman=$(command -v pacman || true)"
if [ -x /usr/bin/dnf ] || command -v dnf >/dev/null 2>&1; then
pkg_install dnf install -y java-17-openjdk-devel
elif [ -x /usr/bin/apt-get ] || command -v apt-get >/dev/null 2>&1; then
pkg_install apt-get update
pkg_install apt-get install -y openjdk-17-jdk
elif [ -x /sbin/apk ] || [ -x /usr/sbin/apk ] || command -v apk >/dev/null 2>&1; then
pkg_install apk add --no-cache openjdk17
elif [ -x /usr/bin/pacman ] || command -v pacman >/dev/null 2>&1; then
pkg_install pacman -Sy --noconfirm jdk17-openjdk
else
echo "No supported package manager found for JDK installation."
exit 1
fi
java -version
fi
if command -v javac >/dev/null 2>&1; then
javac -version
java_home="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")"
echo "JAVA_HOME=$java_home" >> "$GITHUB_ENV"
fi
- name: Configure persistent Gradle cache
shell: bash
run: |
set -euo pipefail
gradle_home="${KOMIKKU_GRADLE_CACHE:-${HOME}/.cache/komikku-ci-gradle}"
mkdir -p "${gradle_home}"
echo "GRADLE_USER_HOME=${gradle_home}" >> "${GITHUB_ENV}"
echo "Gradle dependencies/cache: ${gradle_home}"
- name: Prepare release metadata
id: prepare
shell: bash
run: |
set -euo pipefail
version_tag="${GITHUB_REF_NAME:-${GITHUB_REF#refs/tags/}}"
version_display="$(printf '%s' "$version_tag" | sed -E 's/^v//' )"
previous_tag="$(git tag --list 'v*' --sort=-version:refname | grep -Fxv "$version_tag" | head -n 1 || true)"
upstream_repo="komikku-app/komikku"
upstream_ref=""
upstream_base=""
for ref in master main; do
candidate="$(git ls-remote "https://github.com/${upstream_repo}.git" "refs/heads/${ref}" | awk '{print $1}' || true)"
if [ -n "$candidate" ]; then
upstream_ref="$ref"
upstream_base="$candidate"
break
fi
done
echo "VERSION_TAG=$version_tag" >> "$GITHUB_OUTPUT"
echo "VERSION_DISPLAY=$version_display" >> "$GITHUB_OUTPUT"
echo "PREV_TAG_NAME=$previous_tag" >> "$GITHUB_OUTPUT"
echo "UPSTREAM_REPO=$upstream_repo" >> "$GITHUB_OUTPUT"
echo "UPSTREAM_REF=$upstream_ref" >> "$GITHUB_OUTPUT"
echo "UPSTREAM_BASE=$upstream_base" >> "$GITHUB_OUTPUT"
if [ -n "$upstream_base" ]; then
git fetch --no-tags --depth=1 "https://github.com/${upstream_repo}.git" "$upstream_base" || true
fi
# Release notes: only commits since the *previous release tag* (short, correct scope).
# Do not use upstream_base..HEAD here — that lists the whole fork delta and is huge.
changelog_commit_limit=50
log_range=""
if [ -n "$previous_tag" ]; then
log_range="${previous_tag}..HEAD"
fi
commit_log_file="$(mktemp /tmp/komikku-commit-logs.XXXXXX)"
declare -a new_lines fix_lines improve_lines other_lines
bucket_for_subject() {
local s="$1"
local b=other
shopt -s nocasematch
local r_feat r_feature r_add r_fix r_perf r_refactor r_chore r_docs r_style r_test r_ci r_build r_improve
r_feat='^feat\([^)]*\):'
r_feature='^feature\([^)]*\):'
r_add='^add\([^)]*\):'
r_fix='^fix\([^)]*\):'
r_perf='^perf\([^)]*\):'
r_refactor='^refactor\([^)]*\):'
r_chore='^chore\([^)]*\):'
r_docs='^docs\([^)]*\):'
r_style='^style\([^)]*\):'
r_test='^test\([^)]*\):'
r_ci='^ci\([^)]*\):'
r_build='^build\([^)]*\):'
r_improve='^improve\([^)]*\):'
if [[ "$s" =~ ^feat: ]] || [[ "$s" =~ $r_feat ]]; then b=new
elif [[ "$s" =~ ^feature: ]] || [[ "$s" =~ $r_feature ]]; then b=new
elif [[ "$s" =~ ^add: ]] || [[ "$s" =~ $r_add ]]; then b=new
elif [[ "$s" =~ ^fix: ]] || [[ "$s" =~ $r_fix ]]; then b=fix
elif [[ "$s" =~ ^perf: ]] || [[ "$s" =~ $r_perf ]]; then b=improve
elif [[ "$s" =~ ^refactor: ]] || [[ "$s" =~ $r_refactor ]]; then b=improve
elif [[ "$s" =~ ^chore: ]] || [[ "$s" =~ $r_chore ]]; then b=improve
elif [[ "$s" =~ ^docs: ]] || [[ "$s" =~ $r_docs ]]; then b=improve
elif [[ "$s" =~ ^style: ]] || [[ "$s" =~ $r_style ]]; then b=improve
elif [[ "$s" =~ ^test: ]] || [[ "$s" =~ $r_test ]]; then b=improve
elif [[ "$s" =~ ^ci: ]] || [[ "$s" =~ $r_ci ]]; then b=improve
elif [[ "$s" =~ ^build: ]] || [[ "$s" =~ $r_build ]]; then b=improve
elif [[ "$s" =~ ^improve: ]] || [[ "$s" =~ $r_improve ]]; then b=improve
fi
shopt -u nocasematch
printf '%s' "$b"
}
clear_buckets() {
new_lines=() fix_lines=() improve_lines=() other_lines=()
}
fill_buckets_from_git() {
local rev_range="$1"
shift
while IFS= read -r line || [ -n "${line:-}" ]; do
[ -z "${line:-}" ] && continue
subject="${line%%$'\x1f'*}"
author="${line#*$'\x1f'}"
entry="- ${subject} (@${author})"
case "$(bucket_for_subject "$subject")" in
new) new_lines+=("$entry") ;;
fix) fix_lines+=("$entry") ;;
improve) improve_lines+=("$entry") ;;
*) other_lines+=("$entry") ;;
esac
done < <(git log "$@" --pretty=tformat:'%s%x1f%an' "$rev_range")
}
emit_buckets_from_arrays() {
local any=0
if [ ${#new_lines[@]} -gt 0 ]; then
printf '%s\n' "##### New" ""
printf '%s\n' "${new_lines[@]}"
printf '\n'
any=1
fi
if [ ${#fix_lines[@]} -gt 0 ]; then
printf '%s\n' "##### Fix" ""
printf '%s\n' "${fix_lines[@]}"
printf '\n'
any=1
fi
if [ ${#improve_lines[@]} -gt 0 ]; then
printf '%s\n' "##### Improve" ""
printf '%s\n' "${improve_lines[@]}"
printf '\n'
any=1
fi
if [ ${#other_lines[@]} -gt 0 ]; then
printf '%s\n' "##### Other" ""
printf '%s\n' "${other_lines[@]}"
printf '\n'
any=1
fi
[ "$any" -eq 1 ]
}
if [ -n "$log_range" ]; then
total_in_range="$(git rev-list --count "${log_range}" 2>/dev/null || printf '0')"
clear_buckets
fill_buckets_from_git "$log_range" "-n${changelog_commit_limit}"
{
if emit_buckets_from_arrays; then
if [ "${total_in_range}" -gt "${changelog_commit_limit}" ] 2>/dev/null; then
printf '%s\n' "" "_Showing the ${changelog_commit_limit} most recent commits since ${previous_tag}._" \
"_Older commits in this release: use **Full Changelog** below._" ""
fi
else
printf '%s\n' "##### Other" "" "- No commit messages in this tag range." ""
fi
} > "${commit_log_file}"
else
printf '%s\n' "##### Other" "" "- First release tag, or no prior \`v*\` tag found — see repository history." "" > "${commit_log_file}"
fi
# Do not pass multiline commit text via step outputs / env (breaks some Forgejo/act_runner
# runners before the shell starts — empty logs). Only pass a temp file path.
echo "COMMIT_LOG_FILE=${commit_log_file}" >> "${GITHUB_ENV}"
- name: Set up Gradle
shell: bash
run: |
set -euo pipefail
sdk_candidates=(
"${ANDROID_HOME:-}"
"${ANDROID_SDK_ROOT:-}"
"/opt/android-sdk-linux"
"/home/yuna/Android/Sdk"
"/home/runner/Android/Sdk"
"/root/Android/Sdk"
)
sdk_dir=""
for candidate in "${sdk_candidates[@]}"; do
if [ -n "$candidate" ] && [ -d "$candidate/platforms" ] && [ -d "$candidate/build-tools" ]; then
sdk_dir="$candidate"
break
fi
done
if [ -z "$sdk_dir" ]; then
echo "Android SDK not found. Checked:"
for candidate in "${sdk_candidates[@]}"; do
[ -n "$candidate" ] && echo " - $candidate"
done
exit 1
fi
export ANDROID_HOME="$sdk_dir"
export ANDROID_SDK_ROOT="$sdk_dir"
echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV"
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
sdkmanager_bin="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
if [ -x "$sdkmanager_bin" ]; then
if [ ! -x "$ANDROID_HOME/build-tools/35.0.0/aidl" ]; then
echo "Installing Android SDK components via sdkmanager..."
set +o pipefail
yes | "$sdkmanager_bin" --licenses >/dev/null || true
yes | "$sdkmanager_bin" "platform-tools" "build-tools;35.0.0"
set -o pipefail
else
echo "Android build-tools 35.0.0 present; skipping sdkmanager (saves time)."
fi
else
echo "sdkmanager not found at $sdkmanager_bin"
fi
if [ ! -x "$ANDROID_HOME/build-tools/35.0.0/aidl" ]; then
echo "Expected executable missing: $ANDROID_HOME/build-tools/35.0.0/aidl"
ls -la "$ANDROID_HOME/build-tools" || true
exit 1
fi
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties
chmod +x ./gradlew
./gradlew --no-daemon --max-workers=1 \
"-Dorg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=640m -XX:ReservedCodeCacheSize=192m -Dfile.encoding=UTF-8" \
-Dorg.gradle.parallel=false \
-Dorg.gradle.configureondemand=false \
-Dorg.gradle.vfs.watch=false \
-Dkotlin.daemon.enabled=false \
"-Dkotlin.daemon.jvmargs=-Xmx512m,-XX:MaxMetaspaceSize=256m,-XX:ReservedCodeCacheSize=128m" \
-Dkotlin.compiler.execution.strategy=in-process \
-Pkotlin.incremental=true \
--version
- name: Build app
run: |
if [ -n "${ANDROID_HOME:-}" ]; then
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties
fi
./gradlew --no-daemon --max-workers=1 \
"-Dorg.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=640m -XX:ReservedCodeCacheSize=192m -Dfile.encoding=UTF-8" \
-Dorg.gradle.parallel=false \
-Dorg.gradle.configureondemand=false \
-Dorg.gradle.vfs.watch=false \
-Dkotlin.daemon.enabled=false \
"-Dkotlin.daemon.jvmargs=-Xmx512m,-XX:MaxMetaspaceSize=256m,-XX:ReservedCodeCacheSize=128m" \
-Dkotlin.compiler.execution.strategy=in-process \
-Pkotlin.incremental=true \
--stacktrace \
assembleRelease -Penable-updater -Pdisable-code-shrink
- name: Rename release artifacts
shell: bash
run: |
set -euo pipefail
version_tag="${{ steps.prepare.outputs.VERSION_TAG }}"
mv app/build/outputs/apk/release/app-universal-release-unsigned.apk "Komikku-${version_tag}-unsigned.apk"
mv app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk "Komikku-arm64-v8a-${version_tag}-unsigned.apk"
mv app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk "Komikku-armeabi-v7a-${version_tag}-unsigned.apk"
mv app/build/outputs/apk/release/app-x86-release-unsigned.apk "Komikku-x86-${version_tag}-unsigned.apk"
mv app/build/outputs/apk/release/app-x86_64-release-unsigned.apk "Komikku-x86_64-${version_tag}-unsigned.apk"
if [ -d app/build/outputs/mapping/release ]; then
zip -qr "Komikku-mapping-${version_tag}.zip" app/build/outputs/mapping/release
fi
- name: Sign APKs
shell: bash
env:
SIGNING_KEYSTORE_B64: ${{ secrets.SIGNING_KEYSTORE_B64 }}
SIGNING_KEYSTORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
set -euo pipefail
if [ -z "${SIGNING_KEYSTORE_B64}" ] || [ -z "${SIGNING_KEYSTORE_PASSWORD}" ] || [ -z "${SIGNING_KEY_ALIAS}" ] || [ -z "${SIGNING_KEY_PASSWORD}" ]; then
echo "Signing secrets are required for installable release APKs."
echo "Set SIGNING_KEYSTORE_B64, SIGNING_KEYSTORE_PASSWORD, SIGNING_KEY_ALIAS, SIGNING_KEY_PASSWORD."
exit 1
fi
keystore_file="$(mktemp)"
printf '%s' "${SIGNING_KEYSTORE_B64}" | base64 -d > "${keystore_file}"
apksigner_bin=""
if command -v apksigner >/dev/null 2>&1; then
apksigner_bin="$(command -v apksigner)"
elif [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}/build-tools" ]; then
apksigner_bin="$(ls -1 "${ANDROID_HOME}"/build-tools/*/apksigner 2>/dev/null | sort -V | tail -n 1 || true)"
fi
if [ -z "${apksigner_bin}" ]; then
echo "apksigner not found in PATH or ANDROID_HOME/build-tools."
exit 1
fi
for unsigned_apk in Komikku-*-unsigned.apk; do
[ -e "${unsigned_apk}" ] || continue
signed_apk="${unsigned_apk/-unsigned.apk/.apk}"
"${apksigner_bin}" sign \
--ks "${keystore_file}" \
--ks-key-alias "${SIGNING_KEY_ALIAS}" \
--ks-pass "pass:${SIGNING_KEYSTORE_PASSWORD}" \
--key-pass "pass:${SIGNING_KEY_PASSWORD}" \
--out "${signed_apk}" \
"${unsigned_apk}"
"${apksigner_bin}" verify "${signed_apk}"
done
- name: Create Obtainium-friendly aliases
shell: bash
run: |
set -euo pipefail
alias_asset() {
src="$1"
dst="$2"
if [ -e "$src" ]; then
cp -f "$src" "$dst"
fi
}
# Only publish signed APK aliases.
alias_asset "$(ls -1t Komikku-arm64-v8a-*.apk 2>/dev/null | head -n1)" "Komikku-arm64-v8a.apk"
for candidate in Komikku-*.apk; do
case "$candidate" in
*arm64-v8a*|*armeabi-v7a*|*x86_64*|*x86*|*-unsigned.apk)
;;
*)
alias_asset "$candidate" "Komikku-universal.apk"
break
;;
esac
done
- name: Create or update release
id: release
shell: bash
env:
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION_TAG: ${{ steps.prepare.outputs.VERSION_TAG }}
VERSION_DISPLAY: ${{ steps.prepare.outputs.VERSION_DISPLAY }}
PREV_TAG_NAME: ${{ steps.prepare.outputs.PREV_TAG_NAME }}
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
REPOSITORY_NAME: ${{ github.repository }}
FORGEJO_CURL_INSECURE: "true"
run: |
set -euo pipefail
echo "Create or update release: shell running; COMMIT_LOG_FILE=${COMMIT_LOG_FILE:-<unset>}"
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
discover_jq() {
if command -v jq >/dev/null 2>&1; then
JQ_BIN="$(command -v jq)"
export JQ_BIN
return 0
fi
for cand in /usr/bin/jq /usr/local/bin/jq /bin/jq; do
if [ -x "$cand" ]; then
JQ_BIN="$cand"
export JQ_BIN
return 0
fi
done
return 1
}
ensure_jq() {
if discover_jq; then
return 0
fi
run_as_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
return 1
fi
}
if command -v dnf >/dev/null 2>&1; then
run_as_root dnf install -y jq
elif command -v apt-get >/dev/null 2>&1; then
run_as_root apt-get update
run_as_root apt-get install -y jq
elif command -v apk >/dev/null 2>&1; then
run_as_root apk add --no-cache jq
elif command -v pacman >/dev/null 2>&1; then
run_as_root pacman -Sy --noconfirm jq
fi
discover_jq
}
ensure_curl() {
if command -v curl >/dev/null 2>&1; then
return 0
fi
run_as_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
return 1
fi
}
if command -v dnf >/dev/null 2>&1; then
run_as_root dnf install -y curl
elif command -v apt-get >/dev/null 2>&1; then
run_as_root apt-get update
run_as_root apt-get install -y curl
elif command -v apk >/dev/null 2>&1; then
run_as_root apk add --no-cache curl
elif command -v pacman >/dev/null 2>&1; then
run_as_root pacman -Sy --noconfirm curl
fi
command -v curl >/dev/null 2>&1
}
ensure_jq || { echo "jq not found; install jq on the runner (dnf install -y jq)."; exit 1; }
ensure_curl || { echo "curl not found; install curl on the runner (dnf install -y curl)."; exit 1; }
curl_tls_args=()
if [ "${FORGEJO_CURL_INSECURE:-false}" = "true" ]; then
curl_tls_args+=(--insecure)
fi
api_base="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases"
payload_file="$(mktemp)"
response_file="$(mktemp)"
commit_logs=""
if [ -n "${COMMIT_LOG_FILE:-}" ] && [ -f "${COMMIT_LOG_FILE}" ]; then
commit_logs="$(cat "${COMMIT_LOG_FILE}")"
fi
commit_logs="$(printf '%s' "$commit_logs" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
if [ -z "$commit_logs" ]; then
commit_logs=$'##### Other\n\n- Initial release\n'
fi
full_changelog=""
if [ -n "${PREV_TAG_NAME:-}" ]; then
full_changelog="**Full Changelog**: [${REPOSITORY_NAME}@${PREV_TAG_NAME}...${VERSION_TAG}](${REPO_URL}/compare/${PREV_TAG_NAME}...${VERSION_TAG})"
fi
release_body_file="$(mktemp)"
{
printf '%s\n' \
"#### What's Changed" \
"" \
"${commit_logs}" \
""
if [ -n "${full_changelog}" ]; then
printf '%s\n' "${full_changelog}" ""
fi
printf '%s\n' \
"<!-->" \
"> [!TIP]" \
">" \
"> ### If you are unsure which version to download then go with \`Komikku-universal.apk\`"
} > "${release_body_file}"
# Build JSON — body from file only (jq --arg body "$huge" hits ARG_MAX).
"${JQ_BIN}" -n \
--arg tag "${VERSION_TAG}" \
--arg title "Komikku ${VERSION_DISPLAY}" \
--rawfile body "${release_body_file}" \
'{tag_name: $tag, name: $title, body: $body, draft: false, prerelease: false}' > "${payload_file}"
tag_path_enc="$("${JQ_BIN}" -nr --arg t "${VERSION_TAG}" '$t|@uri')"
tag_get_url="${api_base}/tags/${tag_path_enc}"
echo "Forgejo release API: GET ${tag_get_url}"
status="$(curl "${curl_tls_args[@]}" -sS -o "$response_file" -w '%{http_code}' \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"${tag_get_url}")"
echo "release-by-tag GET HTTP status: ${status}"
if [ "${status}" != "200" ] && [ "${status}" != "404" ]; then
echo "Unexpected GET response body:"
cat "$response_file"
exit 1
fi
release_id=""
if [ "${status}" = "200" ]; then
release_id="$("${JQ_BIN}" -r '.id // empty' < "$response_file")"
if [ -z "${release_id}" ] || [ "${release_id}" = "null" ]; then
echo "GET returned 200 but missing release id; body:"
cat "$response_file"
exit 1
fi
echo "PATCH existing release id=${release_id}"
patch_status="$(curl "${curl_tls_args[@]}" -sS -o "$response_file" -w '%{http_code}' \
-X PATCH \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H 'Content-Type: application/json' \
--data @"$payload_file" \
"${api_base}/${release_id}")"
echo "PATCH HTTP status: ${patch_status}"
if [ "${patch_status}" != "200" ]; then
echo "PATCH response body:"
cat "$response_file"
exit 1
fi
elif [ "${status}" = "404" ]; then
echo "POST new release for tag ${VERSION_TAG}"
post_status="$(curl "${curl_tls_args[@]}" -sS -o "$response_file" -w '%{http_code}' \
-X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H 'Content-Type: application/json' \
--data @"$payload_file" \
"${api_base}")"
echo "POST HTTP status: ${post_status}"
if [ "${post_status}" != "201" ] && [ "${post_status}" != "200" ]; then
echo "POST response body:"
cat "$response_file"
exit 1
fi
release_id="$("${JQ_BIN}" -r '.id // empty' < "$response_file")"
if [ -z "${release_id}" ] || [ "${release_id}" = "null" ]; then
echo "POST succeeded but missing release id; body:"
cat "$response_file"
exit 1
fi
fi
echo "RELEASE_ID=${release_id}" >> "$GITHUB_OUTPUT"
- name: Upload release assets
shell: bash
env:
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_ID: ${{ steps.release.outputs.RELEASE_ID }}
FORGEJO_CURL_INSECURE: "true"
run: |
set -euo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"
discover_jq() {
if command -v jq >/dev/null 2>&1; then
JQ_BIN="$(command -v jq)"
export JQ_BIN
return 0
fi
for cand in /usr/bin/jq /usr/local/bin/jq /bin/jq; do
if [ -x "$cand" ]; then
JQ_BIN="$cand"
export JQ_BIN
return 0
fi
done
return 1
}
ensure_jq() {
if discover_jq; then
return 0
fi
run_as_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
return 1
fi
}
if command -v dnf >/dev/null 2>&1; then
run_as_root dnf install -y jq
elif command -v apt-get >/dev/null 2>&1; then
run_as_root apt-get update
run_as_root apt-get install -y jq
elif command -v apk >/dev/null 2>&1; then
run_as_root apk add --no-cache jq
elif command -v pacman >/dev/null 2>&1; then
run_as_root pacman -Sy --noconfirm jq
fi
discover_jq
}
ensure_curl() {
if command -v curl >/dev/null 2>&1; then
return 0
fi
run_as_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
return 1
fi
}
if command -v dnf >/dev/null 2>&1; then
run_as_root dnf install -y curl
elif command -v apt-get >/dev/null 2>&1; then
run_as_root apt-get update
run_as_root apt-get install -y curl
elif command -v apk >/dev/null 2>&1; then
run_as_root apk add --no-cache curl
elif command -v pacman >/dev/null 2>&1; then
run_as_root pacman -Sy --noconfirm curl
fi
command -v curl >/dev/null 2>&1
}
ensure_jq || { echo "jq not found; install jq on the runner (dnf install -y jq)."; exit 1; }
ensure_curl || { echo "curl not found; install curl on the runner (dnf install -y curl)."; exit 1; }
curl_tls_args=()
if [ "${FORGEJO_CURL_INSECURE:-false}" = "true" ]; then
curl_tls_args+=(--insecure)
fi
if [ -z "${RELEASE_ID:-}" ] || [ "${RELEASE_ID}" = "null" ]; then
echo "RELEASE_ID missing or null — create/update release step likely failed."
exit 1
fi
release_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}"
assets_file="$(mktemp)"
err_body="$(mktemp)"
assets_status="$(curl "${curl_tls_args[@]}" -sS -o "$assets_file" -w '%{http_code}' \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"${release_api}/assets")"
if [ "${assets_status}" != "200" ]; then
echo "List assets failed HTTP ${assets_status}:"
cat "$assets_file"
rm -f "$assets_file" "$err_body"
exit 1
fi
for file in Komikku-*.apk Komikku-mapping-*.zip; do
[ -e "$file" ] || continue
if [ "${file#*-unsigned.apk}" != "${file}" ]; then
continue
fi
asset_name="$(basename "$file")"
asset_id="$("${JQ_BIN}" -r --arg n "${asset_name}" '(.[] | select(.name == $n) | .id) // empty' "$assets_file")"
if [ -n "$asset_id" ] && [ "$asset_id" != "null" ]; then
del_status="$(curl "${curl_tls_args[@]}" -sS -o "$err_body" -w '%{http_code}' \
-X DELETE \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"${release_api}/assets/${asset_id}")"
if [ "${del_status}" != "204" ] && [ "${del_status}" != "200" ] && [ "${del_status}" != "404" ]; then
echo "DELETE old asset ${asset_name} failed HTTP ${del_status}:"
cat "$err_body"
rm -f "$assets_file" "$err_body"
exit 1
fi
fi
encoded_name="$("${JQ_BIN}" -nr --arg s "${asset_name}" '$s | @uri')"
up_status="$(curl "${curl_tls_args[@]}" -sS -o "$err_body" -w '%{http_code}' \
-X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H 'Content-Type: application/octet-stream' \
--data-binary @"$file" \
"${release_api}/assets?name=${encoded_name}")"
if [ "${up_status}" != "201" ] && [ "${up_status}" != "200" ]; then
echo "Upload ${asset_name} failed HTTP ${up_status}:"
cat "$err_body"
rm -f "$assets_file" "$err_body"
exit 1
fi
echo "Uploaded ${asset_name} (HTTP ${up_status})"
done
rm -f "$assets_file" "$err_body"