forked from Mirror/pmbootstrap
141 lines
5.6 KiB
Python
141 lines
5.6 KiB
Python
# Copyright 2023 Oliver Smith
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
from pmb.helpers import logging
|
|
import os
|
|
from pathlib import Path
|
|
from pmb.types import PmbArgs
|
|
import pmb.helpers.mount
|
|
import pmb.install.losetup
|
|
import pmb.helpers.cli
|
|
import pmb.config
|
|
from pmb.core import Chroot
|
|
from pmb.core.context import get_context
|
|
|
|
|
|
def previous_install(path: Path) -> bool:
|
|
"""
|
|
Search the disk for possible existence of a previous installation of
|
|
pmOS. We temporarily mount the possible pmOS_boot partition as
|
|
/dev/diskp1 inside the native chroot to check the label from there.
|
|
:param path: path to disk block device (e.g. /dev/mmcblk0)
|
|
"""
|
|
label = ""
|
|
for blockdevice_outside in [path.with_stem(f"{path.name}1"), path.with_stem(f"{path.name}p1")]:
|
|
if not os.path.exists(blockdevice_outside):
|
|
continue
|
|
blockdevice_inside = "/dev/diskp1"
|
|
pmb.helpers.mount.bind_file(blockdevice_outside, Chroot.native() / blockdevice_inside)
|
|
try:
|
|
label = pmb.chroot.root(
|
|
["blkid", "-s", "LABEL", "-o", "value", blockdevice_inside], output_return=True
|
|
)
|
|
except RuntimeError:
|
|
logging.info(
|
|
"WARNING: Could not get block device label,"
|
|
" assume no previous installation on that partition"
|
|
)
|
|
|
|
pmb.helpers.run.root(["umount", Chroot.native() / blockdevice_inside])
|
|
return "pmOS_boot" in label
|
|
|
|
|
|
def mount_disk(path: Path) -> None:
|
|
"""
|
|
:param path: path to disk block device (e.g. /dev/mmcblk0)
|
|
"""
|
|
# Sanity checks
|
|
if not os.path.exists(path):
|
|
raise RuntimeError(f"The disk block device does not exist: {path}")
|
|
for path_mount in path.parent.glob(f"{path.name}*"):
|
|
if pmb.helpers.mount.ismount(path_mount):
|
|
raise RuntimeError(f"{path_mount} is mounted! Will not attempt to format this!")
|
|
logging.info(f"(native) mount /dev/install (host: {path})")
|
|
pmb.helpers.mount.bind_file(path, Chroot.native() / "dev/install")
|
|
if previous_install(path):
|
|
if not pmb.helpers.cli.confirm(
|
|
"WARNING: This device has a previous installation of pmOS. CONTINUE?"
|
|
):
|
|
raise RuntimeError("Aborted.")
|
|
else:
|
|
if not pmb.helpers.cli.confirm(f"EVERYTHING ON {path} WILL BE ERASED! CONTINUE?"):
|
|
raise RuntimeError("Aborted.")
|
|
|
|
|
|
def create_and_mount_image(
|
|
args: PmbArgs, size_boot: int, size_root: int, size_reserve: int, split: bool = False
|
|
) -> None:
|
|
"""
|
|
Create a new image file, and mount it as /dev/install.
|
|
|
|
:param size_boot: size of the boot partition in MiB
|
|
:param size_root: size of the root partition in MiB
|
|
:param size_reserve: empty partition between root and boot in MiB (pma#463)
|
|
:param split: create separate images for boot and root partitions
|
|
"""
|
|
|
|
# Short variables for paths
|
|
chroot = Chroot.native()
|
|
config = get_context().config
|
|
img_path_prefix = Path("/home/pmos/rootfs")
|
|
img_path_full = img_path_prefix / f"{config.device}.img"
|
|
img_path_boot = img_path_prefix / f"{config.device}-boot.img"
|
|
img_path_root = img_path_prefix / f"{config.device}-root.img"
|
|
|
|
# Umount and delete existing images
|
|
for img_path in [img_path_full, img_path_boot, img_path_root]:
|
|
outside = chroot / img_path
|
|
if os.path.exists(outside):
|
|
pmb.helpers.mount.umount_all(chroot / "mnt")
|
|
pmb.install.losetup.umount(img_path)
|
|
pmb.chroot.root(["rm", img_path])
|
|
|
|
# Make sure there is enough free space
|
|
size_mb = round(size_boot + size_reserve + size_root)
|
|
disk_data = os.statvfs(get_context().config.work)
|
|
free = round((disk_data.f_bsize * disk_data.f_bavail) / (1024**2))
|
|
if size_mb > free:
|
|
raise RuntimeError(
|
|
f"Not enough free space to create rootfs image! (free: {free}M, required: {size_mb}M)"
|
|
)
|
|
|
|
# Create empty image files
|
|
pmb.chroot.user(["mkdir", "-p", "/home/pmos/rootfs"])
|
|
size_mb_full = str(size_mb) + "M"
|
|
size_mb_boot = str(size_boot) + "M"
|
|
size_mb_root = str(size_root) + "M"
|
|
images = {img_path_full: size_mb_full}
|
|
if split:
|
|
images = {img_path_boot: size_mb_boot, img_path_root: size_mb_root}
|
|
for img_path, image_size_mb in images.items():
|
|
logging.info(f"(native) create {img_path.name} ({image_size_mb})")
|
|
pmb.chroot.root(["truncate", "-s", image_size_mb, img_path])
|
|
|
|
# Mount to /dev/install
|
|
mount_image_paths = {img_path_full: "/dev/install"}
|
|
if split:
|
|
mount_image_paths = {img_path_boot: "/dev/installp1", img_path_root: "/dev/installp2"}
|
|
|
|
for img_path, mount_point in mount_image_paths.items():
|
|
logging.info(f"(native) mount {mount_point} ({img_path.name})")
|
|
pmb.install.losetup.mount(img_path, args.sector_size)
|
|
device = pmb.install.losetup.device_by_back_file(img_path)
|
|
pmb.helpers.mount.bind_file(device, Chroot.native() / mount_point)
|
|
|
|
|
|
def create(
|
|
args: PmbArgs, size_boot: int, size_root: int, size_reserve: int, split: bool, disk: Path | None
|
|
) -> None:
|
|
"""
|
|
Create /dev/install (the "install blockdevice").
|
|
|
|
:param size_boot: size of the boot partition in MiB
|
|
:param size_root: size of the root partition in MiB
|
|
:param size_reserve: empty partition between root and boot in MiB (pma#463)
|
|
:param split: create separate images for boot and root partitions
|
|
:param disk: path to disk block device (e.g. /dev/mmcblk0) or None
|
|
"""
|
|
pmb.helpers.mount.umount_all(Chroot.native() / "dev/install")
|
|
if disk:
|
|
mount_disk(disk)
|
|
else:
|
|
create_and_mount_image(args, size_boot, size_root, size_reserve, split)
|