if build:
with complete_step("Running prepare script in build overlay…"), mount_build_overlay(state):
run_workspace_command(
- state,
+ state.root,
["/root/prepare", "build"],
network=True,
bwrap_params=bwrap,
- env=dict(SRCDIR="/root/src"),
+ env=dict(SRCDIR="/root/src") | state.environment,
)
clean()
else:
with complete_step("Running prepare script…"):
run_workspace_command(
- state,
+ state.root,
["/root/prepare", "final"],
network=True,
bwrap_params=bwrap,
- env=dict(SRCDIR="/root/src"),
+ env=dict(SRCDIR="/root/src") | state.environment,
)
clean()
"--bind", state.config.postinst_script, "/root/postinst",
]
- run_workspace_command(state, ["/root/postinst", "final"], bwrap_params=bwrap,
- network=state.config.with_network)
+ run_workspace_command(state.root, ["/root/postinst", "final"], bwrap_params=bwrap,
+ network=state.config.with_network, env=state.environment)
state.root.joinpath("root/postinst").unlink()
not state.root.joinpath("usr/lib/kernel/install.d/50-dracut.install").exists() and
not state.root.joinpath("etc/kernel/install.d/50-dracut.install").exists()):
with complete_step("Running dpkg-reconfigure dracut…"):
- run_workspace_command(state, ["dpkg-reconfigure", "dracut"], env=dict(hostonly_l="no"))
+ run_workspace_command(state.root, ["dpkg-reconfigure", "dracut"],
+ env=dict(hostonly_l="no") | state.environment)
return
with complete_step("Running kernel-install…"):
cmd.insert(1, "--verbose")
# Make dracut think --no-host-only was passed via the CLI.
- run_workspace_command(state, cmd, env=dict(hostonly_l="no"))
+ run_workspace_command(state.root, cmd, env=dict(hostonly_l="no") | state.environment)
if machine_id and (p := state.root / "boot" / machine_id / kver / "initrd").exists():
shutil.move(p, state.root / state.installer.initrd_path(kver))
cmd = f"mkdir /tmp/relabel && mount --bind / /tmp/relabel && exec setfiles -m -r /tmp/relabel -F {fc} /tmp/relabel || exit $?"
with complete_step(f"Relabeling files using {policy} policy"):
- run_workspace_command(state, ["sh", "-c", cmd])
+ run_workspace_command(state.root, ["sh", "-c", cmd], env=state.environment)
def reuse_cache_tree(state: MkosiState) -> bool:
# build-script output goes to stdout so we can run language servers from within mkosi
# build-scripts. See https://github.com/systemd/mkosi/pull/566 for more information.
- run_workspace_command(state, cmd, network=state.config.with_network, bwrap_params=bwrap,
- stdout=sys.stdout, env=env)
+ run_workspace_command(state.root, cmd, network=state.config.with_network, bwrap_params=bwrap,
+ stdout=sys.stdout, env=env | state.environment)
def need_cache_tree(state: MkosiState) -> bool:
from textwrap import dedent
from mkosi.distributions import DistributionInstaller
-from mkosi.run import run_with_apivfs
+from mkosi.run import bwrap
from mkosi.types import PathString
from mkosi.util import MkosiState, sort_packages
if state.config.initrds:
cmdline += ["--assume-installed", "initramfs"]
- run_with_apivfs(state, cmdline, env=dict(KERNEL_INSTALL_BYPASS="1"))
+ bwrap(cmdline, apivfs=state.root, env=dict(KERNEL_INSTALL_BYPASS="1") | state.environment)
@classmethod
def install(cls, state: MkosiState) -> None:
- cls.install_packages(state, ["setup"])
+ cls.install_packages(state, ["filesystem"], apivfs=False)
# On Fedora, the default rpmdb has moved to /usr/lib/sysimage/rpm so if that's the case we need to
# move it back to /var/lib/rpm on CentOS.
move_rpm_db(state.root)
@classmethod
- def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
+ def install_packages(cls, state: MkosiState, packages: Sequence[str], apivfs: bool = True) -> None:
release = int(state.config.release)
if release <= 7:
else:
env = {}
- invoke_dnf(state, "install", packages, env)
+ invoke_dnf(state, "install", packages, env, apivfs=apivfs)
@classmethod
def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
from textwrap import dedent
from mkosi.distributions import DistributionInstaller
-from mkosi.run import run, run_with_apivfs
+from mkosi.run import bwrap, run
from mkosi.types import CompletedProcess, PathString
from mkosi.util import MkosiState
INITRD="No",
)
- return run_with_apivfs(state, ["apt-get", operation, *extra], env=env)
+ return bwrap(["apt-get", operation, *extra], apivfs=state.root, env=env | state.environment)
from mkosi.distributions import DistributionInstaller
from mkosi.remove import unlink_try_hard
-from mkosi.run import run_with_apivfs
+from mkosi.run import bwrap
from mkosi.util import Distribution, MkosiState, detect_distribution, sort_packages
@classmethod
def install(cls, state: MkosiState) -> None:
- cls.install_packages(state, ["setup"])
+ cls.install_packages(state, ["filesystem"], apivfs=False)
@classmethod
- def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
+ def install_packages(cls, state: MkosiState, packages: Sequence[str], apivfs: bool = True) -> None:
release, releasever = parse_fedora_release(state.config.release)
if state.config.local_mirror:
repos += [Repo("updates", updates_url, gpgpath, gpgurl)]
setup_dnf(state, repos)
- invoke_dnf(state, "install", packages)
+ invoke_dnf(state, "install", packages, apivfs=apivfs)
@classmethod
def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
)
-def invoke_dnf(state: MkosiState, command: str, packages: Iterable[str], env: Mapping[str, Any] = {}) -> None:
+def invoke_dnf(
+ state: MkosiState,
+ command: str,
+ packages: Iterable[str],
+ env: Mapping[str, Any] = {},
+ apivfs: bool = True
+) -> None:
if state.config.distribution == Distribution.fedora:
release, _ = parse_fedora_release(state.config.release)
else:
cmdline += sort_packages(packages)
- run_with_apivfs(state, cmdline, env=dict(KERNEL_INSTALL_BYPASS="1") | env)
+ bwrap(cmdline, apivfs=state.root if apivfs else None,
+ env=dict(KERNEL_INSTALL_BYPASS="1") | env | state.environment)
distribution, _ = detect_distribution()
if distribution not in (Distribution.debian, Distribution.ubuntu):
else:
emerge_default_opts += ["--quiet-build", "--quiet"]
cmd = ["emerge", *pkgs, *emerge_default_opts, *opts, *actions]
- run_workspace_command(state, cmd, network=True)
+ run_workspace_command(state.root, cmd, network=True, env=state.environment)
class Gentoo:
)
def get_snapshot_of_portage_tree(self) -> None:
- run_workspace_command(self.state, ["/usr/bin/emerge-webrsync"], network=True)
+ run_workspace_command(self.state.root, ["/usr/bin/emerge-webrsync"], network=True,
+ env=self.state.environment)
def update_stage3(self) -> None:
invoke_emerge(self.state, opts=self.EMERGE_UPDATE_OPTS, pkgs=self.pkgs['boot'])
@classmethod
def install(cls, state: MkosiState) -> None:
- cls.install_packages(state, ["setup"])
+ cls.install_packages(state, ["filesystem"], apivfs=False)
@classmethod
- def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
+ def install_packages(cls, state: MkosiState, packages: Sequence[str], apivfs: bool = True) -> None:
release = state.config.release.strip("'")
if state.config.local_mirror:
repos += [Repo(f"mageia-{release}-updates", updates_url, gpgpath)]
setup_dnf(state, repos)
- invoke_dnf(state, "install", packages)
+ invoke_dnf(state, "install", packages, apivfs=apivfs)
@classmethod
def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
@classmethod
def install(cls, state: MkosiState) -> None:
- cls.install_packages(state, ["setup"])
+ cls.install_packages(state, ["filesystem"])
@classmethod
- def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
+ def install_packages(cls, state: MkosiState, packages: Sequence[str], apivfs: bool = True) -> None:
release = state.config.release.strip("'")
if release[0].isdigit():
repos += [Repo("updates", updates_url, gpgpath)]
setup_dnf(state, repos)
- invoke_dnf(state, "install", packages)
+ invoke_dnf(state, "install", packages, apivfs=apivfs)
@classmethod
def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
from textwrap import dedent
from mkosi.distributions import DistributionInstaller
-from mkosi.run import run_with_apivfs
+from mkosi.run import bwrap
from mkosi.types import PathString
from mkosi.util import MkosiState
@classmethod
def install(cls, state: MkosiState) -> None:
- cls.install_packages(state, ["filesystem", "system-user-root"])
+ cls.install_packages(state, ["filesystem"], apivfs=False)
@classmethod
- def install_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
+ def install_packages(cls, state: MkosiState, packages: Sequence[str], apivfs: bool = True) -> None:
release = state.config.release
if release == "leap":
release = "stable"
repos += [("repo-update", updates_url)]
setup_zypper(state, repos)
- invoke_zypper(state, "install", ["-y", "--download-in-advance", "--no-recommends"], packages)
+ invoke_zypper(state, "install", ["-y", "--download-in-advance", "--no-recommends"], packages, apivfs=apivfs)
@classmethod
def remove_packages(cls, state: MkosiState, packages: Sequence[str]) -> None:
)
-def invoke_zypper(state: MkosiState, verb: str, options: Sequence[str], packages: Sequence[str]) -> None:
+def invoke_zypper(
+ state: MkosiState,
+ verb: str,
+ options: Sequence[str],
+ packages: Sequence[str],
+ apivfs: bool = True
+) -> None:
cmdline: list[PathString] = [
"zypper",
"--root", state.root,
*packages,
]
- run_with_apivfs(state, cmdline,
- env=dict(ZYPP_CONF=str(state.workspace / "zypp.conf"), KERNEL_INSTALL_BYPASS="1"))
+ env = dict(ZYPP_CONF=str(state.workspace / "zypp.conf"), KERNEL_INSTALL_BYPASS="1") | state.environment
+
+ bwrap(cmdline, apivfs=state.root if apivfs else None, env=env)
+
import signal
import subprocess
import sys
+import tempfile
import traceback
from pathlib import Path
from types import TracebackType
from mkosi.log import ARG_DEBUG, die
from mkosi.types import _FILE, CompletedProcess, PathString, Popen
-from mkosi.util import MkosiState, current_user
+from mkosi.util import current_user
CLONE_NEWNS = 0x00020000
CLONE_NEWUSER = 0x10000000
LANG="C.UTF-8",
) | env
- if env["PATH"] == "":
- del env["PATH"]
-
if "run" in ARG_DEBUG:
env["SYSTEMD_LOG_LEVEL"] = "debug"
die(f'"{shlex.join(str(s) for s in cmdline)}" returned non-zero exit code {e.returncode}.', e)
-def run_with_apivfs(
- state: MkosiState,
+def bwrap(
cmd: Sequence[PathString],
- bwrap_params: Sequence[PathString] = tuple(),
+ *,
+ apivfs: Optional[Path] = None,
stdout: _FILE = None,
env: Mapping[str, PathString] = {},
) -> CompletedProcess:
"--unshare-pid",
"--dev-bind", "/", "/",
"--chdir", Path.cwd(),
- "--tmpfs", state.root / "run",
- "--tmpfs", state.root / "tmp",
- "--proc", state.root / "proc",
- "--dev", state.root / "dev",
- "--ro-bind", "/sys", state.root / "sys",
- "--bind", state.var_tmp, state.root / "var/tmp",
"--die-with-parent",
]
- # If passwd or a related file exists in the root directory, bind mount it over the host files while we
- # run the command, to make sure that the command we run uses user/group information from the root instead
- # of from the host. If the file doesn't exist yet, mount over /dev/null instead.
- for f in ("passwd", "group", "shadow", "gshadow"):
- p = state.root / "etc" / f
- if p.exists():
- cmdline += ["--bind", p, f"/etc/{f}"]
- else:
- cmdline += ["--bind", "/dev/null", f"/etc/{f}"]
-
- cmdline += [
- *bwrap_params,
- "sh", "-c",
- ]
-
- env = env | state.environment
+ if apivfs:
+ cmdline += [
+ "--tmpfs", apivfs / "run",
+ "--proc", apivfs / "proc",
+ "--dev", apivfs / "dev",
+ "--ro-bind", "/sys", apivfs / "sys",
+ "--bind", "/tmp", apivfs / "tmp",
+ "--bind", "/var/tmp", apivfs / "var/tmp",
+ "--bind", "/dev/shm", apivfs / "dev/shm",
+ ]
- template = f"chmod 1777 {state.root / 'tmp'} {state.root / 'var/tmp'} {state.root / 'dev/shm'} && exec {{}} || exit $?"
+ # If passwd or a related file exists in the apivfs directory, bind mount it over the host files while
+ # we run the command, to make sure that the command we run uses user/group information from the
+ # apivfs directory instead of from the host. If the file doesn't exist yet, mount over /dev/null
+ # instead.
+ for f in ("passwd", "group", "shadow", "gshadow"):
+ p = apivfs / "etc" / f
+ if p.exists():
+ cmdline += ["--bind", p, f"/etc/{f}"]
+ else:
+ cmdline += ["--bind", "/dev/null", f"/etc/{f}"]
try:
- return run([*cmdline, template.format(shlex.join(str(s) for s in cmd))],
- text=True, stdout=stdout, env=env, log=False)
+ return run([*cmdline, *cmd], text=True, stdout=stdout, env=env, log=False)
except subprocess.CalledProcessError as e:
if "run" in ARG_DEBUG:
- run([*cmdline, template.format("sh")], check=False, env=env, log=False)
+ run([*cmdline, "sh"], stdin=sys.stdin, check=False, env=env, log=False)
die(f'"{shlex.join(str(s) for s in cmd)}" returned non-zero exit code {e.returncode}.')
def run_workspace_command(
- state: MkosiState,
+ root: Path,
cmd: Sequence[PathString],
bwrap_params: Sequence[PathString] = tuple(),
network: bool = False,
"--unshare-ipc",
"--unshare-pid",
"--unshare-cgroup",
- "--bind", state.root, "/",
+ "--bind", root, "/",
"--tmpfs", "/run",
- "--tmpfs", "/tmp",
"--dev", "/dev",
"--proc", "/proc",
"--ro-bind", "/sys", "/sys",
- "--bind", state.var_tmp, "/var/tmp",
+ "--bind", "/tmp", "/tmp",
+ "--bind", "/var/tmp", "/var/tmp",
+ "--bind", "/dev/shm", "/dev/shm",
"--die-with-parent",
*bwrap_params,
]
- resolve = state.root.joinpath("etc/resolv.conf")
+ resolve = root.joinpath("etc/resolv.conf")
+
+ tmp = Path(tempfile.NamedTemporaryFile(delete=False).name)
if network:
# Bubblewrap does not mount over symlinks and /etc/resolv.conf might be a symlink. Deal with this by
# temporarily moving the file somewhere else.
if resolve.is_symlink():
- shutil.move(resolve, state.workspace / "resolv.conf")
+ shutil.move(resolve, tmp)
# If we're using the host network namespace, use the same resolver
cmdline += ["--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf"]
else:
cmdline += ["--unshare-net"]
- cmdline += ["sh", "-c"]
-
env = dict(
container="mkosi",
SYSTEMD_OFFLINE=str(int(network)),
HOME="/",
- # Make sure the default PATH of the distro shell is used.
- PATH="",
- ) | env | state.environment
-
- template = "chmod 1777 /tmp /var/tmp /dev/shm && PATH=$PATH:/usr/bin:/usr/sbin exec {} || exit $?"
+ PATH="/usr/bin:/usr/sbin",
+ ) | env
try:
- return run([*cmdline, template.format(shlex.join(str(s) for s in cmd))],
- text=True, stdout=stdout, env=env, log=False)
+ return run([*cmdline, *cmd], text=True, stdout=stdout, env=env, log=False)
except subprocess.CalledProcessError as e:
if "run" in ARG_DEBUG:
- run([*cmdline, template.format("sh")], check=False, env=env, log=False)
+ run([*cmdline, "sh"], stdin=sys.stdin, check=False, env=env, log=False)
die(f'"{shlex.join(str(s) for s in cmd)}" returned non-zero exit code {e.returncode}.')
finally:
- if state.workspace.joinpath("resolv.conf").is_symlink():
+ if tmp.is_symlink():
resolve.unlink(missing_ok=True)
- shutil.move(state.workspace.joinpath("resolv.conf"), resolve)
+ shutil.move(tmp, resolve)