From ed4072956d1039b1df2e107c3aea410dc936baaa Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Sat, 6 Jun 2020 23:38:34 +0200 Subject: [PATCH] pmbootstrap install --ondev: new option Add initial support for the on-device installer in pmbootstrap. Let pmbootstrap create a regular split image, then prepare a new installer rootfs and copy the previously generated rootfs image into the installer rootfs. Put the installer rootfs into a new image, with reserved space. There is more to do from here, such as disabling the generation of the user account when using --ondev. But this requires support in postmarketos-ondev first, so let's build that iteratively. Related: https://wiki.postmarketos.org/wiki/On-device_installer Related: https://gitlab.com/postmarketOS/postmarketos-ondev/-/issues --- pmb/chroot/shutdown.py | 9 +++--- pmb/chroot/zap.py | 1 + pmb/helpers/frontend.py | 18 ++++++++++++ pmb/install/_install.py | 63 +++++++++++++++++++++++++++++++++++++++-- pmb/parse/arch.py | 2 +- pmb/parse/arguments.py | 5 ++++ 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/pmb/chroot/shutdown.py b/pmb/chroot/shutdown.py index 6e6d4d26..fa106839 100644 --- a/pmb/chroot/shutdown.py +++ b/pmb/chroot/shutdown.py @@ -67,10 +67,11 @@ def shutdown(args, only_install_related=False): path = path_outside[len(chroot):] pmb.install.losetup.umount(args, path, auto_init=False) - # Umount device rootfs chroot - chroot_rootfs = args.work + "/chroot_rootfs_" + args.device - if os.path.exists(chroot_rootfs): - pmb.helpers.mount.umount_all(args, chroot_rootfs) + # Umount device rootfs and installer chroots + for prefix in ["rootfs", "installer"]: + path = f"{args.work}/chroot_{prefix}_{args.device}" + if os.path.exists(path): + pmb.helpers.mount.umount_all(args, path) if not only_install_related: # Umount all folders inside args.work diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index c7300dfd..338c0cae 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -53,6 +53,7 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, patterns = [ "chroot_native", "chroot_buildroot_*", + "chroot_installer_*", "chroot_rootfs_*", ] if pkgs_local: diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index b5998fe2..55fc175c 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -204,6 +204,24 @@ def install(args): if args.rsync and not args.sdcard: raise ValueError("Installation using rsync only works on sdcard.") + # On-device installer checks + # Note that this can't be in the mutually exclusive group that has most of + # the conflicting options, because then it would not work with --sdcard. + if args.on_device_installer: + if args.full_disk_encryption: + raise ValueError("--on-device-installer cannot be combined with" + " --fde. The user can choose to encrypt their" + " installation later in the on-device installer.") + if args.android_recovery_zip: + raise ValueError("--on-device-installer cannot be combined with" + " --android-recovery-zip (patches welcome)") + if args.no_image: + raise ValueError("--on-device-installer cannot be combined with" + " --no-image") + if args.rsync: + raise ValueError("--on-device-installer cannot be combined with" + " --rsync") + if not args.sdcard and args.split is None: # Default to split if the flash method requires it flasher = pmb.config.flashers.get(args.deviceinfo["flash_method"], {}) diff --git a/pmb/install/_install.py b/pmb/install/_install.py index 5aae39eb..bc718780 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -11,6 +11,7 @@ import pmb.chroot.apk import pmb.chroot.other import pmb.chroot.initfs import pmb.config +import pmb.config.pmaports import pmb.helpers.devices import pmb.helpers.run import pmb.install.blockdevice @@ -500,6 +501,56 @@ def install_recovery_zip(args): logging.info("") +def install_on_device_installer(args, step, steps): + # Generate the rootfs image + suffix_rootfs = f"rootfs_{args.device}" + install_system_image(args, 0, suffix_rootfs, step=step, steps=steps, + split=True) + step += 2 + + # Prepare the installer chroot + logging.info(f"*** ({step}/{steps}) CREATE ON-DEVICE INSTALLER ROOTFS ***") + step += 1 + packages = ([f"device-{args.device}", + "postmarketos-ondev"] + + get_kernel_package(args, args.device) + + get_nonfree_packages(args, args.device)) + suffix_installer = f"installer_{args.device}" + pmb.chroot.apk.install(args, packages, suffix_installer) + + # Move rootfs image into installer chroot + img = f"{args.device}-root.img" + img_path_src = f"{args.work}/chroot_native/home/pmos/rootfs/{img}" + img_path_dest = f"{args.work}/chroot_{suffix_installer}/var/lib/rootfs.img" + logging.info(f"({suffix_installer}) add {img} as /var/lib/rootfs.img") + pmb.install.losetup.umount(args, img_path_src) + pmb.helpers.run.root(args, ["mv", img_path_src, img_path_dest]) + + # Run ondev-prepare, so it may generate nice configs from the channel + # properties (e.g. to display the version number), or transform the image + # file into another format. This can all be done without pmbootstrap + # changes in the postmarketos-ondev package. + logging.info(f"({suffix_installer}) ondev-prepare-image") + channel = pmb.config.pmaports.read_config(args)["channel"] + channel_cfg = pmb.config.pmaports.read_config_channel(args) + pmb.chroot.root(args, ["ondev-prepare", channel, + channel_cfg["description"], + channel_cfg["branch_pmaports"], + channel_cfg["branch_aports"], + channel_cfg["mirrordir_alpine"]], suffix_installer) + + # Remove $DEVICE-boot.img (we will generate a new one if --split was + # specified, otherwise the separate boot image is not needed) + img_boot = f"{args.device}-boot.img" + logging.info(f"(native) rm {img_boot}") + pmb.chroot.root(args, ["rm", f"/home/pmos/rootfs/{img_boot}"]) + + # Generate installer image + size_reserve = round(os.path.getsize(img_path_dest) / 1024 / 1024) + 200 + install_system_image(args, size_reserve, suffix_installer, "pmOS_install", + step, steps, args.split, args.sdcard) + + def install(args): # Sanity checks if not args.android_recovery_zip and args.sdcard: @@ -510,6 +561,8 @@ def install(args): steps = 2 elif args.android_recovery_zip: steps = 4 + elif args.on_device_installer: + steps = 8 else: steps = 5 @@ -572,6 +625,10 @@ def install(args): elif args.android_recovery_zip: return install_recovery_zip(args) - install_system_image(args, 0, f"rootfs_{args.device}", split=args.split, - sdcard=args.sdcard) - print_flash_info(args) + if args.on_device_installer: + # Runs install_system_image twice + install_on_device_installer(args, 3, steps) + else: + install_system_image(args, 0, f"rootfs_{args.device}", + split=args.split, sdcard=args.sdcard) + print_flash_info(args, steps) diff --git a/pmb/parse/arch.py b/pmb/parse/arch.py index 684c9afd..fea7308f 100644 --- a/pmb/parse/arch.py +++ b/pmb/parse/arch.py @@ -25,7 +25,7 @@ def alpine_native(): def from_chroot_suffix(args, suffix): if suffix == "native": return args.arch_native - if suffix == "rootfs_" + args.device: + if suffix in [f"rootfs_{args.device}", f"installer_{args.device}"]: return args.deviceinfo["arch"] if suffix.startswith("buildroot_"): return suffix.split("_", 1)[1] diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index 248a039f..cd47b298 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -557,6 +557,11 @@ def arguments(): install.add_argument("--no-base", help="do not install postmarketos-base (advanced)", action="store_false", dest="install_base") + install.add_argument("--on-device-installer", "--ondev", + action="store_true", + help="wrap the resulting image in a graphical" + " on-device installer, so the installation can" + " be customized after flashing") group = install.add_mutually_exclusive_group() group.add_argument("--sparse", help="generate sparse image file" " (even if unsupported by device)", default=None,