diff --git a/README.rst b/README.rst index 5c068b7..f2935fb 100644 --- a/README.rst +++ b/README.rst @@ -23,13 +23,15 @@ Get me started! installed. a. Alternatively, run ``./rootfs.py --chroot`` to run it in a chroot. - b. Alternatively, run ``./rootfs.py`` but don’t run the actual + b. Alternatively, run ``./rootfs.py --bwrap`` to run it in a bubblewrap + sandbox. When user namespaces are supported, this mode is rootless. + c. Alternatively, run ``./rootfs.py`` but don’t run the actual virtualization and instead copy sysa/tmp/initramfs to a USB or some other device and boot from bare metal. NOTE: we now require a hard drive. This is currently hardcoded as sda. You also need to put ``sysc/tmp/disk.img`` onto your sda on the bootstrapping machine. - c. Alternatively, do not use python at all, see "Python-less build" + d. Alternatively, do not use python at all, see "Python-less build" below. 5. Wait. diff --git a/lib/sysgeneral.py b/lib/sysgeneral.py index ebc51e0..467b3e5 100644 --- a/lib/sysgeneral.py +++ b/lib/sysgeneral.py @@ -3,11 +3,13 @@ This file contains a few functions to be shared by all Sys* classes """ +# SPDX-FileCopyrightText: 2022 Dor Askayo # SPDX-FileCopyrightText: 2021-22 fosslinux # SPDX-FileCopyrightText: 2021 Andrius Štikonas # SPDX-License-Identifier: GPL-3.0-or-later import os +import shutil import hashlib import glob import subprocess @@ -33,10 +35,20 @@ class SysGeneral: mounted_tmpfs = False def __del__(self): - if self.mounted_tmpfs and not self.preserve_tmp: + if not self.preserve_tmp: + self.remove_tmp() + + def remove_tmp(self): + """Remove the tmp directory""" + if self.tmp_dir is None: + return + + if self.mounted_tmpfs: print(f"Unmounting tmpfs from {self.tmp_dir}") umount(self.tmp_dir) - os.rmdir(self.tmp_dir) + + print(f"Removing {self.tmp_dir}") + shutil.rmtree(self.tmp_dir, ignore_errors=True) def mount_tmpfs(self): """Mount the tmpfs for this sysx""" diff --git a/rootfs.py b/rootfs.py index 8a5c98c..a1d4907 100755 --- a/rootfs.py +++ b/rootfs.py @@ -7,6 +7,7 @@ you can run bootstap inside chroot. """ # SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2022 Dor Askayo # SPDX-FileCopyrightText: 2021 Andrius Štikonas # SPDX-FileCopyrightText: 2021 Bastian Bittorf # SPDX-FileCopyrightText: 2021 Melg Eight @@ -30,7 +31,7 @@ def create_configuration_file(args): config_path = os.path.join('sysa', 'bootstrap.cfg') with open(config_path, "w", encoding="utf_8") as config: config.write("FORCE_TIMESTAMPS=" + str(args.force_timestamps) + "\n") - config.write("CHROOT=" + str(args.chroot) + "\n") + config.write("CHROOT=" + str(args.chroot or args.bwrap) + "\n") config.write("UPDATE_CHECKSUMS=" + str(args.update_checksums) + "\n") config.write("DISK=sda1\n") @@ -45,7 +46,9 @@ def main(): default="x86") parser.add_argument("-c", "--chroot", help="Run inside chroot", action="store_true") - parser.add_argument("-p", "--preserve", help="Do not unmount temporary dir", + parser.add_argument("-bw", "--bwrap", help="Run inside a bwrap sandbox", + action="store_true") + parser.add_argument("-p", "--preserve", help="Do not remove temporary dir", action="store_true") parser.add_argument("-t", "--tmpdir", help="Temporary directory") parser.add_argument("--force-timestamps", @@ -81,6 +84,8 @@ def main(): count += 1 if args.chroot: count += 1 + if args.bwrap: + count += 1 if args.minikernel: count += 1 if args.bare_metal: @@ -109,11 +114,10 @@ def main(): pass system_c = SysC(arch=args.arch, preserve_tmp=args.preserve, - tmpdir=args.tmpdir, chroot=args.chroot) - system_b = SysB(arch=args.arch, preserve_tmp=args.preserve, - chroot=args.chroot) + tmpdir=args.tmpdir) + system_b = SysB(arch=args.arch, preserve_tmp=args.preserve) system_a = SysA(arch=args.arch, preserve_tmp=args.preserve, - tmpdir=args.tmpdir, chroot=args.chroot, + tmpdir=args.tmpdir, sysb_dir=system_b.sys_dir, sysc_tmp=system_c.tmp_dir) bootstrap(args, system_a, system_b, system_c) @@ -129,15 +133,59 @@ print(shutil.which('chroot')) chroot_binary = run('sudo', 'python3', '-c', find_chroot, capture_output=True).stdout.decode().strip() + system_c.prepare(mount_tmpfs=True, + create_disk_image=False) + system_a.prepare(mount_tmpfs=True, + copy_sysc=True, + create_initramfs=False) + # sysa arch = stage0_arch_map.get(args.arch, args.arch) init = os.path.join(os.sep, 'bootstrap-seeds', 'POSIX', arch, 'kaem-optional-seed') run('sudo', 'env', '-i', 'PATH=/bin', chroot_binary, system_a.tmp_dir, init) + elif args.bwrap: + system_c.prepare(mount_tmpfs=False, + create_disk_image=False) + system_a.prepare(mount_tmpfs=False, + copy_sysc=True, + create_initramfs=False) + + # sysa + arch = stage0_arch_map.get(args.arch, args.arch) + init = os.path.join(os.sep, 'bootstrap-seeds', 'POSIX', arch, 'kaem-optional-seed') + run('bwrap', '--unshare-user', + '--uid', '0', + '--gid', '0', + '--cap-add', 'CAP_SYS_CHROOT', # Required for chroot from sysa to sysc + '--clearenv', + '--setenv', 'PATH', '/usr/bin', + '--bind', system_a.tmp_dir, '/', + '--dir', '/dev', + '--dev-bind', '/dev/null', '/dev/null', + '--dev-bind', '/dev/zero', '/dev/zero', + '--dev-bind', '/dev/random', '/dev/random', + '--dev-bind', '/dev/urandom', '/dev/urandom', + '--dir', '/sysc/dev', + '--dev-bind', '/dev/null', '/sysc/dev/null', + '--dev-bind', '/dev/zero', '/sysc/dev/zero', + '--dev-bind', '/dev/random', '/sysc/dev/random', + '--dev-bind', '/dev/urandom', '/sysc/dev/urandom', + '--proc', '/sysc/proc', + '--bind', '/sys', '/sysc/sys', + '--tmpfs', '/sysc/tmp', + init) + elif args.minikernel: if os.path.isdir('kritis-linux'): shutil.rmtree('kritis-linux') + system_c.prepare(mount_tmpfs=True, + create_disk_image=True) + system_a.prepare(mount_tmpfs=True, + copy_sysc=False, + create_initramfs=True) + run('git', 'clone', '--depth', '1', '--branch', 'v0.7', 'https://github.com/bittorf/kritis-linux.git') @@ -155,11 +203,23 @@ print(shutil.which('chroot')) '--log', '/tmp/bootstrap.log') elif args.bare_metal: + system_c.prepare(mount_tmpfs=True, + create_disk_image=True) + system_a.prepare(mount_tmpfs=True, + copy_sysc=False, + create_initramfs=True) + print("Please:") print(" 1. Take sysa/tmp/initramfs and your kernel, boot using this.") print(" 2. Take sysc/tmp/disk.img and put this on a writable storage medium.") else: + system_c.prepare(mount_tmpfs=True, + create_disk_image=True) + system_a.prepare(mount_tmpfs=True, + copy_sysc=False, + create_initramfs=True) + run(args.qemu_cmd, '-enable-kvm', '-m', str(args.qemu_ram) + 'M', diff --git a/sysa.py b/sysa.py index 3db3744..db8954d 100755 --- a/sysa.py +++ b/sysa.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """System A""" # SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2022 Dor Askayo # SPDX-FileCopyrightText: 2021 Andrius Štikonas # SPDX-FileCopyrightText: 2021 Melg Eight # SPDX-FileCopyrightText: 2021-22 fosslinux @@ -17,7 +18,7 @@ class SysA(SysGeneral): Class responsible for preparing sources for System A. """ # pylint: disable=too-many-instance-attributes,too-many-arguments - def __init__(self, arch, preserve_tmp, tmpdir, chroot, sysb_dir, sysc_tmp): + def __init__(self, arch, preserve_tmp, tmpdir, sysb_dir, sysc_tmp): self.git_dir = os.path.dirname(os.path.join(__file__)) self.arch = arch self.preserve_tmp = preserve_tmp @@ -27,26 +28,22 @@ class SysA(SysGeneral): self.tmp_dir = os.path.join(self.git_dir, 'tmp') else: self.tmp_dir = os.path.join(tmpdir, 'sysa') - os.mkdir(self.tmp_dir) self.sysa_dir = os.path.join(self.tmp_dir, 'sysa') self.base_dir = self.sysa_dir self.cache_dir = os.path.join(self.sys_dir, 'distfiles') self.sysb_dir = sysb_dir self.sysc_tmp = sysc_tmp - self.chroot = chroot - self.prepare() - - if not chroot: - self.make_initramfs() - - def prepare(self): + def prepare(self, mount_tmpfs, copy_sysc, create_initramfs): """ Prepare directory structure for System A. - We create an empty tmpfs, unpack stage0-posix. + We create an empty tmp directory, unpack stage0-posix. Rest of the files are unpacked into more structured directory /sysa """ - self.mount_tmpfs() + if mount_tmpfs: + self.mount_tmpfs() + else: + os.mkdir(self.tmp_dir) self.stage0_posix() self.sysa() @@ -54,9 +51,12 @@ class SysA(SysGeneral): # sysb must be added to sysa as it is another initramfs stage self.sysb() - if self.chroot: + if copy_sysc: self.sysc() + if create_initramfs: + self.make_initramfs() + def sysa(self): """Copy in sysa files for sysa.""" self.get_packages() diff --git a/sysb.py b/sysb.py index e6a53bf..3f02f48 100755 --- a/sysb.py +++ b/sysb.py @@ -12,10 +12,9 @@ class SysB(SysGeneral): """ Class responsible for preparing sources for System B. """ - def __init__(self, arch, preserve_tmp, chroot): + def __init__(self, arch, preserve_tmp): self.git_dir = os.path.dirname(os.path.join(__file__)) self.arch = arch self.preserve_tmp = preserve_tmp - self.chroot = chroot self.sys_dir = os.path.join(self.git_dir, 'sysb') diff --git a/sysc.py b/sysc.py index 19f2400..a915c53 100755 --- a/sysc.py +++ b/sysc.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """System C""" # SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-FileCopyrightText: 2022 Dor Askayo # SPDX-FileCopyrightText: 2021-22 fosslinux # SPDX-FileCopyrightText: 2021 Andrius Štikonas @@ -16,12 +17,14 @@ class SysC(SysGeneral): """ Class responsible for preparing sources for System C. """ + + dev_name = None + # pylint: disable=too-many-instance-attributes - def __init__(self, arch, preserve_tmp, tmpdir, chroot): + def __init__(self, arch, preserve_tmp, tmpdir): self.git_dir = os.path.dirname(os.path.join(__file__)) self.arch = arch self.preserve_tmp = preserve_tmp - self.chroot = chroot self.sys_dir = os.path.join(self.git_dir, 'sysc') self.cache_dir = os.path.join(self.sys_dir, 'distfiles') @@ -29,44 +32,46 @@ class SysC(SysGeneral): self.tmp_dir = os.path.join(self.sys_dir, 'tmp') else: self.tmp_dir = os.path.join(tmpdir, 'sysc') - os.mkdir(self.tmp_dir) - - self.prepare() def __del__(self): if not self.preserve_tmp: - if not self.chroot: - print(f"Deleting {self.dev_name}") + if self.dev_name is not None: + print(f"Detaching {self.dev_name}") run('sudo', 'losetup', '-d', self.dev_name) - print(f"Unmounting tmpfs from {self.tmp_dir}") - umount(self.tmp_dir) - os.rmdir(self.tmp_dir) - def prepare(self): + super().__del__() + + def prepare(self, mount_tmpfs, create_disk_image): """ Prepare directory structure for System C. """ - self.mount_tmpfs() - if not self.chroot: + if mount_tmpfs: + self.mount_tmpfs() + else: + os.mkdir(self.tmp_dir) + + rootfs_dir = None + + if create_disk_image: # Create + mount a disk for QEMU to use disk_path = os.path.join(self.tmp_dir, 'disk.img') self.dev_name = create_disk(disk_path, "msdos", "ext4", '8G') - self.rootfs_dir = os.path.join(self.tmp_dir, 'mnt') - os.mkdir(self.rootfs_dir) - mount(self.dev_name + "p1", self.rootfs_dir, 'ext4') + rootfs_dir = os.path.join(self.tmp_dir, 'mnt') + os.mkdir(rootfs_dir) + mount(self.dev_name + "p1", rootfs_dir, 'ext4') # Use chown to allow executing user to access it run('sudo', 'chown', getpass.getuser(), self.dev_name) - run('sudo', 'chown', getpass.getuser(), self.rootfs_dir) + run('sudo', 'chown', getpass.getuser(), rootfs_dir) else: - self.rootfs_dir = self.tmp_dir + rootfs_dir = self.tmp_dir self.get_packages() - copytree(self.sys_dir, self.rootfs_dir, ignore=shutil.ignore_patterns("tmp")) + copytree(self.sys_dir, rootfs_dir, ignore=shutil.ignore_patterns("tmp")) - # Unmount tmp/mnt if it exists - if not self.chroot: - umount(self.rootfs_dir) + # Unmount tmp/mnt if it was mounted + if create_disk_image: + umount(rootfs_dir) # pylint: disable=line-too-long,too-many-statements def get_packages(self):