Compare commits

...

14 Commits

Author SHA1 Message Date
Oliver Smith ed4072956d
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
2020-06-09 15:48:12 +02:00
Oliver Smith 8d0f3a1c55
install_system_image: add sdcard argument
The on-device installer will run install_system_image once with
sdcard=None and the second time with sdcard=args.sdcard.
2020-06-09 15:48:12 +02:00
Oliver Smith 9a0ef38ee3
install_system_image: add split argument 2020-06-09 15:48:11 +02:00
Oliver Smith 21a6c7189b
install_system_image: add step, steps parameters 2020-06-09 15:48:11 +02:00
Oliver Smith 4a5137064d
install_system_image: add root_label parameter
Prepare for on-device installer, so it can use something other than
"pmOS_root" as label.
2020-06-09 15:48:11 +02:00
Oliver Smith 4bebad987d
install_system_image: add suffix argument
Allow files to be copied from a different suffix than rootfs_$DEVICE.
The on-device installer will use this.
2020-06-09 15:48:11 +02:00
Oliver Smith fdc2fe309e
Cosmetics: install_system_image(): remove FDE msg
Full disk encryption (--fde) has not been the default for a long time,
so no need to warn the user about it.
2020-06-09 15:48:11 +02:00
Oliver Smith c940c96078
pmb.install._install.print_flash_info: cosmetics
Fix a typo and wrap lines at 80 characters (especially one extra long
line). Use f-strings in lines that were modified (as we're doing it
nowadays).
2020-06-09 15:48:11 +02:00
Oliver Smith 854c96f63f
pmb.install._install.print_flash_info: new func
Move code that prints flashing information from install_system_image()
to its own function. For the on-device installer, we'll need to call
install_system_image() twice, without printing the flashing information
each time. While at it, add a "step" parameter.
2020-06-09 15:48:11 +02:00
Oliver Smith 67e3c5ad1a
pmbootstrap install: support size_reserve
Create an empty partition between boot and root. This will be used by
the on-device installer, as explained in detail here:
https://wiki.postmarketos.org/wiki/On-device_installer
2020-06-09 15:48:11 +02:00
Oliver Smith d8c84c8912
Cosmetic: pmb.install.partition: fix comment 2020-06-09 15:48:11 +02:00
Oliver Smith 3f1f21add5
pmb/install: have size_boot, size_root in MB
Prepare for a future patch, that adds reserved space in MB, by changing
size_boot and size_root from bytes to MB everywhere. This is what we need
most of the time and allows to drop some /1024**2 statements.
2020-06-09 15:48:11 +02:00
Oliver Smith 9da12415b8
pmbootstrap chroot --install-blockdev: new option
Create /dev/install inside the chroot from a block device, just like
done during the installation. This is useful for testing the Calamares
installer.
2020-06-09 15:48:11 +02:00
Oliver Smith ac7d66fade
blockdevice.create_and_mount_image: add split arg
Add a "split" argument to the function, instead of using "args.split"
directly. "args.split" is only defined when calling "pmbootstrap install",
but the next patch will add a code path that calls the function from
"pmbootstrap chroot".
2020-06-09 15:48:06 +02:00
9 changed files with 265 additions and 109 deletions

View File

@ -67,10 +67,11 @@ def shutdown(args, only_install_related=False):
path = path_outside[len(chroot):] path = path_outside[len(chroot):]
pmb.install.losetup.umount(args, path, auto_init=False) pmb.install.losetup.umount(args, path, auto_init=False)
# Umount device rootfs chroot # Umount device rootfs and installer chroots
chroot_rootfs = args.work + "/chroot_rootfs_" + args.device for prefix in ["rootfs", "installer"]:
if os.path.exists(chroot_rootfs): path = f"{args.work}/chroot_{prefix}_{args.device}"
pmb.helpers.mount.umount_all(args, chroot_rootfs) if os.path.exists(path):
pmb.helpers.mount.umount_all(args, path)
if not only_install_related: if not only_install_related:
# Umount all folders inside args.work # Umount all folders inside args.work

View File

@ -53,6 +53,7 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,
patterns = [ patterns = [
"chroot_native", "chroot_native",
"chroot_buildroot_*", "chroot_buildroot_*",
"chroot_installer_*",
"chroot_rootfs_*", "chroot_rootfs_*",
] ]
if pkgs_local: if pkgs_local:

View File

@ -25,6 +25,7 @@ import pmb.helpers.run
import pmb.helpers.aportupgrade import pmb.helpers.aportupgrade
import pmb.helpers.status import pmb.helpers.status
import pmb.install import pmb.install
import pmb.install.blockdevice
import pmb.parse import pmb.parse
import pmb.qemu import pmb.qemu
@ -132,6 +133,14 @@ def chroot(args):
env["DISPLAY"] = os.environ.get("DISPLAY") env["DISPLAY"] = os.environ.get("DISPLAY")
env["XAUTHORITY"] = "/home/pmos/.Xauthority" env["XAUTHORITY"] = "/home/pmos/.Xauthority"
# Install blockdevice
if args.install_blockdev:
size_boot = 128 # 128 MB
size_root = 4096 # 4 GB
size_reserve = 2048 # 2 GB
pmb.install.blockdevice.create_and_mount_image(args, size_boot,
size_root, size_reserve)
# Run the command as user/root # Run the command as user/root
if args.user: if args.user:
logging.info("(" + suffix + ") % su pmos -c '" + logging.info("(" + suffix + ") % su pmos -c '" +
@ -195,6 +204,24 @@ def install(args):
if args.rsync and not args.sdcard: if args.rsync and not args.sdcard:
raise ValueError("Installation using rsync only works on 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: if not args.sdcard and args.split is None:
# Default to split if the flash method requires it # Default to split if the flash method requires it
flasher = pmb.config.flashers.get(args.deviceinfo["flash_method"], {}) flasher = pmb.config.flashers.get(args.deviceinfo["flash_method"], {})

View File

@ -11,6 +11,7 @@ import pmb.chroot.apk
import pmb.chroot.other import pmb.chroot.other
import pmb.chroot.initfs import pmb.chroot.initfs
import pmb.config import pmb.config
import pmb.config.pmaports
import pmb.helpers.devices import pmb.helpers.devices
import pmb.helpers.run import pmb.helpers.run
import pmb.install.blockdevice import pmb.install.blockdevice
@ -19,32 +20,38 @@ import pmb.install.recovery
import pmb.install import pmb.install
def mount_device_rootfs(args, suffix="native"): def mount_device_rootfs(args, suffix_rootfs, suffix_mount="native"):
""" """
Mount the device rootfs. Mount the device rootfs.
:param suffix_rootfs: the chroot suffix, where the rootfs that will be
installed on the device has been created (e.g.
"rootfs_qemu-amd64")
:param suffix_mount: the chroot suffix, where the device rootfs will be
mounted (e.g. "native")
""" """
mountpoint = "/mnt/rootfs_" + args.device mountpoint = f"/mnt/{suffix_rootfs}"
pmb.helpers.mount.bind(args, args.work + "/chroot_rootfs_" + args.device, pmb.helpers.mount.bind(args, f"{args.work}/chroot_{suffix_rootfs}",
args.work + "/chroot_" + suffix + mountpoint) f"{args.work}/chroot_{suffix_mount}{mountpoint}")
return mountpoint return mountpoint
def get_subpartitions_size(args): def get_subpartitions_size(args, suffix):
""" """
Calculate the size of the boot and root subpartition. Calculate the size of the boot and root subpartition.
:param suffix: the chroot suffix, e.g. "rootfs_qemu-amd64"
:returns: (boot, root) the size of the boot and root :returns: (boot, root) the size of the boot and root
partition as integer in bytes partition as integer in MB
""" """
boot = int(args.boot_size) * 1024 * 1024 boot = int(args.boot_size)
# Estimate root partition size, then add some free space. The size # Estimate root partition size, then add some free space. The size
# calculation is not as trivial as one may think, and depending on the # calculation is not as trivial as one may think, and depending on the
# file system etc it seems to be just impossible to get it right. # file system etc it seems to be just impossible to get it right.
chroot = args.work + "/chroot_rootfs_" + args.device chroot = f"{args.work}/chroot_{suffix}"
root = pmb.helpers.other.folder_size(args, chroot) root = pmb.helpers.other.folder_size(args, chroot) / 1024 / 1024
root *= 1.20 root *= 1.20
root += 50 * 1024 * 1024 root += 50
return (boot, root) return (boot, root)
@ -94,16 +101,17 @@ def get_kernel_package(args, device):
return ["device-" + device + "-kernel-" + args.kernel] return ["device-" + device + "-kernel-" + args.kernel]
def copy_files_from_chroot(args): def copy_files_from_chroot(args, suffix):
""" """
Copy all files from the rootfs chroot to /mnt/install, except Copy all files from the rootfs chroot to /mnt/install, except
for the home folder (because /home will contain some empty for the home folder (because /home will contain some empty
mountpoint folders). mountpoint folders).
:param suffix: the chroot suffix, e.g. "rootfs_qemu-amd64"
""" """
# Mount the device rootfs # Mount the device rootfs
logging.info("(native) copy rootfs_" + args.device + " to" + logging.info(f"(native) copy {suffix} to /mnt/install/")
" /mnt/install/") mountpoint = mount_device_rootfs(args, suffix)
mountpoint = mount_device_rootfs(args)
mountpoint_outside = args.work + "/chroot_native" + mountpoint mountpoint_outside = args.work + "/chroot_native" + mountpoint
# Remove empty qemu-user binary stub (where the binary was bind-mounted) # Remove empty qemu-user binary stub (where the binary was bind-mounted)
@ -303,7 +311,7 @@ def embed_firmware(args):
"deviceinfo_sd_embed_firmware_step_size " "deviceinfo_sd_embed_firmware_step_size "
"is not valid: {}".format(step)) "is not valid: {}".format(step))
device_rootfs = mount_device_rootfs(args) device_rootfs = mount_device_rootfs(args, f"rootfs_{args.device}")
binaries = args.deviceinfo["sd_embed_firmware"].split(",") binaries = args.deviceinfo["sd_embed_firmware"].split(",")
# Perform three checks prior to writing binaries to disk: 1) that binaries # Perform three checks prior to writing binaries to disk: 1) that binaries
@ -366,31 +374,36 @@ def sanity_check_sdcard(device):
raise RuntimeError("{} is read-only, is the sdcard locked?".format(device)) raise RuntimeError("{} is read-only, is the sdcard locked?".format(device))
def install_system_image(args): def install_system_image(args, size_reserve, suffix, root_label="pmOS_root",
step=3, steps=5, split=False, sdcard=None):
"""
:param size_reserve: empty partition between root and boot in MB (pma#463)
:param suffix: the chroot suffix, where the rootfs that will be installed
on the device has been created (e.g. "rootfs_qemu-amd64")
:param root_label: label of the root partition (e.g. "pmOS_root")
:param step: next installation step
:param steps: total installation steps
:param split: create separate images for boot and root partitions
:param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
"""
# Partition and fill image/sdcard # Partition and fill image/sdcard
logging.info("*** (3/5) PREPARE INSTALL BLOCKDEVICE ***") logging.info(f"*** ({step}/{steps}) PREPARE INSTALL BLOCKDEVICE ***")
pmb.chroot.shutdown(args, True) pmb.chroot.shutdown(args, True)
(size_boot, size_root) = get_subpartitions_size(args) (size_boot, size_root) = get_subpartitions_size(args, suffix)
if not args.rsync: if not args.rsync:
pmb.install.blockdevice.create(args, size_boot, size_root) pmb.install.blockdevice.create(args, size_boot, size_root,
if not args.split: size_reserve, split, sdcard)
pmb.install.partition(args, size_boot) if not split:
if not args.split: pmb.install.partition(args, size_boot, size_reserve)
pmb.install.partitions_mount(args) if not split:
root_id = 3 if size_reserve else 2
pmb.install.partitions_mount(args, root_id, sdcard)
if args.full_disk_encryption: pmb.install.format(args, size_reserve, root_label, sdcard)
logging.info("WARNING: Full disk encryption is enabled!")
logging.info("Make sure that osk-sdl has been properly configured for your device")
logging.info("or else you will be unable to unlock the rootfs on boot!")
logging.info("If you started a device port, it is recommended you disable")
logging.info("FDE by re-running the install command without '--fde' until")
logging.info("you have properly configured osk-sdl. More information:")
logging.info("<https://postmarketos.org/osk-port>")
pmb.install.format(args)
# Just copy all the files # Just copy all the files
logging.info("*** (4/5) FILL INSTALL BLOCKDEVICE ***") logging.info(f"*** ({step + 1}/{steps}) FILL INSTALL BLOCKDEVICE ***")
copy_files_from_chroot(args) copy_files_from_chroot(args, suffix)
create_home_from_skel(args) create_home_from_skel(args)
configure_apk(args) configure_apk(args)
copy_ssh_keys(args) copy_ssh_keys(args)
@ -402,7 +415,7 @@ def install_system_image(args):
if sparse is None: if sparse is None:
sparse = args.deviceinfo["flash_sparse"] == "true" sparse = args.deviceinfo["flash_sparse"] == "true"
if sparse and not args.split and not args.sdcard: if sparse and not split and not sdcard:
logging.info("(native) make sparse rootfs") logging.info("(native) make sparse rootfs")
pmb.chroot.apk.install(args, ["android-tools"]) pmb.chroot.apk.install(args, ["android-tools"])
sys_image = args.device + ".img" sys_image = args.device + ".img"
@ -412,8 +425,13 @@ def install_system_image(args):
pmb.chroot.user(args, ["mv", "-f", sys_image_sparse, sys_image], pmb.chroot.user(args, ["mv", "-f", sys_image_sparse, sys_image],
working_dir="/home/pmos/rootfs/") working_dir="/home/pmos/rootfs/")
# Kernel flash information
logging.info("*** (5/5) FLASHING TO DEVICE ***") def print_flash_info(args, step=5):
""" Print flashing information, based on the deviceinfo data and the
pmbootstrap arguments.
:param step: installation step number """
logging.info(f"*** ({step}/{step}) FLASHING TO DEVICE ***")
logging.info("Run the following to flash your installation to the" logging.info("Run the following to flash your installation to the"
" target device:") " target device:")
@ -428,16 +446,18 @@ def install_system_image(args):
logging.info("* pmbootstrap flasher flash_rootfs") logging.info("* pmbootstrap flasher flash_rootfs")
logging.info(" Flashes the generated rootfs image to your device:") logging.info(" Flashes the generated rootfs image to your device:")
if args.split: if args.split:
logging.info(" " + args.work + "/chroot_native/home/pmos/rootfs/" + logging.info(f" {args.work}/chroot_native/home/pmos/rootfs/"
args.device + "-rootfs.img") f"{args.device}-rootfs.img")
else: else:
logging.info(" " + args.work + "/chroot_native/home/pmos/rootfs/" + logging.info(f" {args.work}/chroot_native/home/pmos/rootfs/"
args.device + ".img") f"{args.device}.img")
logging.info(" (NOTE: This file has a partition table, which contains" logging.info(" (NOTE: This file has a partition table, which"
" /boot and / subpartitions. That way we don't need to" " contains /boot and / subpartitions. That way we"
" change the partition layout on your device.)") " don't need to change the partition layout on your"
" device.)")
# if current flasher supports vbmeta and partition is explicitly spcified in deviceinfo # if current flasher supports vbmeta and partition is explicitly specified
# in deviceinfo
if "flash_vbmeta" in flasher_actions and \ if "flash_vbmeta" in flasher_actions and \
(args.deviceinfo["flash_fastboot_partition_vbmeta"] or (args.deviceinfo["flash_fastboot_partition_vbmeta"] or
args.deviceinfo["flash_heimdall_partition_vbmeta"]): args.deviceinfo["flash_heimdall_partition_vbmeta"]):
@ -448,14 +468,15 @@ def install_system_image(args):
# (e.g. an Android boot image is generated). In that case, "flash_kernel" # (e.g. an Android boot image is generated). In that case, "flash_kernel"
# works even when partitions are split or installing for sdcard. # works even when partitions are split or installing for sdcard.
# This is not possible if the flash method requires split partitions. # This is not possible if the flash method requires split partitions.
if "flash_kernel" in flasher_actions and (not requires_split or args.split): if "flash_kernel" in flasher_actions and \
(not requires_split or args.split):
logging.info("* pmbootstrap flasher flash_kernel") logging.info("* pmbootstrap flasher flash_kernel")
logging.info(" Flashes the kernel + initramfs to your device:") logging.info(" Flashes the kernel + initramfs to your device:")
if requires_split: if requires_split:
logging.info(" " + args.work + "/chroot_native/home/pmos/rootfs/" + logging.info(f" {args.work}/chroot_native/home/pmos/rootfs/"
args.device + "-boot.img") f"{args.device}-boot.img")
else: else:
logging.info(" " + args.work + "/chroot_rootfs_" + args.device + "/boot") logging.info(f" {args.work}/chroot_rootfs_{args.device}/boot")
if "boot" in flasher_actions: if "boot" in flasher_actions:
logging.info(" (NOTE: " + method + " also supports booting" logging.info(" (NOTE: " + method + " also supports booting"
@ -471,7 +492,7 @@ def install_system_image(args):
def install_recovery_zip(args): def install_recovery_zip(args):
logging.info("*** (3/4) CREATING RECOVERY-FLASHABLE ZIP ***") logging.info("*** (3/4) CREATING RECOVERY-FLASHABLE ZIP ***")
suffix = "buildroot_" + args.deviceinfo["arch"] suffix = "buildroot_" + args.deviceinfo["arch"]
mount_device_rootfs(args, suffix) mount_device_rootfs(args, f"rootfs_{args.device}", suffix)
pmb.install.recovery.create_zip(args, suffix) pmb.install.recovery.create_zip(args, suffix)
# Flash information # Flash information
@ -480,6 +501,56 @@ def install_recovery_zip(args):
logging.info("<https://postmarketos.org/recoveryzip>") logging.info("<https://postmarketos.org/recoveryzip>")
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): def install(args):
# Sanity checks # Sanity checks
if not args.android_recovery_zip and args.sdcard: if not args.android_recovery_zip and args.sdcard:
@ -490,6 +561,8 @@ def install(args):
steps = 2 steps = 2
elif args.android_recovery_zip: elif args.android_recovery_zip:
steps = 4 steps = 4
elif args.on_device_installer:
steps = 8
else: else:
steps = 5 steps = 5
@ -547,7 +620,15 @@ def install(args):
# Set the hostname as the device name # Set the hostname as the device name
setup_hostname(args) setup_hostname(args)
if args.android_recovery_zip: if args.no_image:
install_recovery_zip(args) return
elif not args.no_image: elif args.android_recovery_zip:
install_system_image(args) return install_recovery_zip(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)

View File

@ -9,14 +9,15 @@ import pmb.helpers.cli
import pmb.config import pmb.config
def previous_install(args): def previous_install(args, path):
""" """
Search the sdcard for possible existence of a previous installation of pmOS. Search the sdcard for possible existence of a previous installation of pmOS.
We temporarily mount the possible pmOS_boot partition as /dev/sdcardp1 inside We temporarily mount the possible pmOS_boot partition as /dev/sdcardp1 inside
the native chroot to check the label from there. the native chroot to check the label from there.
:param path: path to sdcard device (e.g. /dev/mmcblk0)
""" """
label = "" label = ""
for blockdevice_outside in [args.sdcard + "1", args.sdcard + "p1"]: for blockdevice_outside in [f"{path}1", f"{path}p1"]:
if not os.path.exists(blockdevice_outside): if not os.path.exists(blockdevice_outside):
continue continue
blockdevice_inside = "/dev/sdcardp1" blockdevice_inside = "/dev/sdcardp1"
@ -28,39 +29,45 @@ def previous_install(args):
return "pmOS_boot" in label return "pmOS_boot" in label
def mount_sdcard(args): def mount_sdcard(args, path):
"""
:param path: path to sdcard device (e.g. /dev/mmcblk0)
"""
# Sanity checks # Sanity checks
if args.deviceinfo["external_storage"] != "true": if args.deviceinfo["external_storage"] != "true":
raise RuntimeError("According to the deviceinfo, this device does" raise RuntimeError("According to the deviceinfo, this device does"
" not support a sdcard installation.") " not support a sdcard installation.")
if not os.path.exists(args.sdcard): if not os.path.exists(path):
raise RuntimeError("The sdcard device does not exist: " + raise RuntimeError(f"The sdcard device does not exist: {path}")
args.sdcard) for path_mount in glob.glob(f"{path}*"):
for path in glob.glob(args.sdcard + "*"): if pmb.helpers.mount.ismount(path_mount):
if pmb.helpers.mount.ismount(path): raise RuntimeError(f"{path_mount} is mounted! Will not attempt to"
raise RuntimeError(path + " is mounted! We will not attempt" " format this!")
" to format this!") logging.info(f"(native) mount /dev/install (host: {path})")
logging.info("(native) mount /dev/install (host: " + args.sdcard + ")") pmb.helpers.mount.bind_file(args, path,
pmb.helpers.mount.bind_file(args, args.sdcard,
args.work + "/chroot_native/dev/install") args.work + "/chroot_native/dev/install")
if previous_install(args): if previous_install(args, path):
if not pmb.helpers.cli.confirm(args, "WARNING: This device has a" if not pmb.helpers.cli.confirm(args, "WARNING: This device has a"
" previous installation of pmOS." " previous installation of pmOS."
" CONTINUE?"): " CONTINUE?"):
raise RuntimeError("Aborted.") raise RuntimeError("Aborted.")
else: else:
if not pmb.helpers.cli.confirm(args, "EVERYTHING ON " + args.sdcard + if not pmb.helpers.cli.confirm(args, f"EVERYTHING ON {path} WILL BE"
" WILL BE ERASED! CONTINUE?"): " ERASED! CONTINUE?"):
raise RuntimeError("Aborted.") raise RuntimeError("Aborted.")
def create_and_mount_image(args, size_boot, size_root): def create_and_mount_image(args, size_boot, size_root, size_reserve,
split=False):
""" """
Create a new image file, and mount it as /dev/install. Create a new image file, and mount it as /dev/install.
:param size_boot: size of the boot partition in bytes :param size_boot: size of the boot partition in MB
:param size_root: size of the root partition in bytes :param size_root: size of the root partition in MB
:param size_reserve: empty partition between root and boot in MB (pma#463)
:param split: create separate images for boot and root partitions
""" """
# Short variables for paths # Short variables for paths
chroot = args.work + "/chroot_native" chroot = args.work + "/chroot_native"
img_path_prefix = "/home/pmos/rootfs/" + args.device img_path_prefix = "/home/pmos/rootfs/" + args.device
@ -77,7 +84,7 @@ def create_and_mount_image(args, size_boot, size_root):
pmb.chroot.root(args, ["rm", img_path]) pmb.chroot.root(args, ["rm", img_path])
# Make sure there is enough free space # Make sure there is enough free space
size_mb = round((size_boot + size_root) / (1024**2)) size_mb = round(size_boot + size_reserve + size_root)
disk_data = os.statvfs(args.work) disk_data = os.statvfs(args.work)
free = round((disk_data.f_bsize * disk_data.f_bavail) / (1024**2)) free = round((disk_data.f_bsize * disk_data.f_bavail) / (1024**2))
if size_mb > free: if size_mb > free:
@ -86,10 +93,10 @@ def create_and_mount_image(args, size_boot, size_root):
# Create empty image files # Create empty image files
pmb.chroot.user(args, ["mkdir", "-p", "/home/pmos/rootfs"]) pmb.chroot.user(args, ["mkdir", "-p", "/home/pmos/rootfs"])
size_mb_full = str(size_mb) + "M" size_mb_full = str(size_mb) + "M"
size_mb_boot = str(round(size_boot / (1024**2))) + "M" size_mb_boot = str(round(size_boot)) + "M"
size_mb_root = str(round(size_root / (1024**2))) + "M" size_mb_root = str(round(size_root)) + "M"
images = {img_path_full: size_mb_full} images = {img_path_full: size_mb_full}
if args.split: if split:
images = {img_path_boot: size_mb_boot, images = {img_path_boot: size_mb_boot,
img_path_root: size_mb_root} img_path_root: size_mb_root}
for img_path, size_mb in images.items(): for img_path, size_mb in images.items():
@ -98,7 +105,7 @@ def create_and_mount_image(args, size_boot, size_root):
# Mount to /dev/install # Mount to /dev/install
mount_image_paths = {img_path_full: "/dev/install"} mount_image_paths = {img_path_full: "/dev/install"}
if args.split: if split:
mount_image_paths = {img_path_boot: "/dev/installp1", mount_image_paths = {img_path_boot: "/dev/installp1",
img_path_root: "/dev/installp2"} img_path_root: "/dev/installp2"}
@ -111,16 +118,20 @@ def create_and_mount_image(args, size_boot, size_root):
args.work + "/chroot_native" + mount_point) args.work + "/chroot_native" + mount_point)
def create(args, size_boot, size_root): def create(args, size_boot, size_root, size_reserve, split, sdcard):
""" """
Create /dev/install (the "install blockdevice"). Create /dev/install (the "install blockdevice").
:param size_boot: size of the boot partition in bytes :param size_boot: size of the boot partition in MB
:param size_root: size of the root partition in bytes :param size_root: size of the root partition in MB
:param size_reserve: empty partition between root and boot in MB (pma#463)
:param split: create separate images for boot and root partitions
:param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
""" """
pmb.helpers.mount.umount_all( pmb.helpers.mount.umount_all(
args, args.work + "/chroot_native/dev/install") args, args.work + "/chroot_native/dev/install")
if args.sdcard: if sdcard:
mount_sdcard(args) mount_sdcard(args, sdcard)
else: else:
create_and_mount_image(args, size_boot, size_root) create_and_mount_image(args, size_boot, size_root, size_reserve,
split)

View File

@ -23,9 +23,11 @@ def format_and_mount_boot(args):
pmb.chroot.root(args, ["mount", device, mountpoint]) pmb.chroot.root(args, ["mount", device, mountpoint])
def format_and_mount_root(args): def format_and_mount_root(args, device):
"""
:param device: root partition on install block device (e.g. /dev/installp2)
"""
mountpoint = "/dev/mapper/pm_crypt" mountpoint = "/dev/mapper/pm_crypt"
device = "/dev/installp2"
if args.full_disk_encryption: if args.full_disk_encryption:
logging.info("(native) format " + device + " (root, luks), mount to " + logging.info("(native) format " + device + " (root, luks), mount to " +
mountpoint) mountpoint)
@ -41,12 +43,15 @@ def format_and_mount_root(args):
raise RuntimeError("Failed to open cryptdevice!") raise RuntimeError("Failed to open cryptdevice!")
def format_and_mount_pm_crypt(args): def format_and_mount_pm_crypt(args, device, root_label, sdcard):
"""
:param device: root partition on install block device (e.g. /dev/installp2)
:param root_label: label of the root partition (e.g. "pmOS_root")
:param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
"""
# Block device # Block device
if args.full_disk_encryption: if args.full_disk_encryption:
device = "/dev/mapper/pm_crypt" device = "/dev/mapper/pm_crypt"
else:
device = "/dev/installp2"
# Format # Format
if not args.rsync: if not args.rsync:
@ -55,12 +60,12 @@ def format_and_mount_pm_crypt(args):
# When changing the options of mkfs.ext4, also change them in the # When changing the options of mkfs.ext4, also change them in the
# recovery zip code (see 'grep -r mkfs\.ext4')! # recovery zip code (see 'grep -r mkfs\.ext4')!
mkfs_ext4_args = ["mkfs.ext4", "-O", "^metadata_csum", "-F", mkfs_ext4_args = ["mkfs.ext4", "-O", "^metadata_csum", "-F",
"-q", "-L", "pmOS_root"] "-q", "-L", root_label]
# When we don't know the file system size before hand like # When we don't know the file system size before hand like
# with non-block devices, we need to explicitely set a number of # with non-block devices, we need to explicitely set a number of
# inodes. See #1717 and #1845 for details # inodes. See #1717 and #1845 for details
if not args.sdcard: if not sdcard:
mkfs_ext4_args = mkfs_ext4_args + ["-N", "100000"] mkfs_ext4_args = mkfs_ext4_args + ["-N", "100000"]
pmb.chroot.root(args, mkfs_ext4_args + [device]) pmb.chroot.root(args, mkfs_ext4_args + [device])
@ -72,7 +77,13 @@ def format_and_mount_pm_crypt(args):
pmb.chroot.root(args, ["mount", device, mountpoint]) pmb.chroot.root(args, ["mount", device, mountpoint])
def format(args): def format(args, size_reserve, root_label, sdcard):
format_and_mount_root(args) """
format_and_mount_pm_crypt(args) :param size_reserve: empty partition between root and boot in MB (pma#463)
:param root_label: label of the root partition (e.g. "pmOS_root")
:param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
"""
root_dev = "/dev/installp3" if size_reserve else "/dev/installp2"
format_and_mount_root(args, root_dev)
format_and_mount_pm_crypt(args, root_dev, root_label, sdcard)
format_and_mount_boot(args) format_and_mount_boot(args)

View File

@ -8,12 +8,14 @@ import pmb.config
import pmb.install.losetup import pmb.install.losetup
def partitions_mount(args): def partitions_mount(args, root_id, sdcard):
""" """
Mount blockdevices of partitions inside native chroot Mount blockdevices of partitions inside native chroot
:param root_id: root partition id (3 with --reserve-space, otherwise 2)
:param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
""" """
prefix = args.sdcard prefix = sdcard
if not args.sdcard: if not sdcard:
img_path = "/home/pmos/rootfs/" + args.device + ".img" img_path = "/home/pmos/rootfs/" + args.device + ".img"
prefix = pmb.install.losetup.device_by_back_file(args, img_path) prefix = pmb.install.losetup.device_by_back_file(args, img_path)
@ -35,22 +37,28 @@ def partitions_mount(args):
prefix + " to be located at " + prefix + prefix + " to be located at " + prefix +
"1 or " + prefix + "p1!") "1 or " + prefix + "p1!")
for i in [1, 2]: for i in [1, root_id]:
source = prefix + partition_prefix + str(i) source = prefix + partition_prefix + str(i)
target = args.work + "/chroot_native/dev/installp" + str(i) target = args.work + "/chroot_native/dev/installp" + str(i)
pmb.helpers.mount.bind_file(args, source, target) pmb.helpers.mount.bind_file(args, source, target)
def partition(args, size_boot): def partition(args, size_boot, size_reserve):
""" """
Partition /dev/install and create /dev/install{p1,p2} Partition /dev/install and create /dev/install{p1,p2,p3}:
* /dev/installp1: boot
* /dev/installp2: root (or reserved space)
* /dev/installp3: (root, if reserved space > 0)
size_boot: size of the boot partition in bytes. :param size_boot: size of the boot partition in MB
:param size_reserve: empty partition between root and boot in MB (pma#463)
""" """
# Convert to MB and print info # Convert to MB and print info
mb_boot = str(round(size_boot / 1024 / 1024)) + "M" mb_boot = f"{round(size_boot)}M"
logging.info("(native) partition /dev/install (boot: " + mb_boot + mb_reserved = f"{round(size_reserve)}M"
", root: the rest)") mb_root_start = f"{round(size_boot) + round(size_reserve)}M"
logging.info(f"(native) partition /dev/install (boot: {mb_boot},"
f" reserved: {mb_reserved}, root: the rest)")
filesystem = args.deviceinfo["boot_filesystem"] or "ext2" filesystem = args.deviceinfo["boot_filesystem"] or "ext2"
@ -63,9 +71,16 @@ def partition(args, size_boot):
commands = [ commands = [
["mktable", "msdos"], ["mktable", "msdos"],
["mkpart", "primary", filesystem, boot_part_start + 's', mb_boot], ["mkpart", "primary", filesystem, boot_part_start + 's', mb_boot],
["mkpart", "primary", mb_boot, "100%"], ]
if size_reserve:
commands += [["mkpart", "primary", mb_boot, mb_reserved]]
commands += [
["mkpart", "primary", mb_root_start, "100%"],
["set", "1", "boot", "on"] ["set", "1", "boot", "on"]
] ]
for command in commands: for command in commands:
pmb.chroot.root(args, ["parted", "-s", "/dev/install"] + pmb.chroot.root(args, ["parted", "-s", "/dev/install"] +
command, check=False) command, check=False)

View File

@ -25,7 +25,7 @@ def alpine_native():
def from_chroot_suffix(args, suffix): def from_chroot_suffix(args, suffix):
if suffix == "native": if suffix == "native":
return args.arch_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"] return args.deviceinfo["arch"]
if suffix.startswith("buildroot_"): if suffix.startswith("buildroot_"):
return suffix.split("_", 1)[1] return suffix.split("_", 1)[1]

View File

@ -497,6 +497,10 @@ def arguments():
help="Copy .Xauthority and set environment variables," help="Copy .Xauthority and set environment variables,"
" so X11 applications can be started (native" " so X11 applications can be started (native"
" chroot only)") " chroot only)")
chroot.add_argument("-i", "--install-blockdev", action="store_true",
help="Create a sparse image file and mount it as"
" /dev/install, just like during the"
" installation process.")
for action in [build_init, chroot]: for action in [build_init, chroot]:
suffix = action.add_mutually_exclusive_group() suffix = action.add_mutually_exclusive_group()
if action == chroot: if action == chroot:
@ -553,6 +557,11 @@ def arguments():
install.add_argument("--no-base", install.add_argument("--no-base",
help="do not install postmarketos-base (advanced)", help="do not install postmarketos-base (advanced)",
action="store_false", dest="install_base") 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 = install.add_mutually_exclusive_group()
group.add_argument("--sparse", help="generate sparse image file" group.add_argument("--sparse", help="generate sparse image file"
" (even if unsupported by device)", default=None, " (even if unsupported by device)", default=None,