AndroidPatch Module (APM)
About 2984 wordsAbout 10 min
2025-12-23
FolkPatch provides a module mechanism ( AndroidPatch Module ), which can modify system partitions while maintaining their integrity; this mechanism is commonly referred to as systemless.
FolkPatch's module implementation is copied and modified from APatch modules, thanks to APatch.
The following documentation is largely derived from KernelSU documentation, with most content consistent with KernelSU. The main differences to note are:
- File locations
- Environment variables
- SELinux support, FolkPatch directly uses
magiskpolicy
FolkPatch's module operation mechanism is almost identical to Magisk. If you are familiar with Magisk module development, developing FolkPatch modules is very similar. You can skip the module introduction below and just understand the differences between FolkPatch modules and Magisk modules.
BusyBox
FolkPatch provides a fully featured BusyBox binary (including full SELinux support). The executable is located at /data/adb/ap/bin/busybox. FolkPatch's BusyBox supports runtime toggleable "ASH Standalone Shell Mode". This standalone mode means that when running BusyBox's ash shell, every command will directly use the applet built into BusyBox, regardless of what PATH is set to. For example, commands like ls, rm, chmod will not use the commands set in PATH (in Android's case, usually /system/bin/ls, /system/bin/rm, and /system/bin/chmod by default), but instead call BusyBox's built-in applets directly. This ensures that scripts always run in a predictable environment and always have a full suite of commands, regardless of which Android version it is running on. To force a command not to use BusyBox, you must call the executable with its full path.
Every shell script running in the FolkPatch context will run in BusyBox's ash shell in standalone mode. This includes all boot scripts and module installation scripts relevant to third-party developers.
For users who want to use this "standalone mode" feature outside of FolkPatch, there are two ways to enable it:
- Set environment variable
ASH_STANDALONEto1.
Example:ASH_STANDALONE=1 /data/adb/ap/bin/busybox sh <script>. - Use command line option toggle:
/data/adb/ap/bin/busybox sh -o standalone <script>.
To ensure all subsequent sh shells execute in standalone mode, the first method is preferred (this is also the method used internally by FolkPatch), as environment variables are inherited by child processes.
Difference from Magisk
FolkPatch's BusyBox is now a binary compiled directly using the Magisk project, thanks to Magisk! Therefore, you don't have to worry about BusyBox script compatibility between Magisk and FolkPatch, because they are exactly the same!
FolkPatch Module
A FolkPatch module is a folder placed inside /data/adb/modules that satisfies the following structure:
$MODID# Module folder named after module ID
module.prop# Module configuration (ID, version, etc.)
system# This directory is typically mounted to the system
...
skip_mount# If present, the module's /system won't be mounted
disable# If present, the module will be disabled
remove# If present, the module will be removed on next reboot
post-fs-data.sh# Runs in post-fs-data mode
post-mount.sh# Runs in post-mount mode
service.sh# Runs in late_start service mode
boot-completed.sh# Runs after Android boot completes
uninstall.sh# Runs when the module is uninstalled
action.sh# Runs when Action is clicked in the manager
system.prop# System properties applied via resetprop
sepolicy.rule# SELinux policy rules loaded at boot
vendor# → $MODID/system/vendor (auto-generated)
product# → $MODID/system/product (auto-generated)
system_ext# → $MODID/system/system_ext (auto-generated)
another_module
...
...
Difference from Magisk
FolkPatch does not have built-in Zygisk support, so there is no Zygisk related content in modules.
module.prop
module.prop is a configuration file for the module. In FolkPatch, if a module does not contain this file, it will not be recognized as a module. The format of this file is as follows:
id=<string>
name=<string>
version=<string>
versionCode=<int>
author=<string>
description=<string>idRequiredstring
Module's unique identifier. Must match regex ^[a-zA-Z][a-zA-Z0-9._-]+$. Examples: a_module, a.module, module-101. Invalid: a module, 1_module, -a-module. Should not be changed after release.
nameRequiredstring
Module display name. Can be any single-line string.
versionRequiredstring
Module version name. Can be any single-line string.
versionCodeRequiredint
Module version code. Must be an integer, used for version comparison.
authorRequiredstring
Module author name. Can be any single-line string.
descriptionRequiredstring
Module description. Can be any single-line string.
Please ensure to use UNIX (LF) line endings, not Windows (CR + LF) or Macintosh (CR).
Shell Scripts
Please read the Boot Scripts section to understand the differences between post-fs-data.sh, post-mount.sh, service.sh, and boot-completed.sh. For most module developers, if you only need to run a startup script, service.sh should be sufficient.
In all your module scripts, please use MODDIR=${0%/*} to get your module's base directory path; do not hardcode your module path in scripts.
Difference from Magisk, KernelSU
You can use the environment variable APATCH to determine if the script is running in FolkPatch. If running in FolkPatch, this value will be set to true.
FolkPatch's module system is consistent with APatch at the underlying implementation level. For module developers, FolkPatch modules are fully compatible with APatch modules.
system Directory
The contents of the module's system directory will be overlaid on top of the system's /system partition after system boot via mounting, which means:
- Files with the same name in the corresponding directory in the system will be overwritten by files in this directory.
- Folders with the same name in the corresponding directory in the system will be merged with folders in this directory.
FolkPatch supports two mount implementations:
Magic Mount (Built-in Mount)
FolkPatch adds Magic Mount (a Magisk-like bind mount mechanism) on top of APatch. When the mount system is enabled in settings, FolkPatch uses the built-in Magic Mount engine to handle the module's system directory mounting.
This works similarly to Magisk's Magic Mount, mapping module files to the system partition via bind mount.
Magic Mount is controlled by the /data/adb/.magic_mount_enable marker file, and the mount source directory is /data/adb/ap/magic_mount.
OverlayFS (via Meta Module)
If a Meta Module is installed, the meta module can take over the mounting logic via the metamount.sh script, using OverlayFS or other custom mounting schemes.
In OverlayFS mode:
- If you want to delete a file or folder in the original system directory, you need to create a file with the same name
filenamein the module directory usingmknod filename c 0 0; this way the OverlayFS system will automatically whiteout effectively deleting this file. - If you want to replace a system directory, you need to create a directory with the same path in the module directory, and then set this attribute for the directory:
setfattr -n trusted.overlay.opaque -v y <TARGET>.
If you are interested in OverlayFS, it is recommended to read the Linux Kernel OverlayFS documentation.
Mount Method Selection
FolkPatch defaults to not mounting module files. If you need mounting, choose one of the following:
- Built-in Magic Mount: Enable via Settings -> General -> Enable Mount System, no extra modules needed
- Meta Module Mount: After installing a meta module, the meta module controls mounting behavior
For detailed comparison of mount methods, please refer to Mount Implementation.
system.prop
The format of this file is exactly the same as build.prop: each line is in the form of [key]=[value].
sepolicy.rule
If your module needs some additional SELinux policy patches, please add these rules to this file. Each line in this file will be treated as a policy statement.
Module Installer
A FolkPatch module installation package is a zip file that can be flashed via the FolkPatch Manager APP. The format of this zip file is as follows:
module.zip
customize.sh# Optional, installation customization script
...# Other module files
Install from External
Select a module .zip file in your file manager, then use "Share" or "Open with" to choose FolkPatch. This will navigate directly to the module installation page.
Selecting multiple .zip files and sharing to FolkPatch will navigate to the Batch Install page instead.
注意
FolkPatch modules do not support installation in Recovery!
Batch Install
FolkPatch supports installing multiple APM modules at once, suitable for batch flashing scenarios.
Accessing Batch Install
There are two ways to access the batch install page:
- Within Manager: On the System Modules (APM) page, tap the "Bulk Install" button in the floating menu, then manually add modules on the batch install page
- External Multi-Select: Select multiple
.zipmodule files in your file manager, share to FolkPatch, and it will automatically navigate to the batch install page
Install Modes
Batch install uses Quick Install Mode (silent install) by default. All modules are installed sequentially without user interaction, and a log is displayed upon completion.
Quick Install Limitations
Quick install mode does not support modules that require volume key interaction (e.g., modules using volume_key in customize.sh). If your modules need volume key input, enable "APM Batch Install Full Process" in settings.
If you need the full installation process (one-by-one processing with volume key support), enable it in Settings → Module → APM Batch Install Full Process. When enabled, batch install processes each module through the full installation screen individually.
Customizing Installation
If you want to control the module installation process, you can create a file named customize.sh in the module directory. This script will be sourced into the current shell after the module is extracted. This script is very useful if your module needs to perform additional operations based on device API version or architecture.
If you want to fully control the script installation process, you can declare SKIPUNZIP=1 in customize.sh to skip all default installation steps; in this case, you need to handle all installation processes yourself (such as extracting modules, setting permissions, etc.).
The customize.sh script runs in FolkPatch's BusyBox ash shell in "standalone mode". You can use the following variables and functions:
Variables
KernelPatch related:
KERNELPATCHOptionalbool
Marks that this script is running in KernelPatch environment, this variable's value will always be true
KERNEL_VERSIONOptionalhex
Inherited from KernelPatch, kernel version number (e.g., 50a01 means 5.10.1)
KERNELPATCH_VERSIONOptionalhex
Inherited from KernelPatch, KernelPatch version number (e.g., a05 means 0.10.5)
SUPERKEYOptionalstring
Inherited from KernelPatch, used to call kpatch or supercall
APatch/FolkPatch related:
APATCHOptionalbool
Marks that this script is running in APatch/FolkPatch environment, this variable's value will always be true
APATCH_VER_CODEOptionalint
APatch current version code (e.g., 10672)
APATCH_VEROptionalstring
APatch current version name (e.g., 10672)
Installation environment:
BOOTMODEOptionalbool
This variable is always true in FolkPatch
MODPATHOptionalpath
Current module installation directory
TMPDIROptionalpath
Directory for storing temporary files
ZIPFILEOptionalpath
Current module installation package file
ARCHOptionalstring
Device CPU architecture, only arm64
IS64BITOptionalbool
Whether it is a 64-bit device
APIOptionalint
Current device Android API version (e.g., 23 on Android 6.0)
Magisk Compatibility
MAGISK_VER_CODE in FolkPatch is 27000, MAGISK_VER is 27.0. These are compatibility variables set for Magisk module compatibility. FolkPatch itself is not based on Magisk.
Functions
ui_print <msg>
print <msg> to console
Avoid using 'echo' as it will not display in custom recovery's console
abort <msg>
print error message <msg> to console and terminate the installation
Avoid using 'exit' as it will skip the termination cleanup steps
set_perm <target> <owner> <group> <permission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
this function is a shorthand for the following commands:
chown owner.group target
chmod permission target
chcon context target
set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
for all files in <directory>, it will call:
set_perm file owner group filepermission context
for all directories in <directory> (including itself), it will call:
set_perm dir owner group dirpermission contextBoot Scripts
In FolkPatch, depending on the script running mode, there are the following types:
post-fs-data mode
- This stage is blocking. The boot process is paused until execution is complete or 10 seconds have passed.
- Scripts run before any modules are mounted. This allows module developers to dynamically adjust their modules before they are mounted.
- This stage happens before Zygote starts.
- Using
setpropwill cause boot process deadlock! Please useresetprop -n <prop_name> <prop_value>instead. - Run scripts in this mode only when necessary.
post-mount mode
- This stage runs after module mounting is complete.
- Suitable for operations that need to execute after modules are mounted.
late_start service mode
- This stage is non-blocking. Your script runs in parallel with the rest of the boot process.
- Most scripts are recommended to run in this mode.
boot-completed mode
- This stage runs in service mode after the Android system has finished booting.
- Suitable for operations that need to execute after the system is fully started.
In FolkPatch, boot scripts are also divided into two types based on storage location: general scripts and module scripts.
General Scripts
Placed in
/data/adb/post-fs-data.d,/data/adb/post-mount.d,/data/adb/service.dor/data/adb/boot-completed.d./data/adb
post-fs-data.d# post-fs-data mode
post-mount.d# post-mount mode
service.d# late_start service mode
boot-completed.d# after Android boot completes
Only executed when the script is set as executable (
chmod +x script.sh).Scripts in
post-fs-data.drun in post-fs-data mode, scripts inservice.drun in late_start service mode, and so on.Modules should not add general scripts during installation.
Module Scripts
- Placed in the module's own folder.
- Only executed when the module is enabled.
post-fs-data.shruns in post-fs-data mode,post-mount.shruns in post-mount mode,service.shruns in late_start service mode, andboot-completed.shruns in service mode after Android system boot completed.
All boot scripts will run in FolkPatch's BusyBox ash shell with "standalone mode" enabled.
Module Development Example
Below is a complete module development example demonstrating the coding conventions for each core file. The code tree sidebar on the left shows the file list — click a file to view its content.
module.prop
system
etc
hosts
bin
demo-tool
vendor
etc
demo-vendor.conf
product
etc
sysconfig
demo-feature.xml
system_ext
etc
sysconfig
demo-ext.xml
customize.sh
service.sh
post-fs-data.sh
system.prop
sepolicy.rule
id=demo_module
name=Demo Module
version=1.0.0
versionCode=100
author=demo_developer
description=A demo module demonstrating FolkPatch module development# Replaces /system/etc/hosts via mount overlay
127.0.0.1 localhost
::1 ip6-localhost
# Ad-blocking entries
127.0.0.1 ad.example.com
127.0.0.1 tracker.example.com
127.0.0.1 ads.example.net#!/system/bin/sh
# Added to /system/bin — available system-wide after mount
case "$1" in
status)
echo "Demo Module: $(getprop persist.demo.module.active)"
;;
version)
echo "Demo Module v1.0.0"
;;
*)
echo "Usage: demo-tool {status|version}"
;;
esac# Mounted to /vendor/etc/demo-vendor.conf
# vendor → MODID/system/vendor (auto-generated symlink)
demo.vendor.feature=enabled
demo.vendor.mode=performance<?xml version="1.0" encoding="utf-8"?>
<!-- Added to /product/etc/sysconfig/ -->
<!-- product → MODID/system/product (auto-generated symlink) -->
<permissions>
<feature name="com.demo.feature" />
</permissions><?xml version="1.0" encoding="utf-8"?>
<!-- Added to /system_ext/etc/sysconfig/ -->
<!-- system_ext → MODID/system/system_ext (auto-generated symlink) -->
<permissions>
<feature name="com.demo.ext.feature" />
</permissions># ============================================
# Demo Module - Installation Script
# Runs in BusyBox ash standalone mode
# ============================================
# Verify FolkPatch/APatch environment
if [ "$APATCH" != true ]; then
abort "This module requires FolkPatch or APatch"
fi
# Device compatibility checks
if [ "$API" -lt 26 ]; then
abort "Android 8.0 (API 26) or above is required (current: $API)"
fi
ui_print "========================"
ui_print " Demo Module v1.0.0"
ui_print "========================"
ui_print "Arch: $ARCH"
ui_print "API: $API"
ui_print "ModPath: $MODPATH"
# Set permissions for system overlay files
if [ -d "$MODPATH/system" ]; then
set_perm_recursive "$MODPATH/system" 0 0 0755 0644
# Executables in /system/bin need execute permission
if [ -d "$MODPATH/system/bin" ]; then
set_perm_recursive "$MODPATH/system/bin" 0 2000 0755 0755 \
u:object_r:system_file:s0
fi
fi
# To fully control installation, uncomment the following:
# SKIPUNZIP=1
# ...then handle extraction and permissions manually
ui_print "Installation complete!"# ============================================
# Demo Module - Late Start Service
# Runs in parallel with boot process (non-blocking)
# Recommended for most startup tasks
# ============================================
MODDIR=${0%/*}
# Wait for system services to stabilize
sleep 60
# Execute post-boot tasks in background
if [ -f "$MODDIR/scripts/post-boot.sh" ]; then
sh "$MODDIR/scripts/post-boot.sh" &
fi# ============================================
# Demo Module - Pre-mount Script
# WARNING: This stage is BLOCKING (max 10s)!
# NEVER use setprop here - it causes deadlock.
# Use resetprop -n instead.
# Only use this when absolutely necessary.
# ============================================
MODDIR=${0%/*}
# Safely set properties before modules are mounted
resetprop -n persist.demo.module.active 1# Demo Module Properties
persist.demo.module.active=1
persist.demo.module.version=1.0.0# SELinux policy rules (processed by magiskpolicy)
# Format: allow <source> <target> <class> <permission>
allow demo_app demo_data_file file { read open getattr }Copyright
Copyright Ownership:APatch Document
This article is reprint from:https://apatch.dev/zh_CN/apm-guide.html(Open in new window)
License under:Attribution-ShareAlike 4.0 International
