diff --git a/xbstrap/base.py b/xbstrap/base.py index dd02e7d..3d39099 100644 --- a/xbstrap/base.py +++ b/xbstrap/base.py @@ -598,6 +598,14 @@ def container_runtime(self): return None return self._site_yml["container"].get("runtime") + @property + def container_build_yml(self): + site_container_yml = self._site_yml.get("container") or {} + if "rootfs" in site_container_yml: + return site_container_yml + root_container_yml = self._root_yml.get("container") or {} + return root_container_yml + @property def site_architectures(self): return self._site_archs @@ -2315,24 +2323,42 @@ def name(self): def check_if_prepared(self, _, cfg): rootfs_cache = os.path.join(_util.find_cache_dir(), "rootfs_cache") - rootfs_marker_path = os.path.join(rootfs_cache, self.hash + ".prepared") + rootfs_tar_path = os.path.join(rootfs_cache, self.hash + ".tar") + + return ItemState(missing=not os.path.exists(rootfs_tar_path)) - return ItemState(missing=not os.path.exists(rootfs_marker_path)) + +# Build the initial file system that debootstrap runs on. +def _build_rootfs_seed_tar(tar_path): + components = ["var", "var/cache", "var/cache/apt", "var/cache/apt/archives"] + with tarfile.open(tar_path, "w") as tf: + for comp in components: + info = tarfile.TarInfo(name=comp) + info.type = tarfile.DIRTYPE + info.mode = 0o755 + info.uid = 0 + info.gid = 0 + info.uname = "root" + info.gname = "root" + info.mtime = 0 + tf.addfile(info) # Build the cbuildrt lower layer configuration for a given rootfs. def _build_rootfs_layers(cfg, rootfs): rootfs_cache = os.path.join(_util.find_cache_dir(), "rootfs_cache") + # OverlayFS requires at least two lower layers when no upper layer is used. + # Hence, always prepend an empty layer. + empty_layer_dir = os.path.join(rootfs_cache, "empty") + _util.try_mkdir(empty_layer_dir) + base_rootfs = cfg.get_rootfs(()) - layers = [ - os.path.join(rootfs_cache, base_rootfs.hash + "-base.tar"), - os.path.join(rootfs_cache, base_rootfs.hash + "-full.tar"), - ] + layers = [empty_layer_dir, os.path.join(rootfs_cache, base_rootfs.hash + ".tar.zstd")] for i in range(len(rootfs.packages)): pkg_rootfs = cfg.get_rootfs(rootfs.packages[: i + 1]) - layers.append(os.path.join(rootfs_cache, pkg_rootfs.hash + "-full.tar")) + layers.append(os.path.join(rootfs_cache, pkg_rootfs.hash + ".tar.zstd")) return layers @@ -2340,21 +2366,18 @@ def _build_rootfs_layers(cfg, rootfs): def prepare_rootfs(cfg, rootfs): # Inspired by https://codeberg.org/Mintsuki/jinx. - site_container_yml = cfg._site_yml.get("container", dict()) + build_yml = cfg.container_build_yml rootfs_cache = os.path.join(_util.find_cache_dir(), "rootfs_cache") _util.try_mkdir(rootfs_cache) - base_tar_path = os.path.join(rootfs_cache, rootfs.hash + "-base.tar") - full_tar_path = os.path.join(rootfs_cache, rootfs.hash + "-full.tar") - rootfs_marker_path = os.path.join(rootfs_cache, rootfs.hash + ".prepared") + out_tar_path = os.path.join(rootfs_cache, rootfs.hash + ".tar.zstd") - # Remove existing tar files. - for tar_path in (base_tar_path, full_tar_path): - try: - os.remove(tar_path) - except FileNotFoundError: - pass + # Remove existing tar file. + try: + os.remove(out_tar_path) + except FileNotFoundError: + pass def run_cbuildrt( args, @@ -2375,9 +2398,10 @@ def run_cbuildrt( "environ": environ or {}, }, "mapCurrentUserTo": { - "uid": site_container_yml["uid"], - "gid": site_container_yml["gid"], + "uid": build_yml["uid"], + "gid": build_yml["gid"], }, + "provideDev": True, "bindMounts": bind_mounts or [], "volumes": volumes or [], } @@ -2421,7 +2445,7 @@ def run_cbuildrt( rootfs={ "layers": lower_dirs, "withUpper": True, - "extractUpper": full_tar_path, + "extractUpper": out_tar_path, }, volumes=[ {"name": "apt_cache", "destination": "/var/cache/apt/archives"}, @@ -2434,8 +2458,8 @@ def run_cbuildrt( suite = container_yml["suite"] snapshot = container_yml["snapshot"] - src_mount = site_container_yml.get("src_mount") - build_mount = site_container_yml.get("build_mount") + src_mount = build_yml.get("src_mount") + build_mount = build_yml.get("build_mount") if src_mount.startswith("/"): src_mount = src_mount[1:] @@ -2456,80 +2480,74 @@ def run_cbuildrt( prepend=[os.path.join(_util.find_home(), "bin"), "/sbin"], ) - # Run debootstrap via cbuildrt with noChroot + noSystemMounts. - # This creates the -base.tar. - script = f""" - set -e - - target="$(pwd)" - - debootstrap '{suite}' "$target" \ - 'https://snapshot.debian.org/archive/debian/{snapshot}' - - mkdir -p "$target/{src_mount}" - mkdir -p "$target/{build_mount}" - - for dev in tty null zero full random urandom; do - rm -f "$target/dev/$dev" - touch "$target/dev/$dev" - done - """ - _util.log_info("Building base rootfs") - run_cbuildrt( - args=["sh", "-c", script], - rootfs={ - "layers": [empty_layer_dir], - "withUpper": True, - "extractUpper": base_tar_path, - }, - no_chroot_or_mounts=True, - volumes=[ - {"name": "apt_cache", "destination": "/var/cache/apt/archives"}, - ], - host_environ=host_environ, - ) + with tempfile.TemporaryDirectory() as scratch: + seed_tar = os.path.join(scratch, "seed.tar") + base_tar = os.path.join(scratch, "base.tar") + _build_rootfs_seed_tar(seed_tar) + + # Run debootstrap via cbuildrt with noChroot + noSystemMounts. + _util.log_info("Building base rootfs") + run_cbuildrt( + args=[ + "debootstrap", + suite, + ".", + f"https://snapshot.debian.org/archive/debian/{snapshot}", + ], + rootfs={ + "layers": [empty_layer_dir], + "withUpper": True, + "extractUpper": base_tar, + "importUpper": seed_tar, + }, + no_chroot_or_mounts=True, + volumes=[ + {"name": "apt_cache", "destination": "/var/cache/apt/archives"}, + ], + host_environ=host_environ, + ) - # Install packages and xbstrap itself. - # This creates the -full.tar. - base_pkgs = container_yml.get("base_packages", []) - base_pkgs_str = " ".join(shlex.quote(p) for p in base_pkgs) + # Install packages and xbstrap itself. + base_pkgs = container_yml.get("base_packages", []) + base_pkgs_str = " ".join(shlex.quote(p) for p in base_pkgs) - script = f""" - set -e + script = f""" + set -e - printf '%s\\n' 'en_US.UTF-8 UTF-8' > /etc/locale.gen + printf '%s\\n' 'en_US.UTF-8 UTF-8' > /etc/locale.gen - cat > /etc/apt/apt.conf < /etc/apt/apt.conf <