commit: 9bc2ca1726c71e29c0e07554cb615f813cff4492
parent b2814c9a9720824dd31d61d3295598da6305ecc4
Author: Andrius Štikonas <andrius@stikonas.eu>
Date: Fri, 27 May 2022 14:09:49 +0100
Merge pull request #175 from doraskayo/bwrap-bootstrap
Add a rootless bootstrap mode using bubblewrap
Diffstat:
6 files changed, 124 insertions(+), 46 deletions(-)
diff --git 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
@@ -3,11 +3,13 @@
This file contains a few functions to be shared by all Sys* classes
"""
+# SPDX-FileCopyrightText: 2022 Dor Askayo <dor.askayo@gmail.com>
# SPDX-FileCopyrightText: 2021-22 fosslinux <fosslinux@aussies.space>
# SPDX-FileCopyrightText: 2021 Andrius Štikonas <andrius@stikonas.eu>
# 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
@@ -7,6 +7,7 @@ you can run bootstap inside chroot.
"""
# SPDX-License-Identifier: GPL-3.0-or-later
+# SPDX-FileCopyrightText: 2022 Dor Askayo <dor.askayo@gmail.com>
# SPDX-FileCopyrightText: 2021 Andrius Štikonas <andrius@stikonas.eu>
# SPDX-FileCopyrightText: 2021 Bastian Bittorf <bb@npl.de>
# SPDX-FileCopyrightText: 2021 Melg Eight <public.melg8@gmail.com>
@@ -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
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""System A"""
# SPDX-License-Identifier: GPL-3.0-or-later
+# SPDX-FileCopyrightText: 2022 Dor Askayo <dor.askayo@gmail.com>
# SPDX-FileCopyrightText: 2021 Andrius Štikonas <andrius@stikonas.eu>
# SPDX-FileCopyrightText: 2021 Melg Eight <public.melg8@gmail.com>
# SPDX-FileCopyrightText: 2021-22 fosslinux <fosslinux@aussies.space>
@@ -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
@@ -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
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""System C"""
# SPDX-License-Identifier: GPL-3.0-or-later
+# SPDX-FileCopyrightText: 2022 Dor Askayo <dor.askayo@gmail.com>
# SPDX-FileCopyrightText: 2021-22 fosslinux <fosslinux@aussies.space>
# SPDX-FileCopyrightText: 2021 Andrius Štikonas <andrius@stikonas.eu>
@@ -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):