Build: upload > /product/media #2
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: Build Custom Module | |
| # To run this workflow | |
| # 1. Fork this repo | |
| # 2. Choose a device template or add custom files to the 'upload' folder | |
| # 3. Run and download the module | |
| run-name: "Build: ${{ inputs.source == 'template' && format('{0}/{1}', inputs.device, inputs.theme) || 'upload' }} > ${{ inputs.target_location }}" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| source: | |
| description: 'Source' | |
| required: true | |
| default: 'upload' | |
| type: choice | |
| options: | |
| - 'upload' | |
| - 'template' | |
| device: | |
| description: 'Device model (only applies when source is template)' | |
| required: false | |
| default: 'liuqin' | |
| type: choice | |
| options: | |
| - 'liuqin' | |
| theme: | |
| description: 'Theme (only applies when source is template)' | |
| required: false | |
| default: 'HyperOS' | |
| type: choice | |
| options: | |
| - 'HyperOS' | |
| - 'MIUI' | |
| - 'MIUI_inverted' | |
| target_location: | |
| description: 'Where to place bootanimation files in the system' | |
| required: true | |
| default: '/product/media' | |
| type: choice | |
| options: | |
| - '/product/media' | |
| - '/system/media' | |
| - '/system_ext/media' | |
| jobs: | |
| build-module: | |
| runs-on: ubuntu-latest | |
| name: Build Module | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Download latest release module | |
| id: download_release | |
| run: | | |
| echo "Fetching latest release info from update.json..." | |
| # Read update.json to get the download URL | |
| if [ ! -f "update.json" ]; then | |
| echo "Error: update.json not found!" | |
| exit 1 | |
| fi | |
| ZIP_URL=$(jq -r '.zipUrl' update.json) | |
| VERSION=$(jq -r '.version' update.json) | |
| if [ -z "$ZIP_URL" ] || [ "$ZIP_URL" = "null" ]; then | |
| echo "Error: No zipUrl found in update.json!" | |
| exit 1 | |
| fi | |
| echo "Version: $VERSION" | |
| echo "Download URL: $ZIP_URL" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| # Download the module zip | |
| mkdir -p release_download | |
| cd release_download | |
| echo "Downloading module..." | |
| curl -L -o module.zip "$ZIP_URL" | |
| if [ ! -f "module.zip" ]; then | |
| echo "Error: Failed to download module!" | |
| exit 1 | |
| fi | |
| echo "Downloaded: module.zip" | |
| # Extract the module | |
| mkdir -p ../build/module | |
| unzip -o "module.zip" -d ../build/module | |
| echo "Module extracted successfully" | |
| # Show module info | |
| if [ -f "../build/module/module.prop" ]; then | |
| echo "" | |
| echo "Module info:" | |
| cat ../build/module/module.prop | |
| fi | |
| - name: Prepare bootanimation files | |
| id: prepare_files | |
| run: | | |
| mkdir -p bootanimations | |
| SOURCE="${{ github.event.inputs.source }}" | |
| # Default to upload if not specified | |
| if [ -z "$SOURCE" ]; then | |
| SOURCE="upload" | |
| fi | |
| echo "Source: $SOURCE" | |
| if [ "$SOURCE" = "template" ]; then | |
| # Build template path from device and theme inputs | |
| DEVICE="${{ github.event.inputs.device }}" | |
| THEME="${{ github.event.inputs.theme }}" | |
| # Use defaults if not specified | |
| DEVICE="${DEVICE:-liuqin}" | |
| THEME="${THEME:-HyperOS}" | |
| TEMPLATE_DIR="templates/$DEVICE/$THEME" | |
| echo "Using template: $DEVICE/$THEME" | |
| if [ ! -d "$TEMPLATE_DIR" ]; then | |
| echo "Error: Template directory not found: $TEMPLATE_DIR" | |
| echo "" | |
| echo "Available templates:" | |
| find templates -type d -mindepth 2 -maxdepth 2 2>/dev/null || echo " (none)" | |
| exit 1 | |
| fi | |
| # Copy template files to bootanimations | |
| cp "$TEMPLATE_DIR"/*.zip bootanimations/ 2>/dev/null || { | |
| echo "Error: No bootanimation files found in template: $TEMPLATE_DIR" | |
| exit 1 | |
| } | |
| echo "Copied template files:" | |
| ls -la bootanimations/ | |
| else | |
| # Use upload folder | |
| echo "Using upload folder" | |
| if [ -d "upload" ] && ls upload/*.zip 1> /dev/null 2>&1; then | |
| cp upload/*.zip bootanimations/ | |
| echo "Copied files from upload folder:" | |
| ls -la bootanimations/ | |
| else | |
| echo "Error: No bootanimation files found in upload folder!" | |
| echo "" | |
| echo "To use this workflow:" | |
| echo "1. Add your bootanimation.zip files to the 'upload' folder" | |
| echo "2. Commit and push the changes" | |
| echo "3. Run this workflow again" | |
| echo "" | |
| echo "OR select 'template' as source and choose a device/system." | |
| echo "" | |
| echo "File naming convention:" | |
| echo " - bootanimation.zip (main boot animation)" | |
| echo " - bootanimation01.zip (alternative 1)" | |
| echo " - bootanimation02.zip (alternative 2)" | |
| echo " - etc." | |
| exit 1 | |
| fi | |
| fi | |
| - name: Validate bootanimation files | |
| run: | | |
| echo "Validating bootanimation files..." | |
| echo "" | |
| errors=0 | |
| warnings=0 | |
| for file in bootanimations/*.zip; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| filesize=$(du -h "$file" | cut -f1) | |
| echo "Checking: $filename ($filesize)" | |
| # Validate ZIP integrity | |
| if ! unzip -t "$file" > /dev/null 2>&1; then | |
| echo " [ERROR] Invalid ZIP file!" | |
| errors=$((errors + 1)) | |
| continue | |
| fi | |
| # Check for desc.txt | |
| if unzip -l "$file" 2>/dev/null | grep -q "desc.txt"; then | |
| echo " [OK] desc.txt found" | |
| else | |
| echo " [WARN] desc.txt not found" | |
| warnings=$((warnings + 1)) | |
| fi | |
| # Check for part0/ | |
| if unzip -l "$file" 2>/dev/null | grep -q "part0/"; then | |
| echo " [OK] part0/ found" | |
| else | |
| echo " [WARN] part0/ not found" | |
| warnings=$((warnings + 1)) | |
| fi | |
| echo "" | |
| fi | |
| done | |
| if [ $errors -gt 0 ]; then | |
| echo "Validation failed with $errors error(s)" | |
| exit 1 | |
| fi | |
| if [ $warnings -gt 0 ]; then | |
| echo "Validation completed with $warnings warning(s)" | |
| else | |
| echo "All files validated successfully" | |
| fi | |
| - name: Prepare module structure | |
| run: | | |
| echo "Preparing module structure..." | |
| # Determine target location | |
| TARGET_LOCATION="${{ github.event.inputs.target_location }}" | |
| if [ -z "$TARGET_LOCATION" ]; then | |
| TARGET_LOCATION="/product/media" | |
| fi | |
| MODULE_TARGET="system$TARGET_LOCATION" | |
| echo "Target location: $TARGET_LOCATION" | |
| echo "Module path: $MODULE_TARGET" | |
| # Remove all existing media directories to avoid conflicts | |
| echo "Cleaning up existing media directories..." | |
| MEDIA_DIRS=( | |
| "build/module/system/product/media" | |
| "build/module/system/system/media" | |
| "build/module/system/system_ext/media" | |
| "build/module/system/product/media/theme/default" | |
| ) | |
| for dir in "${MEDIA_DIRS[@]}"; do | |
| if [ -d "$dir" ]; then | |
| # Check if this is the target or a parent of it | |
| TARGET_DIR_FULL="build/module/$MODULE_TARGET" | |
| if [[ "$TARGET_DIR_FULL" != "$dir"* ]]; then | |
| echo " Removing: $dir" | |
| rm -rf "$dir" | |
| fi | |
| fi | |
| done | |
| # Create target directory with system/ prefix for Magisk | |
| TARGET_DIR="build/module/$MODULE_TARGET" | |
| mkdir -p "$TARGET_DIR" | |
| # Clean any existing bootanimation files from the downloaded module | |
| find build/module -name "bootanimation*.zip" -type f -delete 2>/dev/null || true | |
| echo "Target location: $TARGET_LOCATION" | |
| echo "target_dir=$TARGET_DIR" >> $GITHUB_ENV | |
| echo "target_location=$TARGET_LOCATION" >> $GITHUB_ENV | |
| - name: Copy bootanimation files | |
| run: | | |
| echo "Copying bootanimation files..." | |
| for file in bootanimations/*.zip; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| echo " -> $filename" | |
| cp "$file" "${{ env.target_dir }}/" | |
| fi | |
| done | |
| echo "" | |
| echo "Files copied to module:" | |
| ls -la "${{ env.target_dir }}/" | |
| - name: Get module info | |
| id: module_info | |
| run: | | |
| PROP_FILE="build/module/module.prop" | |
| MODULE_ID=$(grep "^id=" "$PROP_FILE" | cut -d= -f2 | tr -d '\r') | |
| MODULE_NAME=$(grep "^name=" "$PROP_FILE" | cut -d= -f2 | tr -d '\r') | |
| MODULE_VERSION=$(grep "^version=" "$PROP_FILE" | cut -d= -f2 | tr -d '\r') | |
| MODULE_AUTHOR=$(grep "^author=" "$PROP_FILE" | cut -d= -f2 | tr -d '\r') | |
| echo "module_id=$MODULE_ID" >> $GITHUB_OUTPUT | |
| echo "module_name=$MODULE_NAME" >> $GITHUB_OUTPUT | |
| echo "module_version=$MODULE_VERSION" >> $GITHUB_OUTPUT | |
| echo "module_author=$MODULE_AUTHOR" >> $GITHUB_OUTPUT | |
| echo "Module info:" | |
| echo " ID: $MODULE_ID" | |
| echo " Name: $MODULE_NAME" | |
| echo " Version: $MODULE_VERSION" | |
| echo " Author: $MODULE_AUTHOR" | |
| - name: Prepare module artifact | |
| id: prepare_artifact | |
| run: | | |
| MODULE_ID="${{ steps.module_info.outputs.module_id }}" | |
| MODULE_VERSION="${{ steps.module_info.outputs.module_version }}" | |
| # Create a unique artifact name with target location | |
| TARGET_SHORT=$(echo "${{ env.target_location }}" | sed 's|^/||' | tr '/' '-') | |
| ARTIFACT_NAME="${MODULE_ID}-${MODULE_VERSION}-${TARGET_SHORT}" | |
| # Calculate module size | |
| MODULE_SIZE=$(du -sh build/module | cut -f1) | |
| echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT | |
| echo "module_size=$MODULE_SIZE" >> $GITHUB_OUTPUT | |
| echo "" | |
| echo "Module prepared: $ARTIFACT_NAME ($MODULE_SIZE)" | |
| echo "" | |
| echo "Module contents:" | |
| ls -laR build/module | |
| - name: Upload module artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ steps.prepare_artifact.outputs.artifact_name }} | |
| path: build/module | |
| retention-days: 90 | |
| - name: Generate build summary | |
| run: | | |
| SOURCE="${{ github.event.inputs.source }}" | |
| if [ -z "$SOURCE" ]; then | |
| SOURCE="upload" | |
| fi | |
| # Build source description | |
| if [ "$SOURCE" = "template" ]; then | |
| DEVICE="${{ github.event.inputs.device }}" | |
| THEME="${{ github.event.inputs.theme }}" | |
| SOURCE_DESC="template: $DEVICE/$THEME" | |
| else | |
| SOURCE_DESC="upload" | |
| fi | |
| cat << EOF >> $GITHUB_STEP_SUMMARY | |
| # Module Built Successfully | |
| ## Module Details | |
| | Property | Value | | |
| |----------|-------| | |
| | **Artifact** | \`${{ steps.prepare_artifact.outputs.artifact_name }}.zip\` | | |
| | **Size** | ${{ steps.prepare_artifact.outputs.module_size }} | | |
| | **Base Release** | ${{ steps.download_release.outputs.version }} | | |
| | **Source** | $SOURCE_DESC | | |
| | **Name** | ${{ steps.module_info.outputs.module_name }} | | |
| | **Version** | ${{ steps.module_info.outputs.module_version }} | | |
| | **Author** | ${{ steps.module_info.outputs.module_author }} | | |
| | **Target** | \`${{ env.target_location }}\` | | |
| ## Included Bootanimations | |
| EOF | |
| for file in "${{ env.target_dir }}"/*.zip; do | |
| if [ -f "$file" ]; then | |
| filename=$(basename "$file") | |
| size=$(du -h "$file" | cut -f1) | |
| echo "- \`$filename\` ($size)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| done | |
| cat << 'EOF' >> $GITHUB_STEP_SUMMARY | |
| ## Download & Installation | |
| 1. Scroll down to the **Artifacts** section below | |
| 2. Click on the artifact to download the module ZIP | |
| 3. Flash the downloaded ZIP directly using: | |
| - **Magisk Manager** → Modules → Install from storage | |
| - **KernelSU** → Module → Install | |
| - **APatch** → Module → Install | |
| 4. Reboot your device | |
| ## Notes | |
| - The module will replace the boot animation on your device | |
| - A backup of the original animation will be created during installation | |
| - You can uninstall the module to restore the original animation | |
| EOF |