Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebb60c3a99 | ||
|
|
a122fa27b9 | ||
|
|
fb96cf2436 | ||
|
|
1faf3cf961 | ||
|
|
416c8b07d2 | ||
|
|
32d4178875 | ||
|
|
697f7de9d1 |
@@ -2,6 +2,9 @@ name: Android Build & Publish
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -13,8 +16,37 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
# ── 0. Deduplication gate ─────────────────────────────────────────────────
|
||||||
|
# On this Gitea runner version the same push event is dispatched to
|
||||||
|
# multiple workers, creating duplicate runs. This step detects when a
|
||||||
|
# newer run for the same commit already exists and sets skip=true so all
|
||||||
|
# subsequent steps are no-ops, letting the duplicate finish in ~5 s.
|
||||||
|
- name: Dedup gate
|
||||||
|
id: dedup
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
CURRENT="${{ github.run_id }}"
|
||||||
|
HIGHEST=$(curl -sf \
|
||||||
|
"https://git.sal.giize.com/api/v1/repos/mozempk/scopone/actions/runs?limit=50" \
|
||||||
|
-H "Authorization: token $TOKEN" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
runs=json.load(sys.stdin).get('workflow_runs',[])
|
||||||
|
sha='${{ github.sha }}'
|
||||||
|
same=[r['id'] for r in runs if r['head_sha']==sha]
|
||||||
|
print(max(same) if same else $CURRENT)
|
||||||
|
" 2>/dev/null || echo "$CURRENT")
|
||||||
|
if [[ "$CURRENT" -lt "$HIGHEST" ]]; then
|
||||||
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Superseded by run $HIGHEST — skipping all build steps."
|
||||||
|
else
|
||||||
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Latest run ($CURRENT) — proceeding."
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 1. Source ────────────────────────────────────────────────────────────
|
# ── 1. Source ────────────────────────────────────────────────────────────
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Full history + tags required for the changelog step.
|
# Full history + tags required for the changelog step.
|
||||||
@@ -22,6 +54,7 @@ jobs:
|
|||||||
|
|
||||||
# ── 2. Java ──────────────────────────────────────────────────────────────
|
# ── 2. Java ──────────────────────────────────────────────────────────────
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
@@ -29,6 +62,7 @@ jobs:
|
|||||||
|
|
||||||
# ── 3. Node.js ───────────────────────────────────────────────────────────
|
# ── 3. Node.js ───────────────────────────────────────────────────────────
|
||||||
- name: Set up Node 22
|
- name: Set up Node 22
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
@@ -36,6 +70,7 @@ jobs:
|
|||||||
|
|
||||||
# ── 4. Android SDK ───────────────────────────────────────────────────────
|
# ── 4. Android SDK ───────────────────────────────────────────────────────
|
||||||
- name: Install Android SDK command-line tools
|
- name: Install Android SDK command-line tools
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
SDK_DIR="$HOME/android-sdk"
|
SDK_DIR="$HOME/android-sdk"
|
||||||
echo "ANDROID_HOME=$SDK_DIR" >> "$GITHUB_ENV"
|
echo "ANDROID_HOME=$SDK_DIR" >> "$GITHUB_ENV"
|
||||||
@@ -54,6 +89,7 @@ jobs:
|
|||||||
echo "$SDK_DIR/platform-tools" >> "$GITHUB_PATH"
|
echo "$SDK_DIR/platform-tools" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
- name: Accept SDK licenses & install platform/build-tools
|
- name: Accept SDK licenses & install platform/build-tools
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
# { yes || true } absorbs the SIGPIPE that 'yes' gets when sdkmanager closes
|
# { yes || true } absorbs the SIGPIPE that 'yes' gets when sdkmanager closes
|
||||||
# stdin after accepting all prompts — avoids exit-code 141 under 'pipefail'.
|
# stdin after accepting all prompts — avoids exit-code 141 under 'pipefail'.
|
||||||
@@ -65,6 +101,7 @@ jobs:
|
|||||||
|
|
||||||
# ── 5. Caches ────────────────────────────────────────────────────────────
|
# ── 5. Caches ────────────────────────────────────────────────────────────
|
||||||
- name: Cache Gradle files
|
- name: Cache Gradle files
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -75,9 +112,11 @@ jobs:
|
|||||||
|
|
||||||
# ── 6. JS build ──────────────────────────────────────────────────────────
|
# ── 6. JS build ──────────────────────────────────────────────────────────
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build web assets
|
- name: Build web assets
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
env:
|
env:
|
||||||
# Embed the CI run number as the app's build identifier so the
|
# Embed the CI run number as the app's build identifier so the
|
||||||
# in-app update check can compare against Gitea release tags.
|
# in-app update check can compare against Gitea release tags.
|
||||||
@@ -86,22 +125,37 @@ jobs:
|
|||||||
|
|
||||||
# ── 7. Capacitor sync ────────────────────────────────────────────────────
|
# ── 7. Capacitor sync ────────────────────────────────────────────────────
|
||||||
- name: Capacitor sync android
|
- name: Capacitor sync android
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
run: npx cap sync android
|
run: npx cap sync android
|
||||||
|
|
||||||
# ── 8. Android build ─────────────────────────────────────────────────────
|
# ── 8. Android build ─────────────────────────────────────────────────────
|
||||||
- name: Make gradlew executable
|
- name: Make gradlew executable
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
run: chmod +x android/gradlew
|
run: chmod +x android/gradlew
|
||||||
|
|
||||||
- name: Build Debug APK
|
- name: Build Debug APK
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
working-directory: android
|
working-directory: android
|
||||||
run: ./gradlew assembleDebug --no-daemon
|
run: |
|
||||||
|
# Retry up to 3 times to survive transient network errors when the
|
||||||
|
# Gradle wrapper downloads its distribution from GitHub CDN.
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
./gradlew assembleDebug --no-daemon && break
|
||||||
|
[ "$attempt" -lt 3 ] && echo "Attempt $attempt failed — retrying in 30s..." && sleep 30 || exit 1
|
||||||
|
done
|
||||||
|
|
||||||
- name: Build Release APK (unsigned — no signing key required)
|
- name: Build Release APK (unsigned — no signing key required)
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
working-directory: android
|
working-directory: android
|
||||||
run: ./gradlew assembleRelease --no-daemon
|
run: |
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
./gradlew assembleRelease --no-daemon && break
|
||||||
|
[ "$attempt" -lt 3 ] && echo "Attempt $attempt failed — retrying in 30s..." && sleep 30 || exit 1
|
||||||
|
done
|
||||||
|
|
||||||
# ── 9. Upload APKs as workflow artifacts ─────────────────────────────────
|
# ── 9. Upload APKs as workflow artifacts ─────────────────────────────────
|
||||||
- name: Upload APKs as artifacts
|
- name: Upload APKs as artifacts
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: scopone-android-${{ github.run_number }}
|
name: scopone-android-${{ github.run_number }}
|
||||||
@@ -112,6 +166,7 @@ jobs:
|
|||||||
|
|
||||||
# ── 10. Publish to Gitea generic package registry ────────────────────────
|
# ── 10. Publish to Gitea generic package registry ────────────────────────
|
||||||
- name: Publish APKs to Gitea package registry
|
- name: Publish APKs to Gitea package registry
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
@@ -149,6 +204,7 @@ jobs:
|
|||||||
# scopes. If the PAT lacks write:repository the curl will return 403 and
|
# scopes. If the PAT lacks write:repository the curl will return 403 and
|
||||||
# the step will fail — update the PAT scopes on the Gitea web UI.
|
# the step will fail — update the PAT scopes on the Gitea web UI.
|
||||||
- name: Create Gitea release
|
- name: Create Gitea release
|
||||||
|
if: ${{ steps.dedup.outputs.skip != 'true' }}
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
@@ -170,13 +226,20 @@ jobs:
|
|||||||
MD_LIST=$(echo "$COMMIT_LOG" | sed 's/^/- /')
|
MD_LIST=$(echo "$COMMIT_LOG" | sed 's/^/- /')
|
||||||
BODY=$(printf '%s' "$MD_LIST" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
|
BODY=$(printf '%s' "$MD_LIST" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
|
||||||
|
|
||||||
# ── Create the release ───────────────────────────────────────────────
|
# ── Create release (idempotent: reuse existing tag if already present) ───
|
||||||
RESP=$(curl -sf -X POST "$API/releases" \
|
EXISTING=$(curl -sf "$API/releases/tags/$TAG" \
|
||||||
-H "Authorization: token $TOKEN" \
|
-H "Authorization: token $TOKEN" || true)
|
||||||
-H "Content-Type: application/json" \
|
if [[ -n "$EXISTING" ]]; then
|
||||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"Build $VERSION\",\"body\":$BODY,\"draft\":false,\"prerelease\":false}")
|
RELEASE_ID=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||||
RELEASE_ID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
echo "Release $TAG already exists (id=$RELEASE_ID) — reusing."
|
||||||
echo "Created release $TAG (id=$RELEASE_ID)"
|
else
|
||||||
|
RESP=$(curl -sf -X POST "$API/releases" \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"Build $VERSION\",\"body\":$BODY,\"draft\":false,\"prerelease\":false}")
|
||||||
|
RELEASE_ID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||||
|
echo "Created release $TAG (id=$RELEASE_ID)"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Upload APKs as release assets ────────────────────────────────────
|
# ── Upload APKs as release assets ────────────────────────────────────
|
||||||
upload_asset() {
|
upload_asset() {
|
||||||
|
|||||||
66
.gitea/workflows/cleanup-packages.yml
Normal file
66
.gitea/workflows/cleanup-packages.yml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Cleanup old package versions
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
keep:
|
||||||
|
description: "Number of most-recent versions to keep"
|
||||||
|
required: false
|
||||||
|
default: "5"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Delete old scopone-android package versions
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
KEEP: ${{ github.event.inputs.keep || '5' }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
BASE="https://git.sal.giize.com/api/v1/packages/mozempk"
|
||||||
|
|
||||||
|
# Fetch all generic package versions for scopone-android (paginate)
|
||||||
|
ALL_VERSIONS=()
|
||||||
|
page=1
|
||||||
|
while true; do
|
||||||
|
BATCH=$(curl -sf "$BASE?type=generic&q=scopone-android&limit=50&page=$page" \
|
||||||
|
-H "Authorization: token $TOKEN")
|
||||||
|
COUNT=$(echo "$BATCH" | python3 -c "import sys,json; data=json.load(sys.stdin); print(len(data) if isinstance(data,list) else 0)")
|
||||||
|
if [[ "$COUNT" -eq 0 ]]; then break; fi
|
||||||
|
|
||||||
|
# Collect package IDs sorted by version number (numeric desc)
|
||||||
|
IDS=$(echo "$BATCH" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
pkgs=json.load(sys.stdin)
|
||||||
|
# id, version fields
|
||||||
|
for p in pkgs:
|
||||||
|
print(p['id'], p.get('version','0'))
|
||||||
|
")
|
||||||
|
while IFS=' ' read -r pid ver; do
|
||||||
|
ALL_VERSIONS+=("$pid $ver")
|
||||||
|
done <<< "$IDS"
|
||||||
|
|
||||||
|
page=$((page + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
TOTAL=${#ALL_VERSIONS[@]}
|
||||||
|
echo "Total versions found: $TOTAL"
|
||||||
|
if [[ $TOTAL -le $KEEP ]]; then
|
||||||
|
echo "Nothing to delete (total=$TOTAL <= keep=$KEEP)."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sort by version number numerically descending, keep first $KEEP
|
||||||
|
SORTED=$(printf '%s\n' "${ALL_VERSIONS[@]}" | sort -t' ' -k2 -Vr)
|
||||||
|
TO_DELETE=$(echo "$SORTED" | tail -n +"$((KEEP + 1))" | awk '{print $1}')
|
||||||
|
DELETE_COUNT=$(echo "$TO_DELETE" | wc -l)
|
||||||
|
echo "Deleting $DELETE_COUNT versions (keeping $KEEP most recent)…"
|
||||||
|
|
||||||
|
while IFS= read -r pid; do
|
||||||
|
HTTP=$(curl -sf -o /dev/null -w "%{http_code}" \
|
||||||
|
-X DELETE "$BASE/$pid" \
|
||||||
|
-H "Authorization: token $TOKEN")
|
||||||
|
echo " deleted package id=$pid → HTTP $HTTP"
|
||||||
|
done <<< "$TO_DELETE"
|
||||||
|
echo "Done."
|
||||||
Reference in New Issue
Block a user