should_compress_output,
spawn,
tmp_dir,
- var_tmp,
warn,
- workspace,
)
from .manifest import Manifest
from .syscall import blkpg_add_partition, blkpg_del_partition, reflink
state.root.joinpath("etc/kernel/install.conf").write_text("layout=bls\n")
if state.do_run_build_script or config.ssh or config.usr_only:
- root_home(config, state.root).mkdir(mode=0o750)
+ root_home(config, state).mkdir(mode=0o750)
if config.ssh and not state.do_run_build_script:
- root_home(config, state.root).joinpath(".ssh").mkdir(mode=0o700)
+ root_home(config, state).joinpath(".ssh").mkdir(mode=0o700)
if state.do_run_build_script:
- root_home(config, state.root).joinpath("dest").mkdir(mode=0o755)
+ root_home(config, state).joinpath("dest").mkdir(mode=0o755)
if config.build_dir is not None:
- root_home(config, state.root).joinpath("build").mkdir(0o755)
+ root_home(config, state).joinpath("build").mkdir(0o755)
if config.netdev and not state.do_run_build_script:
state.root.joinpath("etc/systemd").mkdir(mode=0o755)
def invoke_dnf(
- config: MkosiConfig, root: Path,
+ config: MkosiConfig,
+ state: MkosiState,
command: str,
packages: Iterable[str],
) -> None:
else:
release = config.release
- config_file = workspace(root) / "dnf.conf"
+ config_file = state.workspace / "dnf.conf"
cmd = 'dnf' if shutil.which('dnf') else 'yum'
"--best",
"--allowerasing",
f"--releasever={release}",
- f"--installroot={root}",
+ f"--installroot={state.root}",
"--setopt=keepcache=1",
"--setopt=install_weak_deps=0",
]
cmdline += [command, *sort_packages(packages)]
- with mount_api_vfs(root):
+ with mount_api_vfs(state.root):
run(cmdline, env=dict(KERNEL_INSTALL_BYPASS="1"))
distribution, _ = detect_distribution()
# On Debian, rpm/dnf ship with a patch to store the rpmdb under ~/
# so it needs to be copied back in the right location, otherwise
# the rpmdb will be broken. See: https://bugs.debian.org/1004863
- rpmdb_home = root / "root/.rpmdb"
+ rpmdb_home = state.root / "root/.rpmdb"
if rpmdb_home.exists():
# Take into account the new location in F36
- rpmdb = root / "usr/lib/sysimage/rpm"
+ rpmdb = state.root / "usr/lib/sysimage/rpm"
if not rpmdb.exists():
- rpmdb = root / "var/lib/rpm"
+ rpmdb = state.root / "var/lib/rpm"
unlink_try_hard(rpmdb)
shutil.move(cast(str, rpmdb_home), rpmdb)
) -> None:
packages = make_rpm_list(config, state, packages)
- invoke_dnf(config, state.root, 'install', packages)
+ invoke_dnf(config, state, 'install', packages)
class Repo(NamedTuple):
gpgurl: Optional[str] = None
-def setup_dnf(config: MkosiConfig, root: Path, repos: Sequence[Repo] = ()) -> None:
+def setup_dnf(config: MkosiConfig, state: MkosiState, repos: Sequence[Repo] = ()) -> None:
gpgcheck = True
- repo_file = workspace(root) / "mkosi.repo"
+ repo_file = state.workspace / "mkosi.repo"
with repo_file.open("w") as f:
for repo in repos:
gpgkey: Optional[str] = None
if config.use_host_repositories:
default_repos = ""
else:
- default_repos = f"reposdir={workspace(root)} {config.repos_dir if config.repos_dir else ''}"
+ default_repos = f"reposdir={state.workspace} {config.repos_dir if config.repos_dir else ''}"
- vars_dir = workspace(root) / "vars"
+ vars_dir = state.workspace / "vars"
vars_dir.mkdir(exist_ok=True)
- config_file = workspace(root) / "dnf.conf"
+ config_file = state.workspace / "dnf.conf"
config_file.write_text(
dedent(
f"""\
if updates_url is not None:
repos += [Repo("updates", updates_url, gpgpath, gpgurl)]
- setup_dnf(config, state.root, repos)
+ setup_dnf(config, state, repos)
packages = {*config.packages}
add_packages(config, packages, "systemd", "util-linux")
if updates_url is not None:
repos += [Repo("updates", updates_url, gpgpath)]
- setup_dnf(config, state.root, repos)
+ setup_dnf(config, state, repos)
packages = {*config.packages}
add_packages(config, packages, "basesystem-minimal")
if updates_url is not None:
repos += [Repo("updates", updates_url, gpgpath)]
- setup_dnf(config, state.root, repos)
+ setup_dnf(config, state, repos)
packages = {*config.packages}
# well we may use basesystem here, but that pulls lot of stuff
else:
repos = centos_stream_repos(config, epel_release)
- setup_dnf(config, state.root, repos)
+ setup_dnf(config, state, repos)
if "-stream" in config.release:
- workspace(state.root).joinpath("vars/stream").write_text(config.release)
+ state.workspace.joinpath("vars/stream").write_text(config.release)
packages = {*config.packages}
add_packages(config, packages, "systemd")
# run_workspace_command() to rebuild the rpm db.
if epel_release <= 8 and state.root.joinpath("usr/bin/rpm").exists():
cmdline = ["rpm", "--rebuilddb", "--define", "_db_backend bdb"]
- run_workspace_command(config, state.root, cmdline)
+ run_workspace_command(config, state, cmdline)
def debootstrap_knows_arg(arg: str) -> bool:
def invoke_apt(
config: MkosiConfig,
- root: Path,
+ state: MkosiState,
subcommand: str,
operation: str,
extra: Iterable[str],
cmdline = [
f"/usr/bin/apt-{subcommand}",
- "-o", f"Dir={root}",
- "-o", f"DPkg::Chroot-Directory={root}",
+ "-o", f"Dir={state.root}",
+ "-o", f"DPkg::Chroot-Directory={state.root}",
"-o", f"APT::Architecture={debarch}",
operation,
*extra,
# Overmount /etc/apt on the host with an empty directory, so that apt doesn't parse any configuration
# from it.
- with mount_bind(workspace(root) / "apt", Path("/") / "etc/apt"), mount_apt_local_mirror(config, root), mount_api_vfs(root):
+ with mount_bind(state.workspace / "apt", Path("/") / "etc/apt"), mount_apt_local_mirror(config, state.root), mount_api_vfs(state.root):
return run(cmdline, env=env, text=True, **kwargs)
root.joinpath(f"etc/apt/sources.list.d/{config.release}-security.list").write_text(f"{security}\n")
-def add_apt_package_if_exists(config: MkosiConfig, root: Path, extra_packages: Set[str], package: str) -> None:
- if invoke_apt(config, root, "cache", "search", ["--names-only", f"^{package}$"], stdout=PIPE).stdout.strip() != "":
+def add_apt_package_if_exists(config: MkosiConfig, state: MkosiState, extra_packages: Set[str], package: str) -> None:
+ if invoke_apt(config, state, "cache", "search", ["--names-only", f"^{package}$"], stdout=PIPE).stdout.strip() != "":
add_packages(config, extra_packages, package)
if not config.with_docs:
# Remove documentation installed by debootstrap
cmdline = ["/bin/rm", "-rf", *doc_paths]
- run_workspace_command(config, state.root, cmdline)
+ run_workspace_command(config, state, cmdline)
# Create dpkg.cfg to ignore documentation on new packages
dpkg_nodoc_conf = state.root / "etc/dpkg/dpkg.cfg.d/01_nodoc"
with dpkg_nodoc_conf.open("w") as f:
install_skeleton_trees(config, state.root, False, late=True)
- invoke_apt(config, state.root, "get", "update", ["--assume-yes"])
+ invoke_apt(config, state, "get", "update", ["--assume-yes"])
if config.bootable and not state.do_run_build_script and state.get_partition(PartitionIdentifier.esp):
- add_apt_package_if_exists(config, state.root, extra_packages, "systemd-boot")
+ add_apt_package_if_exists(config, state, extra_packages, "systemd-boot")
# systemd-resolved was split into a separate package
- add_apt_package_if_exists(config, state.root, extra_packages, "systemd-resolved")
+ add_apt_package_if_exists(config, state, extra_packages, "systemd-resolved")
- invoke_apt(config, state.root, "get", "install", ["--assume-yes", "--no-install-recommends", *extra_packages])
+ invoke_apt(config, state, "get", "install", ["--assume-yes", "--no-install-recommends", *extra_packages])
# Now clean up and add the real repositories, so that the image is ready
if config.local_mirror:
if path.exists():
path.chmod(permissions)
- pacman_conf = workspace(state.root) / "pacman.conf"
+ pacman_conf = state.workspace / "pacman.conf"
if config.repository_key_check:
sig_level = "Required DatabaseOptional"
else:
# If we need to use a local mirror, create a temporary repository definition
# that doesn't get in the image, as it is valid only at image build time.
if config.local_mirror:
- run(["zypper", "--reposd-dir", workspace(state.root) / "zypper-repos.d", "--root", state.root, "addrepo", "-ck", config.local_mirror, "local-mirror"])
+ run(["zypper", "--reposd-dir", state.workspace / "zypper-repos.d", "--root", state.root, "addrepo", "-ck", config.local_mirror, "local-mirror"])
if not config.with_docs:
state.root.joinpath("etc/zypp/zypp.conf").write_text("rpm.install.excludedocs = yes\n")
cmdline: List[PathString] = ["zypper"]
# --reposd-dir needs to be before the verb
if config.local_mirror:
- cmdline += ["--reposd-dir", workspace(state.root) / "zypper-repos.d"]
+ cmdline += ["--reposd-dir", state.workspace / "zypper-repos.d"]
cmdline += [
"--root",
state.root,
gentoo = Gentoo(config, state)
if gentoo.pkgs_fs:
- gentoo.invoke_emerge(config, state.root, pkgs=gentoo.pkgs_fs)
+ gentoo.invoke_emerge(config, state, pkgs=gentoo.pkgs_fs)
if not state.do_run_build_script and config.bootable:
# The gentoo stage3 tarball includes packages that may block chosen
# pkgs_boot. Using Gentoo.EMERGE_UPDATE_OPTS for opts allows the
# package manager to uninstall blockers.
- gentoo.invoke_emerge(config, state.root, pkgs=gentoo.pkgs_boot, opts=Gentoo.EMERGE_UPDATE_OPTS)
+ gentoo.invoke_emerge(config, state, pkgs=gentoo.pkgs_boot, opts=Gentoo.EMERGE_UPDATE_OPTS)
if config.packages:
- gentoo.invoke_emerge(config, state.root, pkgs=config.packages)
+ gentoo.invoke_emerge(config, state, pkgs=config.packages)
if state.do_run_build_script:
- gentoo.invoke_emerge(config, state.root, pkgs=config.build_packages)
+ gentoo.invoke_emerge(config, state, pkgs=config.build_packages)
def install_distribution(config: MkosiConfig, state: MkosiState, cached: bool) -> None:
# installation has completed.
link_rpm_db(state.root)
-def remove_packages(config: MkosiConfig, root: Path) -> None:
+def remove_packages(config: MkosiConfig, state: MkosiState) -> None:
"""Remove packages listed in config.remove_packages"""
if not config.remove_packages:
remove: Callable[[List[str]], Any]
if (config.distribution.package_type == PackageType.rpm):
- remove = lambda p: invoke_dnf(config, root, 'remove', p)
+ remove = lambda p: invoke_dnf(config, state, 'remove', p)
elif config.distribution.package_type == PackageType.deb:
- remove = lambda p: invoke_apt(config, root, "get", "purge", ["--assume-yes", "--auto-remove", *p])
+ remove = lambda p: invoke_apt(config, state, "get", "purge", ["--assume-yes", "--auto-remove", *p])
else:
die(f"Removing packages is not supported for {config.distribution}")
# place to mount it to. But if we create that we might as well
# just copy the file anyway.
- shutil.copy2(config.prepare_script, root_home(config, state.root) / "prepare")
+ shutil.copy2(config.prepare_script, root_home(config, state) / "prepare")
nspawn_params = nspawn_params_for_build_sources(config, SourceFileTransfer.mount)
- run_workspace_command(config, state.root, ["/root/prepare", verb],
+ run_workspace_command(config, state, ["/root/prepare", verb],
network=True, nspawn_params=nspawn_params, env=config.environment)
- srcdir = root_home(config, state.root) / "src"
+ srcdir = root_home(config, state) / "src"
if srcdir.exists():
os.rmdir(srcdir)
- os.unlink(root_home(config, state.root) / "prepare")
+ os.unlink(root_home(config, state) / "prepare")
def run_postinst_script(
# place to mount it to. But if we create that we might as well
# just copy the file anyway.
- shutil.copy2(config.postinst_script, root_home(config, state.root) / "postinst")
+ shutil.copy2(config.postinst_script, root_home(config, state) / "postinst")
- run_workspace_command(config, state.root, ["/root/postinst", verb],
+ run_workspace_command(config, state, ["/root/postinst", verb],
network=(config.with_network is True), env=config.environment)
- root_home(config, state.root).joinpath("postinst").unlink()
+ root_home(config, state).joinpath("postinst").unlink()
def output_dir(config: MkosiConfig) -> Path:
with complete_step("Installing boot loader…"):
if state.get_partition(PartitionIdentifier.esp):
- run_workspace_command(config, state.root, ["bootctl", "install"])
+ run_workspace_command(config, state, ["bootctl", "install"])
def install_extra_trees(config: MkosiConfig, root: Path, for_cache: bool) -> None:
if state.do_run_build_script:
if config.build_script is not None:
with complete_step("Copying in build script…"):
- copy_file(config.build_script, root_home(config, state.root) / config.build_script.name)
+ copy_file(config.build_script, root_home(config, state) / config.build_script.name)
else:
return
return
with complete_step("Copying in sources…"):
- target = root_home(config, state.root) / "src"
+ target = root_home(config, state) / "src"
if sft in (
SourceFileTransfer.copy_git_others,
return
with complete_step("Copying in build tree…"):
- copy_path(install_dir(config, state.root), state.root, copystat=False)
+ copy_path(install_dir(config, state), state.root, copystat=False)
def make_read_only(config: MkosiConfig, root: Path, for_cache: bool, b: bool = True) -> None:
return f
-def generate_xfs(config: MkosiConfig, root: Path, label: str, for_cache: bool) -> Optional[BinaryIO]:
+def generate_xfs(config: MkosiConfig, state: MkosiState, directory: Path, label: str, for_cache: bool) -> Optional[BinaryIO]:
if config.output_format != OutputFormat.gpt_xfs:
return None
if for_cache:
f.truncate(config.root_size)
run(mkfs_xfs_cmd(label) + [f.name])
- xfs_dir = workspace(root) / "xfs"
+ xfs_dir = state.workspace / "xfs"
xfs_dir.mkdir()
with get_loopdev(f) as loopdev, mount_loop(config, Path(loopdev.name), xfs_dir) as mp:
- copy_path(root, mp)
+ copy_path(directory, mp)
return f
-def make_generated_root(config: MkosiConfig, root: Path, for_cache: bool) -> Optional[BinaryIO]:
+def make_generated_root(config: MkosiConfig, state: MkosiState, for_cache: bool) -> Optional[BinaryIO]:
if not is_generated_root(config):
return None
label = "usr" if config.usr_only else "root"
- patched_root = root / "usr" if config.usr_only else root
+ patched_root = state.root / "usr" if config.usr_only else state.root
if config.output_format == OutputFormat.gpt_xfs:
- return generate_xfs(config, patched_root, label, for_cache)
+ return generate_xfs(config, state, patched_root, label, for_cache)
if config.output_format == OutputFormat.gpt_ext4:
return generate_ext4(config, patched_root, label, for_cache)
if config.output_format == OutputFormat.gpt_btrfs:
boot_options = f"{boot_options} {option}=PARTLABEL={partlabel}"
osrelease = state.root / "usr/lib/os-release"
- cmdline = workspace(state.root) / "cmdline"
+ cmdline = state.workspace / "cmdline"
cmdline.write_text(boot_options)
initrd = state.root / boot_directory(state, kver) / "initrd"
if for_cache:
return None
- authorized_keys = root_home(config, state.root) / ".ssh/authorized_keys"
+ authorized_keys = root_home(config, state) / ".ssh/authorized_keys"
f: Optional[TextIO]
if config.ssh_key:
f = open(config.ssh_key, mode="r", encoding="utf-8")
with complete_step("Generating initramfs images…"):
for kver, kimg in gen_kernel_images(config, state.root):
- run_workspace_command(config, state.root, ["kernel-install", "add", kver, Path("/") / kimg])
+ run_workspace_command(config, state, ["kernel-install", "add", kver, Path("/") / kimg])
@dataclasses.dataclass
for_cache, cached, mount=contextlib.nullcontext)
if cleanup:
- remove_packages(config, state.root)
+ remove_packages(config, state)
if manifest:
with complete_step("Recording packages in manifest…"):
invoke_fstrim(config, state, for_cache)
make_read_only(config, state.root, for_cache)
- generated_root = make_generated_root(config, state.root, for_cache)
+ generated_root = make_generated_root(config, state, for_cache)
generated_root_part = insert_generated_root(config, state, raw, loopdev, generated_root, for_cache)
split_root = (
(generated_root or extract_partition(config, state, encrypted.root, for_cache))
return "1" if b else "0"
-def install_dir(config: MkosiConfig, root: Path) -> Path:
- return config.install_dir or workspace(root).joinpath("dest")
+def install_dir(config: MkosiConfig, state: MkosiState) -> Path:
+ return config.install_dir or state.workspace / "dest"
-def run_build_script(config: MkosiConfig, root: Path, raw: Optional[BinaryIO]) -> None:
+def run_build_script(config: MkosiConfig, state: MkosiState, raw: Optional[BinaryIO]) -> None:
if config.build_script is None:
return
with complete_step("Running build script…"):
- os.makedirs(install_dir(config, root), mode=0o755, exist_ok=True)
+ os.makedirs(install_dir(config, state), mode=0o755, exist_ok=True)
- target = f"--directory={root}" if raw is None else f"--image={raw.name}"
+ target = f"--directory={state.root}" if raw is None else f"--image={raw.name}"
with_network = 1 if config.with_network is True else 0
"--as-pid2",
"--link-journal=no",
"--register=no",
- f"--bind={install_dir(config, root)}:/root/dest",
- f"--bind={var_tmp(root)}:/var/tmp",
+ f"--bind={install_dir(config, state)}:/root/dest",
+ f"--bind={state.var_tmp()}:/var/tmp",
f"--setenv=WITH_DOCS={one_zero(config.with_docs)}",
f"--setenv=WITH_TESTS={one_zero(config.with_tests)}",
f"--setenv=WITH_NETWORK={with_network}",
cmdline += ["--private-network"]
if config.usr_only:
- cmdline += [f"--bind={root_home(config, root)}:/root"]
+ cmdline += [f"--bind={root_home(config, state)}:/root"]
if config.nspawn_keep_unit:
cmdline += ["--keep-unit"]
with complete_step(f"Removing artifacts from {what}…"):
unlink_try_hard(state.root)
- unlink_try_hard(var_tmp(state.root))
+ unlink_try_hard(state.var_tmp())
if config.usr_only:
- unlink_try_hard(root_home(config, state.root))
+ unlink_try_hard(root_home(config, state))
def build_stuff(config: MkosiConfig) -> Manifest:
state = MkosiState(
cache_pre_dev=cache_image_path(config, is_final_image=False) if config.incremental else None,
cache_pre_inst=cache_image_path(config, is_final_image=True) if config.incremental else None,
- root=Path(workspace.name, "root"),
+ workspace=Path(workspace.name),
cache=cache,
do_run_build_script=False,
machine_id=config.machine_id or uuid.uuid4().hex,
state.do_run_build_script = True
image = build_image(config, state)
- run_build_script(config, state.root, image.raw)
+ run_build_script(config, state, image.raw)
remove_artifacts(config, state, image.raw, image.archive)
# Run the image builder for the second (final) stage
"USE": " ".join(self.portage_use_flags),
}
- self.sync_portage_tree(config, state.root)
+ self.sync_portage_tree(config, state)
self.set_profile(config)
self.set_default_repo()
self.unmask_arch()
self.provide_patches()
self.set_useflags()
self.mkosi_conf()
- self.baselayout(config, state.root)
+ self.baselayout(config, state)
self.fetch_fix_stage3(config, state.root)
- self.update_stage3(config, state.root)
- self.depclean(config, state.root)
+ self.update_stage3(config, state)
+ self.depclean(config, state)
- def sync_portage_tree(self, config: MkosiConfig,
- root: Path) -> None:
- self.invoke_emerge(config, root, inside_stage3=False, actions=["--sync"])
+ def sync_portage_tree(self, config: MkosiConfig, state: MkosiState) -> None:
+ self.invoke_emerge(config, state, inside_stage3=False, actions=["--sync"])
def fetch_fix_stage3(self, config: MkosiConfig, root: Path) -> None:
"""usrmerge tracker bug: https://bugs.gentoo.org/690294"""
def invoke_emerge(
self,
config: MkosiConfig,
- root: Path,
+ state: MkosiState,
inside_stage3: bool = True,
pkgs: Sequence[str] = (),
actions: Sequence[str] = (),
PREFIX_OPTS: List[str] = []
if "--sync" not in actions:
PREFIX_OPTS = [
- f"--config-root={root.resolve()}",
- f"--root={root.resolve()}",
- f"--sysroot={root.resolve()}",
+ f"--config-root={state.root.resolve()}",
+ f"--root={state.root.resolve()}",
+ f"--sysroot={state.root.resolve()}",
]
MkosiPrinter.print_step(f"Invoking emerge(1) pkgs={pkgs} "
emerge_main([*pkgs, *opts, *actions] + PREFIX_OPTS + self.emerge_default_opts)
else:
if config.usr_only:
- root_home(config, root).mkdir(mode=0o750, exist_ok=True)
+ root_home(config, state).mkdir(mode=0o750, exist_ok=True)
cmd = ["/usr/bin/emerge", *pkgs, *self.emerge_default_opts, *opts, *actions]
MkosiPrinter.print_step("Invoking emerge(1) inside stage3")
run_workspace_command(
config,
- root,
+ state,
cmd,
network=True,
env=self.emerge_vars,
nspawn_params=self.DEFAULT_NSPAWN_PARAMS,
)
- def baselayout(self, config: MkosiConfig, root: Path) -> None:
+ def baselayout(self, config: MkosiConfig, state: MkosiState) -> None:
# TOTHINK: sticky bizness when when image profile != host profile
# REMOVE: once upstream has moved this to stable releases of baselaouy
# https://gitweb.gentoo.org/proj/baselayout.git/commit/?id=57c250e24c70f8f9581860654cdec0d049345292
- self.invoke_emerge(config, root, inside_stage3=False,
+ self.invoke_emerge(config, state, inside_stage3=False,
opts=["--nodeps"],
pkgs=["=sys-apps/baselayout-9999"])
- def update_stage3(self, config: MkosiConfig, root: Path) -> None:
+ def update_stage3(self, config: MkosiConfig, state: MkosiState) -> None:
# exclude baselayout, it expects /sys/.keep but nspawn mounts host's
# /sys for us without the .keep file.
opts = self.EMERGE_UPDATE_OPTS + ["--exclude",
"sys-apps/baselayout"]
- self.invoke_emerge(config, root, pkgs=self.pkgs_sys, opts=opts)
+ self.invoke_emerge(config, state, pkgs=self.pkgs_sys, opts=opts)
# FIXME?: without this we get the following
# Synchronizing state of sshd.service with SysV service script with /lib/systemd/systemd-sysv-install.
# Executing: /lib/systemd/systemd-sysv-install --root=/var/tmp/mkosi-2b6snh_u/root enable sshd
# chroot: failed to run command ‘/usr/sbin/update-rc.d’: No such file or directory
- root.joinpath("etc/init.d/sshd").unlink()
+ state.root.joinpath("etc/init.d/sshd").unlink()
# "build" USE flag can go now, next time users do an update they will
# safely merge baselayout without that flag and it should be fine at
# that point.
self.baselayout_use.unlink()
- def depclean(self, config: MkosiConfig, root: Path) -> None:
- self.invoke_emerge(config, root, actions=["--depclean"])
+ def depclean(self, config: MkosiConfig, state: MkosiState) -> None:
+ self.invoke_emerge(config, state, actions=["--depclean"])
- def _dbg(self, config: MkosiConfig, root: Path) -> None:
+ def _dbg(self, config: MkosiConfig, state: MkosiState) -> None:
"""this is for dropping into shell to see what's wrong"""
cmdline = ["/bin/sh"]
run_workspace_command(
config,
- root,
+ state,
cmdline,
network=True,
nspawn_params=self.DEFAULT_NSPAWN_PARAMS,