Compare commits
151 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
437b7ad45d | ||
|
|
d342a8d595 | ||
|
|
d69ce1c388 | ||
|
|
49a0644f68 | ||
|
|
a81d717ca3 | ||
|
|
4fce1fa940 | ||
|
|
87a173929b | ||
|
|
4ae732a17b | ||
|
|
eb84ab8439 | ||
|
|
5e809fefd4 | ||
|
|
1763d947df | ||
|
|
ce6a0ea7fa | ||
|
|
da57b3db07 | ||
|
|
da7ee11bb3 | ||
|
|
21c1b85796 | ||
|
|
0f556ea9b5 | ||
|
|
06107dba9b | ||
|
|
3615dc23a9 | ||
|
|
86b60f08b1 | ||
|
|
16582cc50e | ||
|
|
559a787d0f | ||
|
|
65d2bff8c7 | ||
|
|
3e07479b0e | ||
|
|
40b19479af | ||
|
|
4fd2625bf7 | ||
|
|
453afb3edb | ||
|
|
118eeaf3ee | ||
|
|
e11cd536dc | ||
|
|
e95e463543 | ||
|
|
7b073fed0c | ||
|
|
5c84266bc7 | ||
|
|
5048e57e76 | ||
|
|
583d649d20 | ||
|
|
b299a9ce81 | ||
|
|
fdfd4582cb | ||
|
|
25b8eb889a | ||
|
|
1ffeebc9a1 | ||
|
|
3de5da42d3 | ||
|
|
e9bd1fc477 | ||
|
|
72a3bb6f58 | ||
|
|
0ba6598c3f | ||
|
|
bd13620d1f | ||
|
|
40f1391775 | ||
|
|
9761f33948 | ||
|
|
612651a076 | ||
|
|
3b06366fd9 | ||
|
|
582ea3e60d | ||
|
|
99470e8133 | ||
|
|
9ccf5ca291 | ||
|
|
30c114c20f | ||
|
|
c1806e13b3 | ||
|
|
266947b0d7 | ||
|
|
ca26501aef | ||
|
|
9a7d1b6143 | ||
|
|
94eac94ce7 | ||
|
|
dc7dd8045e | ||
|
|
0b285f83b1 | ||
|
|
768c3dcec5 | ||
|
|
f178a79195 | ||
|
|
19f8fa203d | ||
|
|
21cb1921a3 | ||
|
|
dc3d034109 | ||
|
|
0293507d1a | ||
|
|
3344fd0755 | ||
|
|
f5e1c1e5ce | ||
|
|
5c0ca305a9 | ||
|
|
1f904815fa | ||
|
|
38acd6f110 | ||
|
|
bad1dd264d | ||
|
|
6124efa164 | ||
|
|
0830cd32b5 | ||
|
|
2b4caa9f97 | ||
|
|
0f815ba662 | ||
|
|
1c9c35156d | ||
|
|
7c5fe16b52 | ||
|
|
e04565bc46 | ||
|
|
e0f4262407 | ||
|
|
6316b85a09 | ||
|
|
f91af6fc72 | ||
|
|
c15d280cb4 | ||
|
|
07019175d1 | ||
|
|
9ce3f0ba29 | ||
|
|
7fab10c8ef | ||
|
|
e89f342881 | ||
|
|
7bca00964e | ||
|
|
06bc3a6a0c | ||
|
|
940a669936 | ||
|
|
b81d3eb198 | ||
|
|
fe43d3ef2c | ||
|
|
384aee7372 | ||
|
|
de5e0a32d3 | ||
|
|
b26a537d55 | ||
|
|
a2f9003008 | ||
|
|
e50767709a | ||
|
|
c290472012 | ||
|
|
7e3738551b | ||
|
|
aa7198f082 | ||
|
|
a52609b549 | ||
|
|
4c21f019c0 | ||
|
|
0d6a348578 | ||
|
|
52036476a2 | ||
|
|
4b1b0e8cc4 | ||
|
|
41f0ee6bf5 | ||
|
|
25137641c1 | ||
|
|
e301ee2574 | ||
|
|
780ef2af25 | ||
|
|
fe3e366181 | ||
|
|
1a410d44c8 | ||
|
|
8aac90d32a | ||
|
|
510fcf1811 | ||
|
|
2cf613d19f | ||
|
|
232800cab1 | ||
|
|
780bd1bfc0 | ||
|
|
320ea451ec | ||
|
|
3ca6462cec | ||
|
|
453f7d6681 | ||
|
|
bc84ffb691 | ||
|
|
72fa8ee325 | ||
|
|
48436e3c46 | ||
|
|
af860b5f9d | ||
|
|
3b08f156d5 | ||
|
|
f37086978d | ||
|
|
55d7525d26 | ||
|
|
ab252123ae | ||
|
|
d6be3d1351 | ||
|
|
688b796e59 | ||
|
|
17c6e6bd05 | ||
|
|
ea69546fcb | ||
|
|
1f552c2774 | ||
|
|
1fc3835259 | ||
|
|
041fd7e5ee | ||
|
|
acca36c3a3 | ||
|
|
fc6b46b07b | ||
|
|
2a0caf80ff | ||
|
|
5396ebad7c | ||
|
|
1f554081a5 | ||
|
|
029c3a25f0 | ||
|
|
f6d9e07077 | ||
|
|
138a92854e | ||
|
|
dbee19612b | ||
|
|
bdf81596e9 | ||
|
|
d8eb71e004 | ||
|
|
bf9b663581 | ||
|
|
8bb8d7ccb7 | ||
|
|
80ea323281 | ||
|
|
ee7fb26790 | ||
|
|
6f020399d8 | ||
|
|
26d3be95e3 | ||
|
|
fd60bc7f13 | ||
|
|
6f06974524 | ||
|
|
edaf975e21 |
289 changed files with 7964 additions and 3440 deletions
|
|
@ -5,6 +5,12 @@ on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Release tag to build and publish (e.g. v1.13.6). Must already exist on the remote.'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
@ -15,32 +21,75 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: forgejo-release
|
runs-on: desktop-release
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repository
|
- name: Clone repository
|
||||||
uses: actions/checkout@v4 # Forgejo-compatible (node20)
|
shell: bash
|
||||||
with:
|
env:
|
||||||
fetch-depth: 0
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
DISPATCH_TAG: ${{ github.event.inputs.tag }}
|
||||||
|
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"
|
||||||
|
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
|
||||||
|
git -c http.sslVerify=false fetch --tags --prune origin "refs/tags/${DISPATCH_TAG}"
|
||||||
|
else
|
||||||
|
git -c http.sslVerify=false fetch --tags --prune origin "${GITHUB_SHA}"
|
||||||
|
fi
|
||||||
|
git checkout --force FETCH_HEAD
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4 # Forgejo-compatible (node20)
|
shell: bash
|
||||||
with:
|
run: |
|
||||||
java-version: 17
|
set -euo pipefail
|
||||||
distribution: temurin
|
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: Write google-services.json
|
- name: Configure persistent Gradle cache
|
||||||
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 # v1.3
|
shell: bash
|
||||||
with:
|
run: |
|
||||||
path: app/google-services.json
|
set -euo pipefail
|
||||||
contents: ${{ secrets.GOOGLE_SERVICES_JSON }}
|
gradle_home="${KOMIKKU_GRADLE_CACHE:-${HOME}/.cache/komikku-ci-gradle}"
|
||||||
write-mode: overwrite
|
mkdir -p "${gradle_home}"
|
||||||
|
echo "GRADLE_USER_HOME=${gradle_home}" >> "${GITHUB_ENV}"
|
||||||
- name: Write client_secrets.json
|
echo "Gradle dependencies/cache: ${gradle_home}"
|
||||||
uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 # v1.3
|
|
||||||
with:
|
|
||||||
path: app/src/main/assets/client_secrets.json
|
|
||||||
contents: ${{ secrets.GOOGLE_CLIENT_SECRETS_JSON }}
|
|
||||||
write-mode: overwrite
|
|
||||||
|
|
||||||
- name: Prepare release metadata
|
- name: Prepare release metadata
|
||||||
id: prepare
|
id: prepare
|
||||||
|
|
@ -48,46 +97,233 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
version_tag="${GITHUB_REF_NAME:-${GITHUB_REF#refs/tags/}}"
|
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
|
||||||
|
version_tag="${{ github.event.inputs.tag }}"
|
||||||
|
else
|
||||||
|
version_tag="${GITHUB_REF_NAME:-${GITHUB_REF#refs/tags/}}"
|
||||||
|
fi
|
||||||
|
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)"
|
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_TAG=$version_tag" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "VERSION_DISPLAY=$version_display" >> "$GITHUB_OUTPUT"
|
||||||
echo "PREV_TAG_NAME=$previous_tag" >> "$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 "$previous_tag" ]; then
|
if [ -n "$upstream_base" ]; then
|
||||||
commit_logs="$(git log --pretty=format:'- %s (@%an)' "${previous_tag}..HEAD")"
|
git fetch --no-tags --depth=1 "https://github.com/${upstream_repo}.git" "$upstream_base" || true
|
||||||
else
|
|
||||||
commit_logs='- Initial release'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
# Release notes: only commits since the *previous release tag* (short, correct scope).
|
||||||
echo 'COMMIT_LOGS<<EOF'
|
# Do not use upstream_base..HEAD here — that lists the whole fork delta and is huge.
|
||||||
printf '%s\n' "$commit_logs"
|
changelog_commit_limit=50
|
||||||
echo 'EOF'
|
log_range=""
|
||||||
} >> "$GITHUB_OUTPUT"
|
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
|
- name: Set up Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4.4.2 # Forgejo-compatible (node20)
|
shell: bash
|
||||||
|
run: |
|
||||||
- name: Check code format
|
set -euo pipefail
|
||||||
run: ./gradlew spotlessCheck
|
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=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:ReservedCodeCacheSize=256m -Dfile.encoding=UTF-8" \
|
||||||
|
-Dorg.gradle.parallel=false \
|
||||||
|
-Dorg.gradle.configureondemand=false \
|
||||||
|
-Dorg.gradle.vfs.watch=false \
|
||||||
|
-Dkotlin.daemon.enabled=false \
|
||||||
|
"-Dkotlin.daemon.jvmargs=-Xmx2048m,-XX:MaxMetaspaceSize=512m,-XX:ReservedCodeCacheSize=256m" \
|
||||||
|
-Dkotlin.compiler.execution.strategy=in-process \
|
||||||
|
-Pkotlin.incremental=true \
|
||||||
|
--version
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
|
run: |
|
||||||
|
if [ -n "${ANDROID_HOME:-}" ]; then
|
||||||
- name: Run unit tests
|
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > local.properties
|
||||||
run: ./gradlew testReleaseUnitTest
|
fi
|
||||||
|
./gradlew --no-daemon --max-workers=1 \
|
||||||
- name: Sign APK
|
"-Dorg.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:ReservedCodeCacheSize=256m -Dfile.encoding=UTF-8" \
|
||||||
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
|
-Dorg.gradle.parallel=false \
|
||||||
with:
|
-Dorg.gradle.configureondemand=false \
|
||||||
releaseDirectory: app/build/outputs/apk/release
|
-Dorg.gradle.vfs.watch=false \
|
||||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
-Dkotlin.daemon.enabled=false \
|
||||||
alias: ${{ secrets.ALIAS }}
|
"-Dkotlin.daemon.jvmargs=-Xmx2048m,-XX:MaxMetaspaceSize=512m,-XX:ReservedCodeCacheSize=256m" \
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
-Dkotlin.compiler.execution.strategy=in-process \
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
-Pkotlin.incremental=true \
|
||||||
env:
|
--stacktrace \
|
||||||
BUILD_TOOLS_VERSION: '35.0.1'
|
assembleRelease -Penable-updater -Pdisable-code-shrink
|
||||||
|
|
||||||
- name: Rename release artifacts
|
- name: Rename release artifacts
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
@ -96,13 +332,113 @@ jobs:
|
||||||
|
|
||||||
version_tag="${{ steps.prepare.outputs.VERSION_TAG }}"
|
version_tag="${{ steps.prepare.outputs.VERSION_TAG }}"
|
||||||
|
|
||||||
mv app/build/outputs/apk/release/app-universal-release-unsigned-signed.apk "Komikku-${version_tag}.apk"
|
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-signed.apk "Komikku-arm64-v8a-${version_tag}.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-signed.apk "Komikku-armeabi-v7a-${version_tag}.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-signed.apk "Komikku-x86-${version_tag}.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-signed.apk "Komikku-x86_64-${version_tag}.apk"
|
mv app/build/outputs/apk/release/app-x86_64-release-unsigned.apk "Komikku-x86_64-${version_tag}-unsigned.apk"
|
||||||
|
|
||||||
zip -qr "Komikku-mapping-${version_tag}.zip" app/build/outputs/mapping/release
|
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
|
||||||
|
|
||||||
|
# Catch corrupt / non-APK artifacts before upload (e.g. truncated build output). Same
|
||||||
|
# manifest parse path the installer uses for "Paket konnte nicht geparst werden".
|
||||||
|
aapt_bin=""
|
||||||
|
if [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}/build-tools" ]; then
|
||||||
|
aapt_bin="$(ls -1 "${ANDROID_HOME}"/build-tools/*/aapt 2>/dev/null | sort -V | tail -n 1 || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "${aapt_bin}" ] || [ ! -x "${aapt_bin}" ]; then
|
||||||
|
echo "aapt not found under ANDROID_HOME; cannot verify APK manifests."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for signed_apk in Komikku-*.apk; do
|
||||||
|
case "${signed_apk}" in *-unsigned.apk) continue ;; esac
|
||||||
|
echo "Verifying installable APK: ${signed_apk}"
|
||||||
|
if ! unzip -tqq "${signed_apk}" >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: ${signed_apk} is not a valid zip (truncated or corrupt build output?)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
first_line="$("${aapt_bin}" dump badging "${signed_apk}" 2>/dev/null | head -n 1 || true)"
|
||||||
|
if ! printf '%s\n' "${first_line}" | grep -q '^package:'; then
|
||||||
|
echo "ERROR: ${signed_apk} failed manifest parse (first line: ${first_line:-<empty>})."
|
||||||
|
echo "First 64 bytes (expect PK zip header 504b0304):"
|
||||||
|
head -c 64 "${signed_apk}" | hexdump -C || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sha256sum "${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
|
- name: Create or update release
|
||||||
id: release
|
id: release
|
||||||
|
|
@ -110,143 +446,330 @@ jobs:
|
||||||
env:
|
env:
|
||||||
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
VERSION_TAG: ${{ steps.prepare.outputs.VERSION_TAG }}
|
VERSION_TAG: ${{ steps.prepare.outputs.VERSION_TAG }}
|
||||||
|
VERSION_DISPLAY: ${{ steps.prepare.outputs.VERSION_DISPLAY }}
|
||||||
PREV_TAG_NAME: ${{ steps.prepare.outputs.PREV_TAG_NAME }}
|
PREV_TAG_NAME: ${{ steps.prepare.outputs.PREV_TAG_NAME }}
|
||||||
COMMIT_LOGS: ${{ steps.prepare.outputs.COMMIT_LOGS }}
|
|
||||||
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
|
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
|
||||||
REPOSITORY_NAME: ${{ github.repository }}
|
REPOSITORY_NAME: ${{ github.repository }}
|
||||||
|
FORGEJO_CURL_INSECURE: "true"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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"
|
api_base="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases"
|
||||||
payload_file="$(mktemp)"
|
payload_file="$(mktemp)"
|
||||||
response_file="$(mktemp)"
|
response_file="$(mktemp)"
|
||||||
|
|
||||||
python3 - <<'PY' > "$payload_file"
|
commit_logs=""
|
||||||
import json
|
if [ -n "${COMMIT_LOG_FILE:-}" ] && [ -f "${COMMIT_LOG_FILE}" ]; then
|
||||||
import os
|
commit_logs="$(cat "${COMMIT_LOG_FILE}")"
|
||||||
import textwrap
|
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
|
||||||
|
|
||||||
version_tag = os.environ["VERSION_TAG"]
|
full_changelog=""
|
||||||
previous_tag = os.environ.get("PREV_TAG_NAME", "").strip()
|
if [ -n "${PREV_TAG_NAME:-}" ]; then
|
||||||
commit_logs = os.environ.get("COMMIT_LOGS", "").strip() or "- Initial release"
|
full_changelog="**Full Changelog**: [${REPOSITORY_NAME}@${PREV_TAG_NAME}...${VERSION_TAG}](${REPO_URL}/compare/${PREV_TAG_NAME}...${VERSION_TAG})"
|
||||||
repo_url = os.environ["REPO_URL"]
|
fi
|
||||||
repository_name = os.environ["REPOSITORY_NAME"]
|
|
||||||
|
|
||||||
if previous_tag:
|
release_body_file="$(mktemp)"
|
||||||
full_changelog = (
|
{
|
||||||
f"**Full Changelog**: "
|
printf '%s\n' \
|
||||||
f"[{repository_name}@{previous_tag}...{version_tag}]"
|
"#### What's Changed" \
|
||||||
f"({repo_url}/compare/{previous_tag}...{version_tag})"
|
"" \
|
||||||
)
|
"${commit_logs}" \
|
||||||
else:
|
""
|
||||||
full_changelog = ""
|
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}"
|
||||||
|
|
||||||
body = textwrap.dedent(
|
# Build JSON — body from file only (jq --arg body "$huge" hits ARG_MAX).
|
||||||
f"""\
|
"${JQ_BIN}" -n \
|
||||||
#### What's Changed
|
--arg tag "${VERSION_TAG}" \
|
||||||
##### New
|
--arg title "Komikku ${VERSION_DISPLAY}" \
|
||||||
|
--rawfile body "${release_body_file}" \
|
||||||
|
'{tag_name: $tag, name: $title, body: $body, draft: false, prerelease: false}' > "${payload_file}"
|
||||||
|
|
||||||
##### Improve
|
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}"
|
||||||
|
|
||||||
##### Fix
|
status="$(curl "${curl_tls_args[@]}" -sS -o "$response_file" -w '%{http_code}' \
|
||||||
|
|
||||||
{commit_logs}
|
|
||||||
|
|
||||||
##### Based on
|
|
||||||
|
|
||||||
{full_changelog}
|
|
||||||
|
|
||||||
<!-->
|
|
||||||
> [!TIP]
|
|
||||||
>
|
|
||||||
> ### If you are unsure which version to download then go with `Komikku-{version_tag}.apk`
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
print(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"tag_name": version_tag,
|
|
||||||
"name": f"Komikku {version_tag}",
|
|
||||||
"body": body,
|
|
||||||
"draft": True,
|
|
||||||
"prerelease": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
PY
|
|
||||||
|
|
||||||
status="$(curl -sS -o "$response_file" -w '%{http_code}' \
|
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
"${api_base}/tags/${VERSION_TAG}")"
|
"${tag_get_url}")"
|
||||||
|
|
||||||
if [ "$status" = "200" ]; then
|
echo "release-by-tag GET HTTP status: ${status}"
|
||||||
release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' < "$response_file")"
|
if [ "${status}" != "200" ] && [ "${status}" != "404" ]; then
|
||||||
curl -fsS \
|
echo "Unexpected GET response body:"
|
||||||
-X PATCH \
|
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
--data @"$payload_file" \
|
|
||||||
"${api_base}/${release_id}" \
|
|
||||||
> "$response_file"
|
|
||||||
elif [ "$status" = "404" ]; then
|
|
||||||
curl -fsS \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
--data @"$payload_file" \
|
|
||||||
"${api_base}" \
|
|
||||||
> "$response_file"
|
|
||||||
release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' < "$response_file")"
|
|
||||||
else
|
|
||||||
cat "$response_file"
|
cat "$response_file"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "RELEASE_ID=$release_id" >> "$GITHUB_OUTPUT"
|
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
|
- name: Upload release assets
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
RELEASE_ID: ${{ steps.release.outputs.RELEASE_ID }}
|
RELEASE_ID: ${{ steps.release.outputs.RELEASE_ID }}
|
||||||
|
FORGEJO_CURL_INSECURE: "true"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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}"
|
release_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}"
|
||||||
assets_file="$(mktemp)"
|
assets_file="$(mktemp)"
|
||||||
|
err_body="$(mktemp)"
|
||||||
|
|
||||||
curl -fsS \
|
assets_status="$(curl "${curl_tls_args[@]}" -sS -o "$assets_file" -w '%{http_code}' \
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
"${release_api}/assets" \
|
"${release_api}/assets")"
|
||||||
> "$assets_file"
|
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
|
for file in Komikku-*.apk Komikku-mapping-*.zip; do
|
||||||
[ -e "$file" ] || continue
|
[ -e "$file" ] || continue
|
||||||
|
|
||||||
asset_name="$(basename "$file")"
|
if [ "${file#*-unsigned.apk}" != "${file}" ]; then
|
||||||
asset_id="$(
|
continue
|
||||||
python3 -c 'import json, sys; assets = json.load(open(sys.argv[1], encoding="utf-8")); name = sys.argv[2]; print(next((str(asset["id"]) for asset in assets if asset.get("name") == name), ""))' \
|
|
||||||
"$assets_file" \
|
|
||||||
"$asset_name"
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [ -n "$asset_id" ]; then
|
|
||||||
curl -fsS \
|
|
||||||
-X DELETE \
|
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
||||||
"${release_api}/assets/${asset_id}" \
|
|
||||||
> /dev/null
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
encoded_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$asset_name")"
|
asset_name="$(basename "$file")"
|
||||||
|
asset_id="$("${JQ_BIN}" -r --arg n "${asset_name}" '(.[] | select(.name == $n) | .id) // empty' "$assets_file")"
|
||||||
|
|
||||||
curl -fsS \
|
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 \
|
-X POST \
|
||||||
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
-H 'Content-Type: application/octet-stream' \
|
-H 'Content-Type: application/octet-stream' \
|
||||||
--data-binary @"$file" \
|
--data-binary @"$file" \
|
||||||
"${release_api}/assets?name=${encoded_name}" \
|
"${release_api}/assets?name=${encoded_name}")"
|
||||||
> /dev/null
|
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
|
done
|
||||||
| |||||||