#!/usr/bin/env python3 import argparse import os import subprocess import sys from stuff.houdini import Houdini from stuff.houdini_hack import Houdini_Hack from stuff.magisk import Magisk from stuff.mindthegapps import MindTheGapps import tools.compat as compat import tools.helper as helper SUPPORTED_ANDROID_VERSIONS = ["13.0.0"] def ensure_base_image(container_runtime, android_version): base_image = "redroid/redroid:{}-latest".format(android_version) inspect = subprocess.run( [container_runtime, "image", "inspect", base_image], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) local_present = inspect.returncode == 0 tar_name = "redroid-redroid-{}-latest.tar".format(android_version) tar_path = os.path.join(helper.get_image_dir(), tar_name) tar_exists = os.path.isfile(tar_path) offline_only = helper.is_offline_only() if local_present: helper.print_color( "Using existing local base image {} ...".format(base_image), helper.bcolors.GREEN, ) return "present" if tar_exists: helper.print_color( "Loading base image from offline archive {} ...".format(tar_path), helper.bcolors.GREEN, ) subprocess.run([container_runtime, "load", "-i", tar_path], check=True) return tar_path if offline_only: raise FileNotFoundError( "REDROID_OFFLINE is set but {} is not in local storage and {} was not found".format( base_image, tar_path, ) ) helper.print_color( "No local base image or offline .tar; pulling {} ...".format(base_image), helper.bcolors.YELLOW, ) pull = subprocess.run([container_runtime, "pull", base_image], check=False) if pull.returncode == 0: return "pulled" raise FileNotFoundError( "Pull failed for {} and offline archive {} was not found".format( base_image, tar_path, ) ) def build_layers(args): dockerfile = "FROM redroid/redroid:{}-latest\n".format(args.android) tags = [args.android] if args.mindthegapps: MindTheGapps(args.android).install() dockerfile += "COPY mindthegapps /\n" tags.append("mindthegapps") if args.houdini: arch = helper.host()[0] if arch not in {"x86", "x86_64"}: raise ValueError("Houdini only makes sense on x86/x86_64 hosts") Houdini(args.android).install() Houdini_Hack(args.android).install() dockerfile += "COPY houdini /\n" tags.append("houdini") if args.magisk: os.environ.setdefault("REDROID_MAGISK_BOOT_COMPLETE", "1") Magisk().install() dockerfile += "COPY magisk /\n" tags.append("magisk") return dockerfile, tags def print_recommended_flow(image_name): print("\nRecommended 5570 launch (headless: always pass --adb-insecure)") print( "python run_redroid.py " "--image {} " "--bridge libhoudini " "--device-profile pixel-7-pro " "--data-dir ~/data-redroid13 " "--port 5570 " "--replace " "--adb-insecure\n".format(image_name) ) print("Recommended post-boot hardening") print( "python bootstrap_redroid.py " "--serial 127.0.0.1:5570 " "--device-profile pixel-7-pro " "--proxy-host auto " "--install-extreme-script\n" ) def main(): parser = argparse.ArgumentParser( description="Build the minimal Android 13 redroid image used for the optimized 5570 setup." ) parser.add_argument( "-a", "--android-version", dest="android", default="13.0.0", choices=SUPPORTED_ANDROID_VERSIONS, help="Android base image to build from", ) parser.add_argument( "-mtg", "--install-mindthegapps", dest="mindthegapps", action="store_true", help="Install MindTheGapps into the image", ) parser.add_argument( "-i", "--install-houdini", dest="houdini", action="store_true", help="Install libhoudini and the Android 13 compatibility hack", ) parser.add_argument( "-m", "--install-magisk", dest="magisk", action="store_true", help="Install Kitsune Mask / Magisk Delta bootless payload", ) parser.add_argument( "-c", "--container", dest="container", default="docker", choices=["docker", "podman"], help="Container runtime", ) parser.add_argument( "--offline", dest="offline", action="store_true", help="Do not pull images or download archives; require offline/downloads and offline/images", ) parser.add_argument( "--no-compat-check", dest="no_compat_check", action="store_true", help="Skip container-runtime / OS compatibility checks before build", ) args = parser.parse_args() if args.offline: os.environ["REDROID_OFFLINE"] = "1" if not args.no_compat_check: build_report = compat.analyze_for_build(args.container) compat.emit_compat_report(build_report) if build_report.errors: sys.exit(1) base_image_source = ensure_base_image(args.container, args.android) dockerfile, tags = build_layers(args) print("\nDockerfile\n{}".format(dockerfile)) with open("./Dockerfile", "w", encoding="utf-8") as handle: handle.write(dockerfile) image_name = "redroid/redroid:" + "_".join(tags) subprocess.run([args.container, "build", "-t", image_name, "."], check=True) helper.print_color( "Successfully built {}".format(image_name), helper.bcolors.GREEN, ) print("Base image source: {}".format(base_image_source)) if args.houdini: print_recommended_flow(image_name) if __name__ == "__main__": main()