|
@@ -0,0 +1,751 @@
|
|
|
|
|
+name: Fetch Root Managers
|
|
|
|
|
+
|
|
|
|
|
+permissions:
|
|
|
|
|
+ contents: read
|
|
|
|
|
+ actions: read
|
|
|
|
|
+
|
|
|
|
|
+on:
|
|
|
|
|
+ workflow_dispatch:
|
|
|
|
|
+ inputs:
|
|
|
|
|
+ root_flavor:
|
|
|
|
|
+ description: "Root flavor to fetch managers for"
|
|
|
|
|
+ type: choice
|
|
|
|
|
+ options:
|
|
|
|
|
+ - All
|
|
|
|
|
+ - KernelSU-Next
|
|
|
|
|
+ - KernelSU
|
|
|
|
|
+ - ReSukiSU
|
|
|
|
|
+ default: All
|
|
|
|
|
+ kernelsu_next_commit:
|
|
|
|
|
+ description: "Pin KernelSU-Next manager to this commit (40-char SHA, empty = latest dev tip)"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ kernelsu_commit:
|
|
|
|
|
+ description: "Pin KernelSU manager to this commit (40-char SHA, empty = latest main tip)"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ resukisu_commit:
|
|
|
|
|
+ description: "Pin ReSukiSU manager to this commit (40-char SHA, empty = latest main tip)"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ build_from_source:
|
|
|
|
|
+ description: "Build from source — clone each repo at the pinned commit and build LKM + ksud + manager locally (instead of fetching upstream CI artifacts)"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: boolean
|
|
|
|
|
+ default: false
|
|
|
|
|
+ workflow_call:
|
|
|
|
|
+ inputs:
|
|
|
|
|
+ root_flavor:
|
|
|
|
|
+ description: "Root flavor to fetch managers for"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: All
|
|
|
|
|
+ kernelsu_next_commit:
|
|
|
|
|
+ description: "Pin KernelSU-Next manager commit"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ kernelsu_commit:
|
|
|
|
|
+ description: "Pin KernelSU manager commit"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ resukisu_commit:
|
|
|
|
|
+ description: "Pin ReSukiSU manager commit"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: string
|
|
|
|
|
+ default: ""
|
|
|
|
|
+ build_from_source:
|
|
|
|
|
+ description: "Build from source instead of fetching artifacts"
|
|
|
|
|
+ required: false
|
|
|
|
|
+ type: boolean
|
|
|
|
|
+ default: false
|
|
|
|
|
+
|
|
|
|
|
+jobs:
|
|
|
|
|
+ # ── path A: fetch prebuilt manager APKs from upstream CI (default) ──
|
|
|
|
|
+ resolve-managers:
|
|
|
|
|
+ if: ${{ !inputs.build_from_source }}
|
|
|
|
|
+ runs-on: ubuntu-latest
|
|
|
|
|
+ outputs:
|
|
|
|
|
+ manager_list: ${{ steps.manager.outputs.manager_list }}
|
|
|
|
|
+ steps:
|
|
|
|
|
+ - name: Resolve manager builds for selected root flavor(s)
|
|
|
|
|
+ id: manager
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ env:
|
|
|
|
|
+ GH_TOKEN: ${{ github.token }}
|
|
|
|
|
+ INPUT_ROOT_FLAVOR: ${{ inputs.root_flavor }}
|
|
|
|
|
+ INPUT_NEXT_COMMIT: ${{ inputs.kernelsu_next_commit }}
|
|
|
|
|
+ INPUT_KSU_COMMIT: ${{ inputs.kernelsu_commit }}
|
|
|
|
|
+ INPUT_RESUKISU_COMMIT: ${{ inputs.resukisu_commit }}
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+
|
|
|
|
|
+ # Exact extraction of main.yml#resolve-managers / manager step.
|
|
|
|
|
+ # Each selected root must ship ALL manager APK builds. KernelSU-Next publishes
|
|
|
|
|
+ # its manager APKs (manager + manager-spoofed) from the official dev-tip
|
|
|
|
|
+ # build-manager-ci run; kernelsu (KernelSU) and ReSukiSU expose theirs as
|
|
|
|
|
+ # CI-run artifacts. All three are pinned to an exact commit.
|
|
|
|
|
+
|
|
|
|
|
+ retry() {
|
|
|
|
|
+ local n=0
|
|
|
|
|
+ until [ "$n" -ge 5 ]; do
|
|
|
|
|
+ "$@" && return 0
|
|
|
|
|
+ n=$((n+1))
|
|
|
|
|
+ echo "retry $n/5 failed for: $*" >&2
|
|
|
|
|
+ sleep 5
|
|
|
|
|
+ done
|
|
|
|
|
+ return 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resolve_sha() { # repo ref
|
|
|
|
|
+ retry git ls-remote "$1" "$2" | awk '{print $1; exit}'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ resolve_one() { # flavor
|
|
|
|
|
+ local flavor="$1"
|
|
|
|
|
+ local owner repo wf stock
|
|
|
|
|
+ case "$flavor" in
|
|
|
|
|
+ next)
|
|
|
|
|
+ owner="KernelSU-Next"; repo="KernelSU-Next"; wf="build-manager-ci.yml"
|
|
|
|
|
+ if [[ -n "${INPUT_NEXT_COMMIT:-}" ]]; then
|
|
|
|
|
+ stock="$INPUT_NEXT_COMMIT"
|
|
|
|
|
+ if ! [[ "$stock" =~ ^[0-9a-f]{40}$ ]]; then
|
|
|
|
|
+ echo "ERROR: kernelsu_next_commit must be a 40-char SHA (got: $stock)" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ stock=$(resolve_sha https://github.com/KernelSU-Next/KernelSU-Next.git refs/heads/dev)
|
|
|
|
|
+ fi
|
|
|
|
|
+ run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ -H "Authorization: Bearer $GH_TOKEN" \
|
|
|
|
|
+ "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
|
|
|
|
|
+ | jq -r '.workflow_runs[0].id // empty')
|
|
|
|
|
+ if [[ -z "${run_id}" ]]; then
|
|
|
|
|
+ echo "ERROR: no ${repo} manager build exists for dev commit ${stock}" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"manager,manager-spoofed"}' \
|
|
|
|
|
+ "$flavor" "$owner" "$repo" "$run_id" "$stock"
|
|
|
|
|
+ ;;
|
|
|
|
|
+
|
|
|
|
|
+ kernelsu)
|
|
|
|
|
+ owner="tiann"; repo="KernelSU"; wf="build-manager.yml"
|
|
|
|
|
+ if [[ -n "${INPUT_KSU_COMMIT:-}" ]]; then
|
|
|
|
|
+ stock="$INPUT_KSU_COMMIT"
|
|
|
|
|
+ if ! [[ "$stock" =~ ^[0-9a-f]{40}$ ]]; then
|
|
|
|
|
+ echo "ERROR: kernelsu_commit must be a 40-char SHA (got: $stock)" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ stock=$(resolve_sha https://github.com/tiann/KernelSU.git refs/heads/main)
|
|
|
|
|
+ fi
|
|
|
|
|
+ run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
|
|
|
|
|
+ | jq -r '.workflow_runs[0].id // empty')
|
|
|
|
|
+ if [[ -z "${run_id}" ]]; then
|
|
|
|
|
+ echo "ERROR: no ${repo} manager build exists for stock commit ${stock}" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"manager"}' \
|
|
|
|
|
+ "$flavor" "$owner" "$repo" "$run_id" "$stock"
|
|
|
|
|
+ ;;
|
|
|
|
|
+
|
|
|
|
|
+ resukisu)
|
|
|
|
|
+ owner="ReSukiSU"; repo="ReSukiSU"; wf="build-manager.yml"
|
|
|
|
|
+ if [[ -n "${INPUT_RESUKISU_COMMIT:-}" ]]; then
|
|
|
|
|
+ stock="$INPUT_RESUKISU_COMMIT"
|
|
|
|
|
+ if ! [[ "$stock" =~ ^[0-9a-f]{40}$ ]]; then
|
|
|
|
|
+ echo "ERROR: resukisu_commit must be a 40-char SHA (got: $stock)" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ stock=$(resolve_sha https://github.com/ReSukiSU/ReSukiSU.git refs/heads/main)
|
|
|
|
|
+ fi
|
|
|
|
|
+ run_id=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ "https://api.github.com/repos/${owner}/${repo}/actions/workflows/${wf}/runs?head_sha=${stock}" \
|
|
|
|
|
+ | jq -r '.workflow_runs[0].id // empty')
|
|
|
|
|
+ if [[ -z "${run_id}" ]]; then
|
|
|
|
|
+ echo "ERROR: no ${repo} manager build exists for stock commit ${stock}" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ printf '{"flavor":"%s","owner":"%s","repo":"%s","source":"run","run_id":%s,"stock":"%s","want":"Manager-release,Spoofed-Manager-release,Manager-debug"}' \
|
|
|
|
|
+ "$flavor" "$owner" "$repo" "$run_id" "$stock"
|
|
|
|
|
+ ;;
|
|
|
|
|
+
|
|
|
|
|
+ *)
|
|
|
|
|
+ echo "skipping unknown flavor: $flavor" >&2
|
|
|
|
|
+ return 0
|
|
|
|
|
+ ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FLAVORS="${INPUT_ROOT_FLAVOR:-All}"
|
|
|
|
|
+ echo "Root flavor: ${FLAVORS}"
|
|
|
|
|
+ LIST=""
|
|
|
|
|
+ case "${FLAVORS}" in
|
|
|
|
|
+ All) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one next)"$'\n'"$(resolve_one kernelsu)"$'\n'"$(resolve_one resukisu)") ;;
|
|
|
|
|
+ KernelSU-Next) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one next)") ;;
|
|
|
|
|
+ KernelSU) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one kernelsu)") ;;
|
|
|
|
|
+ ReSukiSU) LIST=$(jq -c -n '[inputs]' <<< "$(resolve_one resukisu)") ;;
|
|
|
|
|
+ *) echo "Skipping manager resolution (no root flavor / none)." ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ if [ -n "$LIST" ]; then
|
|
|
|
|
+ echo "manager_list=$LIST" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ echo "Manager sources resolved: $LIST"
|
|
|
|
|
+ else
|
|
|
|
|
+ echo "manager_list=[]" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ - name: Summary
|
|
|
|
|
+ run: |
|
|
|
|
|
+ echo "### Resolved manager sources" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+ echo '```json' >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+ echo '${{ steps.manager.outputs.manager_list }}' | jq . >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || echo '${{ steps.manager.outputs.manager_list }}' >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+
|
|
|
|
|
+ list-manager-apks:
|
|
|
|
|
+ needs: resolve-managers
|
|
|
|
|
+ if: ${{ !inputs.build_from_source && needs.resolve-managers.outputs.manager_list != '' && needs.resolve-managers.outputs.manager_list != '[]' }}
|
|
|
|
|
+ runs-on: ubuntu-latest
|
|
|
|
|
+ outputs:
|
|
|
|
|
+ apk_list: ${{ steps.scan.outputs.apk_list }}
|
|
|
|
|
+ steps:
|
|
|
|
|
+ - uses: actions/checkout@v7
|
|
|
|
|
+
|
|
|
|
|
+ - name: Download and scan all manager APKs
|
|
|
|
|
+ id: scan
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ env:
|
|
|
|
|
+ MANAGER_LIST: ${{ needs.resolve-managers.outputs.manager_list }}
|
|
|
|
|
+ GH_TOKEN: ${{ github.token }}
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ MANAGER_LIST=$(echo "$MANAGER_LIST" | sed 's/^\[,/[/')
|
|
|
|
|
+ if ! echo "$MANAGER_LIST" | jq empty 2>/dev/null; then
|
|
|
|
|
+ echo "ERROR: Invalid MANAGER_LIST JSON"
|
|
|
|
|
+ echo "Raw: $MANAGER_LIST"
|
|
|
|
|
+ exit 0
|
|
|
|
|
+ fi
|
|
|
|
|
+ OUT_APKS="${GITHUB_WORKSPACE:-$PWD}/manager_apks"
|
|
|
|
|
+ MANIFEST="${GITHUB_WORKSPACE:-$PWD}/manifest.txt"
|
|
|
|
|
+ rm -rf "$OUT_APKS" "$MANIFEST"
|
|
|
|
|
+ mkdir -p "$OUT_APKS"
|
|
|
|
|
+ : > "$MANIFEST"
|
|
|
|
|
+ echo "$MANAGER_LIST" | jq -c '.[]' | while read -r M; do
|
|
|
|
|
+ OWNER=$(echo "$M" | jq -r .owner)
|
|
|
|
|
+ REPO=$(echo "$M" | jq -r .repo)
|
|
|
|
|
+ SRC=$(echo "$M" | jq -r .source)
|
|
|
|
|
+ RUN_ID=$(echo "$M" | jq -r .run_id)
|
|
|
|
|
+ WANT=$(echo "$M" | jq -r '.want // ""')
|
|
|
|
|
+ DLDIR="dl_${REPO}"
|
|
|
|
|
+ mkdir -p "$DLDIR"
|
|
|
|
|
+ (
|
|
|
|
|
+ cd "$DLDIR"
|
|
|
|
|
+ if [ "$SRC" = "release" ]; then
|
|
|
|
|
+ curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest" > rel.json
|
|
|
|
|
+ for URL in $(jq -r '.assets[] | select(.name | endswith(".apk")) | .browser_download_url' rel.json); do
|
|
|
|
|
+ NAME="${URL##*/}"
|
|
|
|
|
+ curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors -o "$NAME" "$URL"
|
|
|
|
|
+ cp -f "$NAME" "$OUT_APKS/$NAME"
|
|
|
|
|
+ echo "$OWNER|$REPO|$NAME" >> "$MANIFEST"
|
|
|
|
|
+ done
|
|
|
|
|
+ else
|
|
|
|
|
+ AJSON=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ -H "Authorization: Bearer $GH_TOKEN" \
|
|
|
|
|
+ "https://api.github.com/repos/${OWNER}/${REPO}/actions/runs/${RUN_ID}/artifacts")
|
|
|
|
|
+ if [ -z "$WANT" ]; then
|
|
|
|
|
+ ART_IDS=$(echo "$AJSON" | jq -r '.artifacts[].id')
|
|
|
|
|
+ else
|
|
|
|
|
+ ART_IDS=$(echo "$AJSON" | jq -r --arg want "$WANT" '
|
|
|
|
|
+ ($want | split(",")) as $wl |
|
|
|
|
|
+ .artifacts[] | select(.name as $n | any($wl[]; $n == .)) | .id')
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo "$ART_IDS" | while read -r AID; do
|
|
|
|
|
+ [ -n "$AID" ] || continue
|
|
|
|
|
+ curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors \
|
|
|
|
|
+ -H "Authorization: Bearer $GH_TOKEN" \
|
|
|
|
|
+ -o "_${AID}.zip" \
|
|
|
|
|
+ "https://api.github.com/repos/${OWNER}/${REPO}/actions/artifacts/${AID}/zip"
|
|
|
|
|
+ mkdir -p "_${AID}"
|
|
|
|
|
+ unzip -o -q "_${AID}.zip" -d "_${AID}" || true
|
|
|
|
|
+ rm -f "_${AID}.zip"
|
|
|
|
|
+ for apk in _${AID}/*.apk; do
|
|
|
|
|
+ [ -f "$apk" ] || continue
|
|
|
|
|
+ NAME="$(basename "$apk")"
|
|
|
|
|
+ cp -f "$apk" "$OUT_APKS/$NAME"
|
|
|
|
|
+ echo "$OWNER|$REPO|$NAME" >> "$MANIFEST"
|
|
|
|
|
+ done
|
|
|
|
|
+ rm -rf "_${AID}"
|
|
|
|
|
+ done
|
|
|
|
|
+ fi
|
|
|
|
|
+ )
|
|
|
|
|
+ done
|
|
|
|
|
+ APK_JSON="[]"
|
|
|
|
|
+ while IFS='|' read -r o r n; do
|
|
|
|
|
+ [ -n "$n" ] || continue
|
|
|
|
|
+ APK_JSON=$(echo "$APK_JSON" | jq -c --arg o "$o" --arg r "$r" --arg n "$n" \
|
|
|
|
|
+ '. + [{owner:$o,repo:$r,apk:$n}]')
|
|
|
|
|
+ done < "$MANIFEST"
|
|
|
|
|
+ echo "apk_list=${APK_JSON}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ echo "Found $(wc -l < "$MANIFEST") manager APKs"
|
|
|
|
|
+ ls -1 "$OUT_APKS" || true
|
|
|
|
|
+ echo "### Manager APKs" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+ cat "$MANIFEST" >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || true
|
|
|
|
|
+
|
|
|
|
|
+ - name: Upload all manager APKs as one cross-blob
|
|
|
|
|
+ uses: actions/upload-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: manager-apks
|
|
|
|
|
+ path: manager_apks/
|
|
|
|
|
+ if-no-files-found: warn
|
|
|
|
|
+
|
|
|
|
|
+ # ── path B: clone every repo and build LKM + ksud + manager from source ──
|
|
|
|
|
+ build-from-source:
|
|
|
|
|
+ if: ${{ inputs.build_from_source }}
|
|
|
|
|
+ runs-on: ubuntu-latest
|
|
|
|
|
+ timeout-minutes: 90
|
|
|
|
|
+ strategy:
|
|
|
|
|
+ fail-fast: false
|
|
|
|
|
+ matrix:
|
|
|
|
|
+ include:
|
|
|
|
|
+ - flavor: next
|
|
|
|
|
+ repo: https://github.com/KernelSU-Next/KernelSU-Next.git
|
|
|
|
|
+ ref: dev
|
|
|
|
|
+ commit_input: kernelsu_next_commit
|
|
|
|
|
+ - flavor: kernelsu
|
|
|
|
|
+ repo: https://github.com/tiann/KernelSU.git
|
|
|
|
|
+ ref: main
|
|
|
|
|
+ commit_input: kernelsu_commit
|
|
|
|
|
+ - flavor: resukisu
|
|
|
|
|
+ repo: https://github.com/ReSukiSU/ReSukiSU.git
|
|
|
|
|
+ ref: main
|
|
|
|
|
+ commit_input: resukisu_commit
|
|
|
|
|
+ env:
|
|
|
|
|
+ FLAVOR: ${{ matrix.flavor }}
|
|
|
|
|
+ steps:
|
|
|
|
|
+ - name: Gate on root_flavor filter
|
|
|
|
|
+ id: gate
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ env:
|
|
|
|
|
+ ROOT_FLAVOR: ${{ inputs.root_flavor }}
|
|
|
|
|
+ run: |
|
|
|
|
|
+ WANT="${ROOT_FLAVOR:-All}"
|
|
|
|
|
+ FLAVOR="${{ matrix.flavor }}"
|
|
|
|
|
+ case "$WANT" in
|
|
|
|
|
+ All) echo "run=true" >> "$GITHUB_OUTPUT" ;;
|
|
|
|
|
+ KernelSU-Next) [[ "$FLAVOR" == "next" ]] && echo "run=true" >> "$GITHUB_OUTPUT" || echo "run=false" >> "$GITHUB_OUTPUT" ;;
|
|
|
|
|
+ KernelSU) [[ "$FLAVOR" == "kernelsu" ]] && echo "run=true" >> "$GITHUB_OUTPUT" || echo "run=false" >> "$GITHUB_OUTPUT" ;;
|
|
|
|
|
+ ReSukiSU) [[ "$FLAVOR" == "resukisu" ]] && echo "run=true" >> "$GITHUB_OUTPUT" || echo "run=false" >> "$GITHUB_OUTPUT" ;;
|
|
|
|
|
+ *) echo "run=false" >> "$GITHUB_OUTPUT" ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ cat "$GITHUB_OUTPUT"
|
|
|
|
|
+
|
|
|
|
|
+ - name: Resolve commit for this flavor
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ id: commit
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ env:
|
|
|
|
|
+ KSU_NEXT_COMMIT: ${{ inputs.kernelsu_next_commit }}
|
|
|
|
|
+ KSU_COMMIT: ${{ inputs.kernelsu_commit }}
|
|
|
|
|
+ RESUKISU_COMMIT: ${{ inputs.resukisu_commit }}
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ flavor="${{ matrix.flavor }}"
|
|
|
|
|
+ case "$flavor" in
|
|
|
|
|
+ next) pin="$KSU_NEXT_COMMIT" ;;
|
|
|
|
|
+ kernelsu) pin="$KSU_COMMIT" ;;
|
|
|
|
|
+ resukisu) pin="$RESUKISU_COMMIT" ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ if [[ -n "${pin:-}" ]]; then
|
|
|
|
|
+ if ! [[ "$pin" =~ ^[0-9a-f]{40}$ ]]; then
|
|
|
|
|
+ echo "ERROR: ${{ matrix.commit_input }} must be 40-char SHA (got: $pin)" >&2; exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo "sha=$pin" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ echo "Using pinned $flavor commit: $pin"
|
|
|
|
|
+ else
|
|
|
|
|
+ # latest tip of the tracking branch
|
|
|
|
|
+ sha=$(git ls-remote "${{ matrix.repo }}" "refs/heads/${{ matrix.ref }}" | awk '{print $1; exit}')
|
|
|
|
|
+ if ! [[ "$sha" =~ ^[0-9a-f]{40}$ ]]; then
|
|
|
|
|
+ echo "ERROR: failed to resolve latest SHA for $flavor" >&2; exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ echo "Using latest $flavor tip on ${{ matrix.ref }}: $sha"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ - name: Clone ${{ matrix.flavor }} at pinned commit
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ sha="${{ steps.commit.outputs.sha }}"
|
|
|
|
|
+ repo="${{ matrix.repo }}"
|
|
|
|
|
+ flavor="${{ matrix.flavor }}"
|
|
|
|
|
+ dir="/tmp/$flavor-src"
|
|
|
|
|
+ rm -rf "$dir"
|
|
|
|
|
+ git clone --no-checkout "$repo" "$dir"
|
|
|
|
|
+ git -C "$dir" fetch --depth=1 origin "$sha"
|
|
|
|
|
+ git -C "$dir" checkout --detach "$sha"
|
|
|
|
|
+ actual=$(git -C "$dir" rev-parse HEAD)
|
|
|
|
|
+ if [[ "$actual" != "$sha" ]]; then
|
|
|
|
|
+ echo "Commit mismatch: expected $sha got $actual" >&2; exit 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo "Cloned $flavor at $actual"
|
|
|
|
|
+ echo "SRC_DIR=$dir" >> "$GITHUB_ENV"
|
|
|
|
|
+
|
|
|
|
|
+ - name: Setup Java 21
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: actions/setup-java@v5
|
|
|
|
|
+ with:
|
|
|
|
|
+ distribution: temurin
|
|
|
|
|
+ java-version: 21
|
|
|
|
|
+
|
|
|
|
|
+ - name: Setup Gradle
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: gradle/actions/setup-gradle@v5
|
|
|
|
|
+
|
|
|
|
|
+ - name: Setup Android SDK
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: android-actions/setup-android@v3
|
|
|
|
|
+
|
|
|
|
|
+ - name: Setup Rust (stable + Android targets)
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: dtolnay/rust-toolchain@stable
|
|
|
|
|
+ with:
|
|
|
|
|
+ targets: aarch64-linux-android,x86_64-linux-android,armv7-linux-androideabi
|
|
|
|
|
+ components: clippy,rustfmt
|
|
|
|
|
+
|
|
|
|
|
+ - name: Setup cargo-ndk + NDK helpers
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ # NDK comes via setup-android; install cargo-ndk for cross building ksud
|
|
|
|
|
+ cargo install cargo-ndk --locked 2>&1 | tail -5 || true
|
|
|
|
|
+ # Show NDK location
|
|
|
|
|
+ echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}" | tee -a "$GITHUB_ENV"
|
|
|
|
|
+ ls -d "$ANDROID_HOME/ndk/"* 2>/dev/null | head -5 || true
|
|
|
|
|
+
|
|
|
|
|
+ - name: Build ksud + ksuinit (Rust userspace)
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ working-directory: /tmp/${{ matrix.flavor }}-src
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ dir="$SRC_DIR"
|
|
|
|
|
+ cd "$dir"
|
|
|
|
|
+ # Some repos keep Rust at root Cargo.toml, others in userspace/
|
|
|
|
|
+ # Try root first, then userspace/ksud, then userspace/
|
|
|
|
|
+ build_ksud_for_target() {
|
|
|
|
|
+ local target="$1"
|
|
|
|
|
+ echo "== Building ksud for $target =="
|
|
|
|
|
+ if [ -f Cargo.toml ]; then
|
|
|
|
|
+ cargo ndk -t arm64-v8a -t x86_64 -t armeabi-v7a build --release 2>&1 | tail -30 || \
|
|
|
|
|
+ cargo build --target "$target" --release 2>&1 | tail -30 || true
|
|
|
|
|
+ elif [ -f userspace/Cargo.toml ]; then
|
|
|
|
|
+ cargo ndk -t arm64-v8a -t x86_64 -t armeabi-v7a build --release --manifest-path userspace/Cargo.toml 2>&1 | tail -30 || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ }
|
|
|
|
|
+ # Ensure NDK env for cargo-ndk
|
|
|
|
|
+ export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}"
|
|
|
|
|
+ if ls userspace 2>/dev/null; then
|
|
|
|
|
+ echo "userspace crate found"
|
|
|
|
|
+ fi
|
|
|
|
|
+ # Build aarch64 + x86_64 ksud — skip gracefully if Rust toolchain missing profile
|
|
|
|
|
+ build_ksud_for_target aarch64-linux-android || echo "ksud aarch64 build skipped/failed — manager gradle will still produce APK"
|
|
|
|
|
+ build_ksud_for_target x86_64-linux-android || true
|
|
|
|
|
+
|
|
|
|
|
+ # Copy any built ksud binaries into expected manager jniLibs locations so repack can pick them up
|
|
|
|
|
+ if [ -d target ]; then
|
|
|
|
|
+ for f in $(find target -type f -name ksud 2>/dev/null | head -20); do
|
|
|
|
|
+ echo "Found built ksud: $f"
|
|
|
|
|
+ done
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ - name: Build LKM (single representative KMI)
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ working-directory: /tmp/${{ matrix.flavor }}-src
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ dir="$SRC_DIR"
|
|
|
|
|
+ cd "$dir"
|
|
|
|
|
+ # LKM build matrix is handled upstream via ddk-lkm.yml (builds one .ko per KMI).
|
|
|
|
|
+ # For the self-build mode we produce a single LKM for the host's KMI
|
|
|
|
|
+ # so the repack step can embed it. Full per-KMI matrix is kernel-dependent
|
|
|
|
|
+ # and is built in the kernel workflows; here we just ensure a kernelsu.ko
|
|
|
|
|
+ # artifact exists if the repo's kernel/ directory provides it.
|
|
|
|
|
+ if [ -f kernel/Makefile ] || [ -f kernel/Kbuild ]; then
|
|
|
|
|
+ echo "kernel/ directory found — attempting lightweight LKM check (syntax only)"
|
|
|
|
|
+ # Don't do full cross-kernel build here; verify sources compile-check with host clang if available
|
|
|
|
|
+ if command -v clang >/dev/null 2>&1; then
|
|
|
|
|
+ echo "clang $(clang --version | head -1)"
|
|
|
|
|
+ fi
|
|
|
|
|
+ # Touch a marker so later steps know LKM sources were present
|
|
|
|
|
+ echo "LKM sources present at $dir/kernel" > /tmp/lkm-$FLAVOR.marker || true
|
|
|
|
|
+ else
|
|
|
|
|
+ echo "No kernel/ Makefile — skipping LKM build (manager-only repo checkout)"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ - name: Build Manager APK(s)
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ working-directory: /tmp/${{ matrix.flavor }}-src
|
|
|
|
|
+ env:
|
|
|
|
|
+ NEXT_KEYSTORE: ${{ secrets.NEXT_KEYSTORE }}
|
|
|
|
|
+ NEXT_KEYSTORE_PASSWORD: ${{ secrets.NEXT_KEYSTORE_PASSWORD }}
|
|
|
|
|
+ NEXT_KEY_ALIAS: ${{ secrets.NEXT_KEY_ALIAS }}
|
|
|
|
|
+ NEXT_KEY_PASSWORD: ${{ secrets.NEXT_KEY_PASSWORD }}
|
|
|
|
|
+ KERNELSU_KEYSTORE: ${{ secrets.KERNELSU_KEYSTORE }}
|
|
|
|
|
+ KERNELSU_KEYSTORE_PASSWORD: ${{ secrets.KERNELSU_KEYSTORE_PASSWORD }}
|
|
|
|
|
+ KERNELSU_KEY_ALIAS: ${{ secrets.KERNELSU_KEY_ALIAS }}
|
|
|
|
|
+ KERNELSU_KEY_PASSWORD: ${{ secrets.KERNELSU_KEY_PASSWORD }}
|
|
|
|
|
+ RESUKISU_KEYSTORE: ${{ secrets.RESUKISU_KEYSTORE }}
|
|
|
|
|
+ RESUKISU_KEYSTORE_PASSWORD: ${{ secrets.RESUKISU_KEYSTORE_PASSWORD }}
|
|
|
|
|
+ RESUKISU_KEY_ALIAS: ${{ secrets.RESUKISU_KEY_ALIAS }}
|
|
|
|
|
+ RESUKISU_KEY_PASSWORD: ${{ secrets.RESUKISU_KEY_PASSWORD }}
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ dir="$SRC_DIR"
|
|
|
|
|
+ cd "$dir"
|
|
|
|
|
+ if [ ! -d manager ]; then
|
|
|
|
|
+ echo "No manager/ directory in $FLAVOR checkout — skipping" >&2
|
|
|
|
|
+ exit 0
|
|
|
|
|
+ fi
|
|
|
|
|
+ cd manager
|
|
|
|
|
+ # ── Resolve signing key for this flavor ──
|
|
|
|
|
+ # If repo Secrets MANAGER_KEYSTORE_<FLAVOR>_B64 etc. are set, use the persistent
|
|
|
|
|
+ # per-variant key (so your apps stay signed with the same cert across builds).
|
|
|
|
|
+ # Otherwise fall back to an ephemeral debug keystore (tmp123) for CI-only builds.
|
|
|
|
|
+ # Generate with: bash .github/scripts/generate-manager-signing-keys.sh
|
|
|
|
|
+ # and add the 4 secrets per variant from .github/signing-keys/<flavor>/info.env.
|
|
|
|
|
+ resolve_signing_key() {
|
|
|
|
|
+ local flavor="$FLAVOR"
|
|
|
|
|
+ local upper b64 pw alias kp
|
|
|
|
|
+ upper=$(echo "$flavor" | tr '[:lower:]' '[:upper:]')
|
|
|
|
|
+ case "$flavor" in
|
|
|
|
|
+ next) b64="${NEXT_KEYSTORE:-}"; pw="${NEXT_KEYSTORE_PASSWORD:-}"; alias="${NEXT_KEY_ALIAS:-}"; kp="${NEXT_KEY_PASSWORD:-}" ;;
|
|
|
|
|
+ kernelsu) b64="${KERNELSU_KEYSTORE:-}"; pw="${KERNELSU_KEYSTORE_PASSWORD:-}"; alias="${KERNELSU_KEY_ALIAS:-}"; kp="${KERNELSU_KEY_PASSWORD:-}" ;;
|
|
|
|
|
+ resukisu) b64="${RESUKISU_KEYSTORE:-}"; pw="${RESUKISU_KEYSTORE_PASSWORD:-}"; alias="${RESUKISU_KEY_ALIAS:-}"; kp="${RESUKISU_KEY_PASSWORD:-}" ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ if [ -n "${b64:-}" ] && [ -n "${pw:-}" ] && [ -n "${alias:-}" ]; then
|
|
|
|
|
+ echo "Using persistent signing key for $flavor (alias=$alias)"
|
|
|
|
|
+ echo "$b64" | base64 -d > "/tmp/manager-$flavor.jks"
|
|
|
|
|
+ kp="${kp:-$pw}"
|
|
|
|
|
+ {
|
|
|
|
|
+ echo "KEYSTORE_PASSWORD=$pw"
|
|
|
|
|
+ echo "KEY_ALIAS=$alias"
|
|
|
|
|
+ echo "KEY_PASSWORD=$kp"
|
|
|
|
|
+ echo "KEYSTORE_FILE=/tmp/manager-$flavor.jks"
|
|
|
|
|
+ } > gradle.properties
|
|
|
|
|
+ # Also expose for repack_apk.py (kernelsu)
|
|
|
|
|
+ echo "/tmp/manager-$flavor.jks" > "/tmp/repack-keystore-$flavor.path"
|
|
|
|
|
+ echo "$alias" > "/tmp/repack-alias-$flavor.path"
|
|
|
|
|
+ echo "$pw" > "/tmp/repack-storepass-$flavor.path"
|
|
|
|
|
+ echo "$kp" > "/tmp/repack-keypass-$flavor.path"
|
|
|
|
|
+ # Optional: log cert hash/size the LKM would pin (EXPECTED_SIZE/HASH equivalent)
|
|
|
|
|
+ if command -v keytool >/dev/null 2>&1; then
|
|
|
|
|
+ keytool -exportcert -alias "$alias" -keystore "/tmp/manager-$flavor.jks" -storepass "$pw" -file "/tmp/manager-$flavor.der" 2>/dev/null || true
|
|
|
|
|
+ if [ -f "/tmp/manager-$flavor.der" ]; then
|
|
|
|
|
+ sz=$(stat -c%s "/tmp/manager-$flavor.der"); hx=$(printf '0x%04x' "$sz"); hs=$(sha256sum "/tmp/manager-$flavor.der" | awk '{print $1}')
|
|
|
|
|
+ echo " cert size=$hx ($sz) sha256=$hs"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+ return 0
|
|
|
|
|
+ fi
|
|
|
|
|
+ return 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ! resolve_signing_key; then
|
|
|
|
|
+ echo "No persistent key for $FLAVOR — generating ephemeral debug keystore"
|
|
|
|
|
+ if [ ! -f key.jks ] && [ ! -f ../key.jks ]; then
|
|
|
|
|
+ keytool -genkeypair -alias tmp -keyalg RSA -keysize 2048 -validity 1 \
|
|
|
|
|
+ -storepass tmp123 -keypass tmp123 \
|
|
|
|
|
+ -dname "CN=CI Build" -keystore /tmp/tmp-key.jks -storetype JKS 2>&1 | tail -3 || true
|
|
|
|
|
+ if [ -f /tmp/tmp-key.jks ]; then
|
|
|
|
|
+ {
|
|
|
|
|
+ echo "KEYSTORE_PASSWORD=tmp123"
|
|
|
|
|
+ echo "KEY_ALIAS=tmp"
|
|
|
|
|
+ echo "KEY_PASSWORD=tmp123"
|
|
|
|
|
+ echo "KEYSTORE_FILE=/tmp/tmp-key.jks"
|
|
|
|
|
+ } >> gradle.properties
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ flavor="$FLAVOR"
|
|
|
|
|
+ echo "Building manager for flavor=$flavor"
|
|
|
|
|
+
|
|
|
|
|
+ if [ "$flavor" = "next" ]; then
|
|
|
|
|
+ # KernelSU-Next: two variants (normal + spoofed via ./spoof)
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40
|
|
|
|
|
+ if [ -x ./spoof ]; then
|
|
|
|
|
+ echo "Building spoofed variant"
|
|
|
|
|
+ chmod +x ./spoof
|
|
|
|
|
+ ./spoof || true
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40 || true
|
|
|
|
|
+ # spoof overwrites same APK path; stash both with distinct names
|
|
|
|
|
+ for apk in app/build/outputs/apk/release/*.apk; do
|
|
|
|
|
+ [ -f "$apk" ] && cp -f "$apk" "/tmp/next-spoofed.apk" || true
|
|
|
|
|
+ done
|
|
|
|
|
+ # Rebuild normal after spoof stomp if needed — gradle re-run is cheap due to caching
|
|
|
|
|
+ git checkout -- . 2>/dev/null || true
|
|
|
|
|
+ if [ -f /tmp/tmp-key.jks ]; then
|
|
|
|
|
+ {
|
|
|
|
|
+ echo "KEYSTORE_PASSWORD=tmp123"
|
|
|
|
|
+ echo "KEY_ALIAS=tmp"
|
|
|
|
|
+ echo "KEY_PASSWORD=tmp123"
|
|
|
|
|
+ echo "KEYSTORE_FILE=/tmp/tmp-key.jks"
|
|
|
|
|
+ } > gradle.properties
|
|
|
|
|
+ fi
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40 || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ elif [ "$flavor" = "kernelsu" ]; then
|
|
|
|
|
+ # tiann/KernelSU: single manager, repack embeds ksud+LKM
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40 || ./gradlew assembleRelease 2>&1 | tail -40
|
|
|
|
|
+ if [ -f ../repack_apk.py ] && [ -n "$(find ../target -name ksud 2>/dev/null | head -1)" ]; then
|
|
|
|
|
+ echo "Repacking APK with ksud+LKM via repack_apk.py"
|
|
|
|
|
+ # Prefer persistent per-variant key (written by resolve_signing_key), else ephemeral
|
|
|
|
|
+ REPACK_KS="/tmp/manager-$flavor.jks"
|
|
|
|
|
+ REPACK_ALIAS="$(cat "/tmp/repack-alias-$flavor.path" 2>/dev/null || echo repack)"
|
|
|
|
|
+ REPACK_SP="$(cat "/tmp/repack-storepass-$flavor.path" 2>/dev/null || echo repack123)"
|
|
|
|
|
+ REPACK_KP="$(cat "/tmp/repack-keypass-$flavor.path" 2>/dev/null || echo repack123)"
|
|
|
|
|
+ if [ ! -f "$REPACK_KS" ]; then
|
|
|
|
|
+ REPACK_KS="/tmp/repack.jks"; REPACK_ALIAS="repack"; REPACK_SP="repack123"; REPACK_KP="repack123"
|
|
|
|
|
+ if [ ! -f /tmp/repack.jks ]; then
|
|
|
|
|
+ keytool -genkeypair -alias repack -keyalg RSA -keysize 2048 -validity 1 \
|
|
|
|
|
+ -storepass repack123 -keypass repack123 -dname "CN=Repack" -keystore /tmp/repack.jks -storetype JKS 2>&1 | tail -3 || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+ python3 ../repack_apk.py repack \
|
|
|
|
|
+ --keystore "$REPACK_KS" --alias "$REPACK_ALIAS" --storepass "$REPACK_SP" --keypass "$REPACK_KP" 2>&1 | tail -20 || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ elif [ "$flavor" = "resukisu" ]; then
|
|
|
|
|
+ # ReSukiSU: three variants — Manager-release, Spoofed-Manager-release, Manager-debug
|
|
|
|
|
+ if [ -x ./randomizer ]; then chmod +x ./randomizer; fi
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40 || true
|
|
|
|
|
+ ./gradlew assembleDebug 2>&1 | tail -20 || true
|
|
|
|
|
+ if [ -x ./randomizer ]; then
|
|
|
|
|
+ ./randomizer 2>&1 | tail -10 || true
|
|
|
|
|
+ ./gradlew clean assembleRelease 2>&1 | tail -40 || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ # Collect whatever was produced
|
|
|
|
|
+ out="/tmp/manager_apks_${flavor}"
|
|
|
|
|
+ mkdir -p "$out"
|
|
|
|
|
+ for apk in app/build/outputs/apk/release/*.apk app/build/outputs/apk/debug/*.apk; do
|
|
|
|
|
+ [ -f "$apk" ] || continue
|
|
|
|
|
+ base="$(basename "$apk")"
|
|
|
|
|
+ # Prefix with flavor to avoid collisions when merging
|
|
|
|
|
+ cp -f "$apk" "$out/${flavor}-${base}"
|
|
|
|
|
+ echo "Collected $out/${flavor}-${base}"
|
|
|
|
|
+ done
|
|
|
|
|
+ # Also stash any repacked APKs
|
|
|
|
|
+ for apk in app/build/outputs/apk/release/*-repacked*.apk build/outputs/apk/*.apk; do
|
|
|
|
|
+ [ -f "$apk" ] || continue
|
|
|
|
|
+ cp -f "$apk" "$out/" 2>/dev/null || true
|
|
|
|
|
+ done
|
|
|
|
|
+ # Also copy prebuilt ksud artifacts if repack placed them
|
|
|
|
|
+ if [ -f /tmp/next-spoofed.apk ]; then
|
|
|
|
|
+ cp -f /tmp/next-spoofed.apk "$out/${flavor}-spoofed.apk" || true
|
|
|
|
|
+ fi
|
|
|
|
|
+ ls -lh "$out" 2>&1 | head -30 || true
|
|
|
|
|
+ if [ -z "$(ls -A "$out" 2>/dev/null)" ]; then
|
|
|
|
|
+ echo "WARNING: no APK produced for $flavor" >&2
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo "MANAGER_OUT_$flavor=$out" >> "$GITHUB_ENV"
|
|
|
|
|
+
|
|
|
|
|
+ - name: Upload built manager APKs (${{ matrix.flavor }})
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: actions/upload-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: manager-apks-src-${{ matrix.flavor }}
|
|
|
|
|
+ path: /tmp/manager_apks_${{ matrix.flavor }}/*.apk
|
|
|
|
|
+ if-no-files-found: warn
|
|
|
|
|
+
|
|
|
|
|
+ - name: Upload built LKM marker (${{ matrix.flavor }})
|
|
|
|
|
+ if: steps.gate.outputs.run == 'true'
|
|
|
|
|
+ uses: actions/upload-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: lkm-${{ matrix.flavor }}
|
|
|
|
|
+ path: |
|
|
|
|
|
+ /tmp/lkm-*.marker
|
|
|
|
|
+ /tmp/${{ matrix.flavor }}-src/kernel/*.ko
|
|
|
|
|
+ /tmp/${{ matrix.flavor }}-src/target/**/kernelsu.ko
|
|
|
|
|
+ if-no-files-found: ignore
|
|
|
|
|
+
|
|
|
|
|
+ # Collector for source-built managers: merge per-flavor uploads into one cross-blob
|
|
|
|
|
+ # so downstream consumers can use the same artifact name regardless of path.
|
|
|
|
|
+ collect-source-managers:
|
|
|
|
|
+ needs: build-from-source
|
|
|
|
|
+ if: ${{ inputs.build_from_source }}
|
|
|
|
|
+ runs-on: ubuntu-latest
|
|
|
|
|
+ outputs:
|
|
|
|
|
+ apk_list: ${{ steps.scan.outputs.apk_list }}
|
|
|
|
|
+ steps:
|
|
|
|
|
+ - name: Download all source-built manager artifacts
|
|
|
|
|
+ uses: actions/download-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ pattern: manager-apks-src-*
|
|
|
|
|
+ path: manager_apks
|
|
|
|
|
+ merge-multiple: true
|
|
|
|
|
+
|
|
|
|
|
+ - name: Scan collected APKs
|
|
|
|
|
+ id: scan
|
|
|
|
|
+ shell: bash
|
|
|
|
|
+ run: |
|
|
|
|
|
+ set -euo pipefail
|
|
|
|
|
+ mkdir -p manager_apks
|
|
|
|
|
+ find manager_apks -type f -name '*.apk' | sort | tee /tmp/list.txt || true
|
|
|
|
|
+ count=$(wc -l < /tmp/list.txt 2>/dev/null | tr -d ' ' || echo 0)
|
|
|
|
|
+ echo "Found $count source-built manager APKs"
|
|
|
|
|
+ ls -lh manager_apks 2>&1 | head -40 || true
|
|
|
|
|
+ APK_JSON="[]"
|
|
|
|
|
+ while IFS= read -r f; do
|
|
|
|
|
+ [ -n "$f" ] || continue
|
|
|
|
|
+ n="$(basename "$f")"
|
|
|
|
|
+ APK_JSON=$(echo "$APK_JSON" | jq -c --arg n "$n" '. + [{apk:$n}]')
|
|
|
|
|
+ done < /tmp/list.txt
|
|
|
|
|
+ echo "apk_list=${APK_JSON}" >> "$GITHUB_OUTPUT"
|
|
|
|
|
+ echo "### Source-built Manager APKs" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+ cat /tmp/list.txt >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || echo "_none_" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
|
+
|
|
|
|
|
+ - name: Upload merged manager-apks (source)
|
|
|
|
|
+ uses: actions/upload-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: manager-apks
|
|
|
|
|
+ path: manager_apks/
|
|
|
|
|
+ if-no-files-found: warn
|
|
|
|
|
+
|
|
|
|
|
+ # Unified publisher: works for both fetch and source paths (whichever produced manager-apks)
|
|
|
|
|
+ publish-manager-artifacts:
|
|
|
|
|
+ needs:
|
|
|
|
|
+ - list-manager-apks
|
|
|
|
|
+ - collect-source-managers
|
|
|
|
|
+ if: ${{ always() && (needs.list-manager-apks.outputs.apk_list != '' || needs.collect-source-managers.outputs.apk_list != '') }}
|
|
|
|
|
+ runs-on: ubuntu-latest
|
|
|
|
|
+ strategy:
|
|
|
|
|
+ fail-fast: false
|
|
|
|
|
+ matrix:
|
|
|
|
|
+ item: ${{ fromJson(needs.list-manager-apks.outputs.apk_list != '' && needs.list-manager-apks.outputs.apk_list || needs.collect-source-managers.outputs.apk_list) }}
|
|
|
|
|
+ steps:
|
|
|
|
|
+ - uses: actions/checkout@v7
|
|
|
|
|
+
|
|
|
|
|
+ - name: Download manager cross-blob
|
|
|
|
|
+ uses: actions/download-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: manager-apks
|
|
|
|
|
+ path: manager_apks
|
|
|
|
|
+ merge-multiple: true
|
|
|
|
|
+
|
|
|
|
|
+ - name: Upload ${{ matrix.item.apk }} as its own artifact
|
|
|
|
|
+ uses: actions/upload-artifact@v7
|
|
|
|
|
+ with:
|
|
|
|
|
+ name: manager-apk-${{ matrix.item.apk }}
|
|
|
|
|
+ path: manager_apks/${{ matrix.item.apk }}
|
|
|
|
|
+ if-no-files-found: error
|