Skip to content

Check ARB

Check ARB #433

Workflow file for this run

name: Check ARB
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
on:
workflow_dispatch:
inputs:
force_recheck:
description: 'Force recheck all firmwares'
required: false
default: false
type: boolean
device:
description: 'Target Device (optional, e.g. 15)'
required: false
variant:
description: 'Target Variant (optional, e.g. GLO)'
required: false
schedule:
- cron: '0 0 * * *' # Run daily
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
setup-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout Repo
uses: actions/checkout@v5
- name: Generate Matrix
id: set-matrix
env:
TARGET_DEVICE: ${{ inputs.device }}
TARGET_VARIANT: ${{ inputs.variant }}
run: python3 generate_matrix.py
prepare-tools:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v5
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: aria2 unzip curl
version: 1.0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
- name: Install Python dependencies
run: |
pip install -r requirements.txt
- name: Cache Tools
id: cache-tools
uses: actions/cache@v5
with:
path: tools/
key: tools-v1
- name: Setup Tools
if: steps.cache-tools.outputs.cache-hit != 'true'
run: |
mkdir -p tools
curl -L -o tools/arbextract https://github.com/koaaN/arbextract/releases/download/1.0/arbextract-x86_64-linux
chmod +x tools/arbextract
curl -L -o otaripper.tar.gz https://github.com/syedinsaf/otaripper/releases/download/v2.1.1/otaripper-2.1.1-linux-static-x86_64.tar.gz
tar -xzvf otaripper.tar.gz
mv otaripper tools/otaripper || find . -name "otaripper" -type f -exec mv {} tools/otaripper \;
chmod +x tools/otaripper
curl -L -o pdg.tar.gz https://github.com/ssut/payload-dumper-go/releases/download/1.2.2/payload-dumper-go_1.2.2_linux_amd64.tar.gz
tar -xzvf pdg.tar.gz
mv payload-dumper-go tools/payload-dumper-go || find . -name "payload-dumper-go" -type f -exec mv {} tools/payload-dumper-go \;
chmod +x tools/payload-dumper-go
- name: Ensure Tools are Executable
run: chmod +x tools/* || true
check-variant:
needs: [setup-matrix, prepare-tools]
runs-on: ubuntu-latest
continue-on-error: true
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.BOTTOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup-matrix.outputs.matrix) }}
steps:
- name: Checkout Repo
uses: actions/checkout@v5
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: aria2 unzip curl
version: 1.0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Restore Tools Cache
uses: actions/cache/restore@v5
with:
path: tools/
key: tools-v1
fail-on-cache-miss: true
- name: Ensure Tools are Executable
run: chmod +x tools/* || true
- name: Get Firmware Details
id: get_details
run: |
echo "Fetching details for ${{ matrix.device }} ${{ matrix.variant }}..."
python3 fetch_firmware.py "${{ matrix.device }}" "${{ matrix.variant }}" --output fw_info.json
if [ ! -f fw_info.json ]; then
echo "Failed to fetch firmware details (no output file)"
exit 1
fi
URL=$(python3 -c "import sys, json; print(json.load(open('fw_info.json'))['url'])")
VERSION=$(python3 -c "import sys, json; print(json.load(open('fw_info.json'))['version'])")
MD5=$(python3 -c "import sys, json; data=json.load(open('fw_info.json')); print(data.get('md5') or '')")
echo "Device: ${{ matrix.device_name }}"
echo "Variant: ${{ matrix.variant }}"
echo "Latest Firmware URL: $URL"
echo "Latest Firmware Version: $VERSION"
echo "MD5 Checksum: $MD5"
echo "url=$URL" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "md5=$MD5" >> $GITHUB_OUTPUT
echo "device_short=${{ matrix.device_short }}" >> $GITHUB_OUTPUT
echo "device_name=${{ matrix.device_name }}" >> $GITHUB_OUTPUT
echo "variant=${{ matrix.variant }}" >> $GITHUB_OUTPUT
- name: Cache ARB Data
id: cache-arb
if: github.event.inputs.force_recheck != 'true'
uses: actions/cache@v5
with:
path: firmware_data/
key: arb-v9-${{ matrix.device }}-${{ matrix.variant }}-${{ steps.get_details.outputs.version }}-${{ steps.get_details.outputs.md5 }}
- name: Download Firmware
if: steps.cache-arb.outputs.cache-hit != 'true'
run: |
URL="${{ steps.get_details.outputs.url }}"
VERSION="${{ steps.get_details.outputs.version }}"
MD5="${{ steps.get_details.outputs.md5 }}"
if [[ "$URL" == "" || "$URL" == "null" ]]; then
echo "No URL found."
touch skip_check.txt
exit 0
fi
if [[ "$URL" != *".zip"* && "${{ matrix.variant }}" != "CN" ]]; then
echo "Skipping non-direct download link: $URL"
touch skip_check.txt
exit 0
fi
echo "Downloading firmware with robust retry loop..."
MAX_RETRIES=5
RETRY_COUNT=0
SUCCESS=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
echo "Attempting download (Try $((RETRY_COUNT+1))/$MAX_RETRIES)..."
# Construct aria2c command
# -c: resume partial download (critical for large CN files with expiring URLs)
ARIA_CMD="aria2c -c -x16 -s16 -k1M -o firmware.zip"
if [[ "$MD5" != "" && "$MD5" != "None" ]]; then
echo "Enforcing MD5 checksum: $MD5"
ARIA_CMD="$ARIA_CMD --checksum=md5=$MD5"
else
echo "No MD5 checksum available for verification."
fi
if $ARIA_CMD "$URL"; then
echo "Download successful!"
SUCCESS=1
break
fi
echo "Download failed."
# Determine failure reason:
# - If firmware.zip exists but is incomplete → URL expired mid-download
# → KEEP the partial file so aria2c can resume with a refreshed URL
# - If firmware.zip is missing or MD5 mismatch on a complete file
# → Remove and start fresh
FILESIZE=0
if [ -f firmware.zip ]; then
FILESIZE=$(stat -c%s firmware.zip 2>/dev/null || echo 0)
fi
if [[ $FILESIZE -gt 0 && ! -f firmware.zip.aria2 ]]; then
# File exists but no .aria2 control file = download completed but MD5 failed
echo "Checksum mismatch on completed file. Removing for fresh download."
rm -f firmware.zip firmware.zip.aria2
elif [[ $FILESIZE -gt 0 ]]; then
# Partial download with .aria2 control file = URL expired mid-download
echo "Partial download preserved ($(du -sh firmware.zip | cut -f1)). Will resume with refreshed URL."
else
echo "No partial file found. Starting fresh."
rm -f firmware.zip firmware.zip.aria2
fi
RETRY_COUNT=$((RETRY_COUNT+1))
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then break; fi
echo "Refreshing URL..."
python3 fetch_firmware.py "${{ matrix.device }}" "${{ matrix.variant }}" "$VERSION" --output fw_refresh.json
if [ -f fw_refresh.json ]; then
NEW_URL=$(python3 -c "import sys, json; print(json.load(open('fw_refresh.json'))['url'])")
if [[ "$NEW_URL" != "" && "$NEW_URL" != "null" ]]; then
URL="$NEW_URL"
echo "Refreshed URL successfully."
fi
fi
sleep 5
done
if [ $SUCCESS -eq 0 ]; then
echo "Download failed after all attempts."
touch skip_check.txt
fi
- name: Analyze Firmware (Check ARB)
if: steps.cache-arb.outputs.cache-hit != 'true' && hashFiles('skip_check.txt') == ''
run: |
# Use the new standalone script
# It extracts to 'extracted' (temp) and moves final file to 'firmware_data/xbl_config.img'
python3 analyze_firmware.py firmware.zip \
--tools-dir tools \
--output-dir extracted \
--final-dir firmware_data \
--json > result.json
# Inject extra metadata into result.json for update_history.py
# (We could have passed these to analyze_firmware.py, but it's pure analysis)
# Let's just use jq or python to merge? Or just pass arguments to update_history.py explicitly
# Update: update_history.py accepts --json-file and missing args separate.
# We need to make sure result.json has device_short, variant, version if we assume --json-file provides all.
# But analyze_firmware.py only outputs ARB info.
# So we will pass the rest as args.
cat result.json
- name: Extract ARB from Cached Image
if: steps.cache-arb.outputs.cache-hit == 'true' && hashFiles('firmware_data/xbl_config.img') != ''
run: |
echo "Cache hit! Extracting ARB from cached xbl_config.img..."
# Run arbextract and capture output
ARB_OUTPUT=$(tools/arbextract firmware_data/xbl_config.img)
echo "$ARB_OUTPUT"
# Parse the output and create JSON
ARB_INDEX=$(echo "$ARB_OUTPUT" | grep "ARB (Anti-Rollback)" | awk -F':' '{print $2}' | tr -d ' ')
MAJOR=$(echo "$ARB_OUTPUT" | grep "Major Version" | awk -F':' '{print $2}' | tr -d ' ')
MINOR=$(echo "$ARB_OUTPUT" | grep "Minor Version" | awk -F':' '{print $2}' | tr -d ' ')
# Create result.json
cat > result.json << EOF
{
"arb_index": "$ARB_INDEX",
"major": "$MAJOR",
"minor": "$MINOR"
}
EOF
cat result.json
- name: Update JSON History (Current Version)
id: update_history
if: hashFiles('result.json') != ''
run: |
python3 update_history.py \
"${{ steps.get_details.outputs.device_short }}" \
"${{ matrix.variant }}" \
"${{ steps.get_details.outputs.version }}" \
--json-file result.json \
--md5 "${{ steps.get_details.outputs.md5 }}"
- name: Upload Result
uses: actions/upload-artifact@v4
with:
name: result-${{ matrix.device }}-${{ matrix.variant }}
path: result.json
- name: Send Telegram Notification
if: steps.update_history.outputs.is_new == 'true' && env.TELEGRAM_BOT_TOKEN != '' && env.TELEGRAM_CHAT_ID != ''
run: |
# Extract ARB from result.json (it is always created by previous steps if we are here)
ARB=$(python3 -c "import sys, json; print(json.load(open('result.json')).get('arb_index', 'Unknown'))")
# Extract MD5 from result.json (it might be calculated or passed through)
CALC_MD5=$(python3 -c "import sys, json; print(json.load(open('result.json')).get('md5', ''))")
# Check if device+version is hardcode protected (shows ARB=0 but is actually protected)
IS_HARDCODED=$(python3 -c "
from hardcode_rules import is_hardcode_protected
from config import OOS_MAPPING
device_id = '${{ matrix.device }}'
oos_key = OOS_MAPPING.get(device_id, 'oneplus_' + device_id.lower().replace(' ', '_'))
version = '${{ steps.get_details.outputs.version }}'
print('true' if is_hardcode_protected(oos_key, version) else 'false')
")
if [[ "$IS_HARDCODED" == "true" ]]; then
echo "Device is hardcode protected, overriding ARB display to '?'"
ARB="?"
fi
# Prefer calculated MD5, fallback to API one
FINAL_MD5="${{ steps.get_details.outputs.md5 }}"
if [[ "$CALC_MD5" != "" && "$CALC_MD5" != "null" ]]; then
FINAL_MD5="$CALC_MD5"
fi
python3 send_telegram.py \
--token "$TELEGRAM_BOT_TOKEN" \
--chat-id "$TELEGRAM_CHAT_ID" \
--title "🆕 New Firmware Detected!" \
--device "${{ steps.get_details.outputs.device_name }}" \
--variant "${{ matrix.variant }}" \
--version "${{ steps.get_details.outputs.version }}" \
--arb "$ARB" \
--md5 "$FINAL_MD5" \
--url "${{ steps.get_details.outputs.url }}"
- name: Upload JSON History
uses: actions/upload-artifact@v4
with:
name: history-${{ matrix.device }}-${{ matrix.variant }}
path: data/history/${{ steps.get_details.outputs.device_short }}_${{ matrix.variant }}.json
- name: Cleanup
if: always()
run: rm -f firmware.zip
update-readme:
needs: check-variant
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v5
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
pattern: history-*
path: data/history
merge-multiple: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
- name: Restore History & Generate README
run: |
# Install dependencies
pip install -r requirements.txt
# Generate
python3 clean_orphans.py
python3 generate_database.py
python3 generate_readme.py
python3 generate_site.py
- name: Commit and Push
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add -A data/history/
git add README.md data/database.json
git commit -m "Update ARB history and README" || echo "No changes to commit"
git push
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./page
keep_files: true