Introduction: Streamlining Magisk Module Development
Magisk modules have revolutionized the way Android users customize and enhance their devices, offering systemless modifications that preserve the integrity of the /system partition. For developers, creating and maintaining these modules often involves repetitive tasks: packaging files, versioning, and distributing updates. As your module grows in complexity or you manage multiple projects, manually performing these steps becomes cumbersome and error-prone. This article will guide you through automating your Magisk module build process, starting with fundamental shell scripting and culminating in a robust CI/CD pipeline using GitHub Actions, ensuring consistent, efficient, and reliable module releases.
The Manual Build Headache: Why Automation is Crucial
A typical Magisk module package is a simple ZIP file containing specific directories and files like module.prop, customize.sh, and the actual modification files. The manual process often looks like this:
- Make changes to module files.
- Update
module.prop(especially the version code). - Select all relevant files and folders.
- Compress them into a
.zipfile, ensuring the root directory contains the module structure directly. - Rename the
.zipfile to reflect the version. - Manually upload or distribute the file.
This iterative process, especially during rapid development cycles, introduces opportunities for mistakes – forgetting to update the version, including unnecessary files, or incorrect ZIP compression, leading to broken modules or frustrated users. Automation eliminates these human errors.
Enter Shell Scripts: The First Step to Automation
The simplest form of automation involves a shell script to handle the packaging. Let’s create a basic build.sh script.
Basic Packaging Script
First, ensure your module’s structure is correct. Typically, it looks like this:
my-magisk-module/├── module.prop├── customize.sh├── post-fs-data.sh (optional)├── system/ (optional)│ └── ...└── service.sh (optional)
Now, create build.sh in the root of your module directory:
#!/bin/bashMODULE_ID=$(grep -E '^id=' module.prop | cut -d'=' -f2)MODULE_VERSION=$(grep -E '^versionCode=' module.prop | cut -d'=' -f2)OUTPUT_DIR="./build"MODULE_ZIP="${MODULE_ID}-${MODULE_VERSION}.zip"# Clean up previous buildsrm -rf ${OUTPUT_DIR}mkdir -p ${OUTPUT_DIR}# Create the zip packageecho "Building ${MODULE_ZIP}..."zip -r ${OUTPUT_DIR}/${MODULE_ZIP} . -x "*.git*" "*build.sh" "*README.md" "*LICENSE" ".gitignore" "*.DS_Store"echo "Module built successfully: ${OUTPUT_DIR}/${MODULE_ZIP}"
To run this, make it executable: chmod +x build.sh, then execute: ./build.sh. This script reads the module ID and version from module.prop, creates a unique ZIP filename, and excludes common development files from the package.
Understanding module.prop and customize.sh
module.prop: Contains metadata likeid,name,version,versionCode,author, anddescription. TheidandversionCodeare crucial for automation.customize.sh: The core script that Magisk executes during module installation. It handles system modifications, symlinking, and other setup tasks. Your build script only packages this;customize.shitself dictates the installation logic.
Elevating Automation with Advanced Shell Scripting
Beyond basic packaging, you can enhance your build.sh to include more sophisticated checks and operations:
- Version Incrementing: Automatically increment
versionCode. - Linting/Validation: Check
module.propfor required fields orcustomize.shfor common errors. - Dependency Checks: Ensure necessary tools (like
zip) are available. - Cleanup: Remove any temporary files created during the build process.
Here’s an advanced snippet for automatic version increment (you might integrate this into your build script or run it as a separate pre-build step):
# Inside build.sh or a pre-build script# Get current version codeCURRENT_VERSION_CODE=$(grep -E '^versionCode=' module.prop | cut -d'=' -f2)NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1))# Update version code in module.prop (requires sed or similar)sed -i "s/^versionCode=${CURRENT_VERSION_CODE}/versionCode=${NEW_VERSION_CODE}/" module.prop# Optionally update version name based on date or git commit hashVERSION_NAME_DATE=$(date +"%Y.%m.%d")sed -i "s/^version=.*/version=${VERSION_NAME_DATE}-release/" module.prop
This makes sure that every build has a unique and incremental version code, a critical aspect for proper module updates.
From Local Scripts to Cloud Power: CI/CD for Magisk Modules
While local shell scripts are great, they still require manual execution. Continuous Integration/Continuous Delivery (CI/CD) takes automation to the next level by automatically building, testing, and even releasing your module whenever changes are pushed to your repository. This offers:
- Consistency: Every build uses the same environment and steps.
- Collaboration: Multiple developers can contribute without worrying about local environment differences.
- Automation of Releases: New versions can be automatically published upon specific events (e.g., a new Git tag).
- Early Error Detection: Build failures are caught immediately.
For open-source projects, GitHub Actions is an excellent choice for CI/CD, being tightly integrated with GitHub repositories.
Implementing CI/CD with GitHub Actions
Let’s set up a GitHub Actions workflow that triggers on every push to the main branch, builds the module, and creates a GitHub Release when a new tag is pushed.
GitHub Actions Workflow (`.github/workflows/build.yml`)
Create a file named build.yml inside the .github/workflows/ directory in your repository.
name: Build Magisk Moduleon: push: branches: - main tags: - 'v*' # Trigger on tags like v1.0, v2.1.0jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up build environment run: | sudo apt-get update sudo apt-get install -y zip # Ensure zip is available - name: Make build script executable run: chmod +x build.sh - name: Build Magisk Module id: build_module run: ./build.sh - name: Get module artifact path and name id: get_artifact run: | MODULE_ID=$(grep -E '^id=' module.prop | cut -d'=' -f2) MODULE_VERSION=$(grep -E '^versionCode=' module.prop | cut -d'=' -f2) MODULE_ZIP_PATH="./build/${MODULE_ID}-${MODULE_VERSION}.zip" echo "artifact_path=${MODULE_ZIP_PATH}" >> "$GITHUB_OUTPUT" echo "artifact_name=${MODULE_ID}-${MODULE_VERSION}.zip" >> "$GITHUB_OUTPUT" - name: Upload module as artifact uses: actions/upload-artifact@v4 with: name: ${{ steps.get_artifact.outputs.artifact_name }} path: ${{ steps.get_artifact.outputs.artifact_path }} - name: Create GitHub Release if: startsWith(github.ref, 'refs/tags/') # Only run if a tag triggered the workflow uses: softprops/action-gh-release@v1 with: files: ${{ steps.get_artifact.outputs.artifact_path }} prerelease: ${{ contains(github.ref, '-beta') || contains(github.ref, '-rc') }} # Example: v1.0-beta1 becomes prerelease body: | ## New Release: ${{ github.ref_name }} This release contains the latest changes and bug fixes. Please report any issues!
This workflow does the following:
- Triggers: Runs on every push to
mainor when a new Git tag (e.g.,v1.0) is pushed. - Checkout Code: Retrieves your repository’s code.
- Set up Environment: Installs
zip, which ourbuild.shrelies on. - Build Module: Executes your
build.shscript. - Get Artifact Path: A small step to dynamically get the path and name of the generated ZIP file.
- Upload Artifact: Stores the built
.zipfile as a workflow artifact, accessible from the GitHub Actions run summary. - Create GitHub Release: If the workflow was triggered by a tag, it uses
softprops/action-gh-releaseto create a new GitHub Release, attaching the built Magisk module ZIP as an asset. It also intelligently marks prereleases based on tag naming conventions.
To trigger a release, you would typically make your changes, commit them, push to main, and then create and push a tag:
git add .git commit -m "feat: new awesome feature"git push origin maingit tag -a v1.0.0 -m "Version 1.0.0 Release"git push origin v1.0.0
Testing and Distribution Considerations
While the CI/CD pipeline automates the build and release, a critical next step for full automation is integrating automated testing. This could involve:
- Emulator Testing: Using Android emulators (e.g., via AVDs or Genymotion) to install the module and verify its functionality.
- Device Farms: Utilizing services like Firebase Test Lab to test on a wider range of real devices.
- Unit/Integration Tests: If applicable, writing tests for individual components of your module’s scripts (e.g., using Bash automated testing frameworks).
For distribution, the GitHub Release is a solid starting point. Users can download modules directly from your repository’s Releases page. You might also consider integrating with a custom update server or Magisk’s module repository if your module qualifies.
Conclusion
Automating your Magisk module build process transforms tedious manual steps into a streamlined, error-free workflow. By starting with simple shell scripts and progressively moving to a full-fledged CI/CD pipeline with GitHub Actions, you can dramatically improve your development efficiency, ensure consistent releases, and provide a better experience for your module’s users. Embrace automation to spend less time on repetitive tasks and more time on innovating your Android modifications.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →