Check ARB #429
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |