Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ RUN mkdir /root/.android/ && \

# Detect architecture and set environment variable
RUN yes | sdkmanager --sdk_root=$ANDROID_HOME "emulator" "platform-tools" "platforms;android-30" "system-images;android-30;default;x86_64"
# remove /opt/android-sdk/emulator/crashpad_handler
RUN rm -f /opt/android-sdk/emulator/crashpad_handler
# RUN if [ "$(uname -m)" = "aarch64" ]; then \
# unzip /root/emulator.zip -d $ANDROID_HOME && \
# mv /root/package.xml $ANDROID_HOME/emulator/package.xml && \
Expand Down Expand Up @@ -73,8 +75,8 @@ RUN chmod +x /root/start-emulator.sh
EXPOSE 5554 5555

# Healthcheck to ensure the emulator is running
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD adb devices | grep emulator-5554 || exit 1
HEALTHCHECK --interval=10s --timeout=10s --retries=600 \
CMD adb devices | grep emulator-5554 && test -f /data/.first-boot-done || exit 1

# Start Supervisor to manage the emulator
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
Expand Down
36 changes: 21 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,32 +135,38 @@ scrcpy -s localhost:5555

> **Note:** Ensure `scrcpy` is installed on your host machine. [Installation Guide](https://github.com/Genymobile/scrcpy#installation)

### Customizing Device Screen
## ⚙️ **Environment Variables**

The emulator's display can be adjusted with environment variables:
| Variable | Description | Default |
| --- | --- | --- |
| `DNS` | Private DNS server used inside the emulator | `one.one.one.one` |
| `RAM_SIZE` | RAM in megabytes allocated to the emulator | `4096` |
| `SCREEN_RESOLUTION` | Screen size in `WIDTHxHEIGHT` format (e.g. `1080x1920`) | device default |
| `SCREEN_DENSITY` | Screen pixel density in DPI | device default |
| `ROOT_SETUP` | Set to `1` to enable rooting and Magisk. Can be turned on after the first start but cannot be undone without recreating the data volume. | `0` |
| `GAPPS_SETUP` | Set to `1` to install PICO GAPPS. Can be turned on after the first start but cannot be undone without recreating the data volume. | `0` |

- `SCREEN_RESOLUTION` (optional): sets the screen size in `WIDTHxHEIGHT` format.
- `SCREEN_DENSITY` (optional): overrides the device pixel density in DPI.

Configure these variables in your `docker-compose.yml` file (the provided example contains commented entries for reference). If `SCREEN_RESOLUTION` or `SCREEN_DENSITY` are omitted, the emulator uses its default settings.

## 🔄 **First Boot Process**

The first time you start the container, it will perform a comprehensive setup process that includes:

1. **AVD Creation:** Creates a new Android Virtual Device running Android 30 (Android 11)
2. **Installing PICO GAPPS:** Adds essential Google services to the emulator
3. **Rooting the Device:** Performs multiple reboots to:
2. **PICO GAPPS Installation** (when `GAPPS_SETUP=1`): Adds essential Google services.
3. **Rooting the Device** (when `ROOT_SETUP=1`): Performs multiple reboots to:
- Disable AVB verification
- Remount system as writable
- Install root access via the rootAVD script
- Install PICO GAPPS components
- Configure optimal device settings
- Install Magisk for root access
- Reboot to apply root
4. **Extras Copied:** Pushes everything from the `extras` directory to `/sdcard/Download` so files like APKs or Magisk modules are ready for manual installation on the device.
5. **Configuring optimal device settings**

`ROOT_SETUP` and `GAPPS_SETUP` are checked on every start. If you enable them after the first boot, the script installs the requested components once and marks them complete so they won't run again. Removing them later requires recreating the data volume.

> **Important:** The first boot can take 10-15 minutes to complete. You'll know the process is finished when you see the following log output:
> ```
> Broadcast completed: result=0
> Sucess !!
> Success !!
> 2025-04-22 13:45:18,724 INFO exited: first-boot (exit status 0; expected)
> ```

Expand Down Expand Up @@ -196,7 +202,7 @@ This includes:
- [ ] Support for additional Android versions
- [x] Integration with CI/CD pipelines
- [ ] Support ARM64 CPU architecture
- [x] Preinstall PICO GAPPS
- [x] PICO GAPPS installation
- [x] Support Magisk
- [x] Adding web interface of [scrcpy](https://github.com/Shmayro/ws-scrcpy-docker)
- [x] Redirect all logs to container stdout/stderr
Expand All @@ -217,8 +223,8 @@ This includes:

- **First Boot Taking Too Long:**
- This is normal, as the first boot process needs to perform several operations including:
- Installing GAPPS
- Rooting the device
- Installing GAPPS (if enabled)
- Rooting the device (if enabled)
- Configuring system settings
- The process can take 10-15 minutes depending on your system performance
- You can monitor progress with `docker logs -f dockerify-android`
Expand Down
11 changes: 7 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ services:
- ./data:/data
- ./extras:/extras
environment:
- DNS=one.one.one.one
- RAM_SIZE=8192
DNS: one.one.one.one
# RAM_SIZE: 4096
# Optional screen resolution in WIDTHxHEIGHT format
#- SCREEN_RESOLUTION=720x720
#SCREEN_RESOLUTION: 720x720
# Optional screen density (dpi)
#- SCREEN_DENSITY=227
#SCREEN_DENSITY: 227
ROOT_SETUP: 0 # set to 1 to enable rooting
GAPPS_SETUP: 0 # set to 1 to install PICO GAPPS
privileged: true
devices:
- /dev/kvm
Expand All @@ -36,3 +38,4 @@ services:
adb connect dockerify-android:5555 &&
npm start
"

127 changes: 75 additions & 52 deletions first-boot.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#!/bin/bash

# apply settings
bool_true() {
case "${1,,}" in
1|true|yes) return 0 ;;
*) return 1 ;;
esac
}

apply_settings() {
adb wait-for-device
# Waiting for the boot sequence to be completed.
Expand All @@ -24,67 +30,84 @@ apply_settings() {
adb shell svc wifi enable
}

# Detect ip and forward ADB ports from the container's network
# interface to localhost.
prepare_system() {
adb wait-for-device
adb root
adb shell avbctl disable-verification
adb disable-verity
adb reboot
adb wait-for-device
adb root
adb remount
}

install_gapps() {
prepare_system
echo "Installing GAPPS ..."
wget https://netcologne.dl.sourceforge.net/project/opengapps/x86_64/20220503/open_gapps-x86_64-11.0-pico-20220503.zip?viasf=1 -O gapps-11.zip
unzip gapps-11.zip 'Core/*' -d gapps-11 && rm gapps-11.zip
rm gapps-11/Core/setup*
lzip -d gapps-11/Core/*.lz
for f in gapps-11/Core/*.tar; do
tar -x --strip-components 2 -f "$f" -C gapps-11
done
adb push gapps-11/etc /system
adb push gapps-11/framework /system
adb push gapps-11/app /system
adb push gapps-11/priv-app /system
rm -r gapps-11
touch /data/.gapps-done
}

install_root() {
adb wait-for-device
echo "Root Script Starting..."
# Root the AVD by patching the ramdisk.
git clone https://gitlab.com/newbit/rootAVD.git
pushd rootAVD
sed -i 's/read -t 10 choice/choice=1/' rootAVD.sh
./rootAVD.sh system-images/android-30/default/x86_64/ramdisk.img
cp /opt/android-sdk/system-images/android-30/default/x86_64/ramdisk.img /data/android.avd/ramdisk.img
popd
echo "Root Done"
sleep 10
rm -r rootAVD
touch /data/.root-done
}

copy_extras() {
adb wait-for-device
# Push any Magisk modules for manual installation later
for f in $(ls /extras/*); do
adb push $f /sdcard/Download/
done
}

# Detect the container's IP and forward ADB to localhost.
LOCAL_IP=$(ip addr list eth0 | grep "inet " | cut -d' ' -f6 | cut -d/ -f1)
socat tcp-listen:"5555",bind="$LOCAL_IP",fork tcp:127.0.0.1:"5555" &

echo "Emulator is healthy. Proceeding..."
gapps_needed=false
root_needed=false
if bool_true "$GAPPS_SETUP" && [ ! -f /data/.gapps-done ]; then gapps_needed=true; fi
if bool_true "$ROOT_SETUP" && [ ! -f /data/.root-done ]; then root_needed=true; fi

# Check if the script has already run
# Skip initialization if first boot already completed.
if [ -f /data/.first-boot-done ]; then
[ "$gapps_needed" = true ] && install_gapps && [ "$root_needed" = false ] && adb reboot
[ "$root_needed" = true ] && install_root
apply_settings
copy_extras
exit 0
fi

echo "Init ADV ..."

echo "Init AVD ..."
echo "no" | avdmanager create avd -n android -k "system-images;android-30;default;x86_64"

echo "Preparation ..."

adb wait-for-device
adb root
adb shell avbctl disable-verification
adb disable-verity
adb reboot
adb wait-for-device
adb root
adb remount
for f in $(ls /extras/*); do
adb push $f /sdcard/Download/
done

echo "Installing GAPPS ..."

wget https://netcologne.dl.sourceforge.net/project/opengapps/x86_64/20220503/open_gapps-x86_64-11.0-pico-20220503.zip?viasf=1 -O gapps-11.zip
unzip gapps-11.zip 'Core/*' -d gapps-11 && rm gapps-11.zip
rm gapps-11/Core/setup*
lzip -d gapps-11/Core/*.lz
for f in $(ls gapps-11/Core/*.tar); do
tar -x --strip-components 2 -f $f -C gapps-11
done

adb push gapps-11/etc /system
adb push gapps-11/framework /system
adb push gapps-11/app /system
adb push gapps-11/priv-app /system

echo "Root Script Starting..."

# Root the VM
git clone https://gitlab.com/newbit/rootAVD.git
pushd rootAVD
sed -i 's/read -t 10 choice/choice=1/' rootAVD.sh
./rootAVD.sh system-images/android-30/default/x86_64/ramdisk.img
cp /opt/android-sdk/system-images/android-30/default/x86_64/ramdisk.img /data/android.avd/ramdisk.img
popd
echo "Root Done"
sleep 15
echo "Cleanup ..."
# done
rm -r gapps-11
rm -r rootAVD
[ "$gapps_needed" = true ] && install_gapps && [ "$root_needed" = false ] && adb reboot
[ "$root_needed" = true ] && install_root
apply_settings
copy_extras

touch /data/.first-boot-done
echo "Sucess !!"
echo "Success !!"
17 changes: 14 additions & 3 deletions start-emulator.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/bin/bash

# Check if the .first-boot-done file exists
if [ -f /data/.first-boot-done ]; then
# Kill any running emulator instances before starting a new one
pkill -f "/opt/android-sdk/emulator/emulator"

# Use custom ramdisk if present
if [ -f /data/android.avd/ramdisk.img ]; then
RAMDISK="-ramdisk /data/android.avd/ramdisk.img"
fi

Expand All @@ -13,5 +16,13 @@ if [ -n "$SCREEN_DENSITY" ]; then
SCREEN_DENSITY_FLAG="-dpi-device $SCREEN_DENSITY"
fi

# Configure optional screen resolution and density
if [ -n "$SCREEN_RESOLUTION" ]; then
SCREEN_RESOLUTION_FLAG="-skin $SCREEN_RESOLUTION"
fi
if [ -n "$SCREEN_DENSITY" ]; then
SCREEN_DENSITY_FLAG="-dpi-device $SCREEN_DENSITY"
fi

# Start the emulator with the appropriate ramdisk.img
/opt/android-sdk/emulator/emulator -avd android -nojni -netfast -writable-system -no-window -no-audio -no-boot-anim -skip-adb-auth -gpu swiftshader_indirect -no-snapshot -no-metrics $SCREEN_RESOLUTION_FLAG $SCREEN_DENSITY_FLAG $RAMDISK -qemu -m "${RAM_SIZE:-4096}"
/opt/android-sdk/emulator/emulator -avd android -nojni -netfast -writable-system -no-window -no-audio -no-boot-anim -skip-adb-auth -gpu swiftshader_indirect -no-snapshot -no-metrics $SCREEN_RESOLUTION_FLAG $SCREEN_DENSITY_FLAG $RAMDISK -qemu -m ${RAM_SIZE:-4096}