Merge pull request #175 from doraskayo/bwrap-bootstrap

Add a rootless bootstrap mode using bubblewrap
This commit is contained in:
Andrius Štikonas 2022-05-27 14:09:49 +01:00 committed by GitHub
commit 9bc2ca1726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 46 deletions

View File

@ -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 dont 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 dont 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.

View File

@ -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"""

View File

@ -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',

24
sysa.py
View File

@ -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()

View File

@ -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')

49
sysc.py
View File

@ -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):