From 949006c20b23f38d24b9c8b062b60c5262460797 Mon Sep 17 00:00:00 2001 From: Haroun EL ALAMI Date: Tue, 26 Aug 2025 17:29:50 +0200 Subject: [PATCH 1/3] feat(emulator): make root and gapps setup optional --- README.md | 36 +++++++------ docker-compose.yml | 13 +++-- first-boot.sh | 129 ++++++++++++++++++++++++++------------------- start-emulator.sh | 14 +++-- 4 files changed, 116 insertions(+), 76 deletions(-) mode change 100644 => 100755 first-boot.sh mode change 100644 => 100755 start-emulator.sh diff --git a/README.md b/README.md index d7e0594..f0fce8e 100644 --- a/README.md +++ b/README.md @@ -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) > ``` @@ -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 @@ -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` diff --git a/docker-compose.yml b/docker-compose.yml index d505122..89dc443 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,14 +9,16 @@ services: - "5555:5555" volumes: - ./data:/data - - ./extras:/extras + - ./extras:/extras # Magisk modules placed here are auto-installed once root is available environment: - - DNS=one.one.one.one - - RAM_SIZE=8192 + DNS: one.one.one.one + RAM_SIZE: 8192 # 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 @@ -36,3 +38,4 @@ services: adb connect dockerify-android:5555 && npm start " + diff --git a/first-boot.sh b/first-boot.sh old mode 100644 new mode 100755 index da825d8..628c174 --- a/first-boot.sh +++ b/first-boot.sh @@ -1,9 +1,15 @@ #!/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. + # Wait until the boot sequence is fully completed before applying tweaks. COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') while [ "$COMPLETED" != "1" ]; do COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') @@ -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 !!" \ No newline at end of file +echo "Success !!" diff --git a/start-emulator.sh b/start-emulator.sh old mode 100644 new mode 100755 index 11db5f3..9e72344 --- a/start-emulator.sh +++ b/start-emulator.sh @@ -1,7 +1,7 @@ #!/bin/bash -# Check if the .first-boot-done file exists -if [ -f /data/.first-boot-done ]; then +# Use custom ramdisk if present +if [ -f /data/android.avd/ramdisk.img ]; then RAMDISK="-ramdisk /data/android.avd/ramdisk.img" fi @@ -13,5 +13,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} From cda7eadd6711b17625c9acfc3979cb01b1466cac Mon Sep 17 00:00:00 2001 From: Haroun EL ALAMI Date: Tue, 26 Aug 2025 21:58:34 +0200 Subject: [PATCH 2/3] fix: update comments for clarity in docker-compose and first-boot scripts --- docker-compose.yml | 2 +- first-boot.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89dc443..ff9e5b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: - "5555:5555" volumes: - ./data:/data - - ./extras:/extras # Magisk modules placed here are auto-installed once root is available + - ./extras:/extras environment: DNS: one.one.one.one RAM_SIZE: 8192 diff --git a/first-boot.sh b/first-boot.sh index 628c174..20aafe0 100755 --- a/first-boot.sh +++ b/first-boot.sh @@ -9,7 +9,7 @@ bool_true() { apply_settings() { adb wait-for-device - # Wait until the boot sequence is fully completed before applying tweaks. + # Waiting for the boot sequence to be completed. COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') while [ "$COMPLETED" != "1" ]; do COMPLETED=$(adb shell getprop sys.boot_completed | tr -d '\r') From e3fdfdcfe2009849d39b99b42e973cf7e5c7fc3b Mon Sep 17 00:00:00 2001 From: Haroun EL ALAMI Date: Wed, 27 Aug 2025 00:07:48 +0200 Subject: [PATCH 3/3] fix: remove unnecessary crashpad_handler and improve healthcheck --- Dockerfile | 6 ++++-- docker-compose.yml | 2 +- start-emulator.sh | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index cfdfb19..29e0701 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ @@ -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"] diff --git a/docker-compose.yml b/docker-compose.yml index ff9e5b1..1dd05c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: - ./extras:/extras environment: DNS: one.one.one.one - RAM_SIZE: 8192 + # RAM_SIZE: 4096 # Optional screen resolution in WIDTHxHEIGHT format #SCREEN_RESOLUTION: 720x720 # Optional screen density (dpi) diff --git a/start-emulator.sh b/start-emulator.sh index 9e72344..3411601 100755 --- a/start-emulator.sh +++ b/start-emulator.sh @@ -1,5 +1,8 @@ #!/bin/bash +# 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"