import logging
import os
import resource
+import shlex
import shutil
import subprocess
import sys
import tempfile
import textwrap
import uuid
-from collections.abc import Iterator, Sequence
+from collections.abc import Iterator, Mapping, Sequence
from pathlib import Path
-from typing import Optional, TextIO, Union
+from typing import ContextManager, Optional, TextIO, Union
from mkosi.archive import extract_tar, make_cpio, make_tar
from mkosi.config import (
chdir,
flatten,
format_rlimit,
+ make_executable,
one_zero,
scopedenv,
try_import,
return ["mkosi-chroot", mountpoint] if script.suffix == ".chroot" else [os.fspath(script)]
+@contextlib.contextmanager
+def finalize_scripts(scripts: Mapping[str, Sequence[PathString]] = {}) -> Iterator[Path]:
+ with tempfile.TemporaryDirectory(prefix="mkosi-scripts") as d:
+ for name, script in scripts.items():
+ # Make sure we don't end up in a recursive loop when we name a script after the binary it execs
+ # by removing the scripts directory from the PATH when we execute a script.
+ (Path(d) / name).write_text(
+ textwrap.dedent(
+ f"""\
+ #!/bin/sh
+ DIR="$(cd "$(dirname "$0")" && pwd)"
+ PATH="$(echo "$PATH" | tr ':' '\\n' | grep -v "$DIR" | tr '\\n' ':')"
+ export PATH
+ exec {shlex.join(str(s) for s in script)} "$@"
+ """
+ )
+ )
+
+ make_executable(Path(d) / name)
+
+ yield Path(d)
+
+
+def finalize_host_scripts(state: MkosiState, chroot: Sequence[PathString]) -> ContextManager[Path]:
+ git = {"git": ("git", "-c", "safe.directory=*")} if find_binary("git") else {}
+ return finalize_scripts(git | {"mkosi-chroot": chroot} | package_manager_scripts(state))
+
+
+def finalize_chroot_scripts(state: MkosiState) -> ContextManager[Path]:
+ git = {"git": ("git", "-c", "safe.directory=*")} if find_binary("git", state.root) else {}
+ return finalize_scripts(git)
+
+
def run_prepare_scripts(state: MkosiState, build: bool) -> None:
if not state.config.prepare_scripts:
return
step_msg = "Running prepare script {}…"
arg = "final"
+ d = stack.enter_context(finalize_chroot_scripts(state))
+
for script in state.config.prepare_scripts:
chroot: list[PathString] = chroot_cmd(
state.root,
options=[
"--bind", script, "/work/prepare",
"--bind", Path.cwd(), "/work/src",
+ "--bind", d, "/work/scripts",
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
"--setenv", "BUILDROOT", "/",
],
)
+ d = stack.enter_context(finalize_host_scripts(state, chroot))
+
with complete_step(step_msg.format(script)):
bwrap(
script_maybe_chroot(script, "/work/prepare") + [arg],
network=True,
readonly=True,
options=finalize_mounts(state.config),
- scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+ scripts=d,
env=env | state.config.environment,
stdin=sys.stdin,
)
with (
mount_build_overlay(state),\
mount_passwd(state.root),\
- mount_volatile_overlay(state)\
+ mount_volatile_overlay(state),\
+ finalize_chroot_scripts(state) as d\
):
for script in state.config.build_scripts:
chroot = chroot_cmd(
"--bind", state.install_dir, "/work/dest",
"--bind", state.staging, "/work/out",
"--bind", Path.cwd(), "/work/src",
+ "--bind", d, "/work/scripts",
*(["--bind", str(state.config.build_dir), "/work/build"] if state.config.build_dir else []),
"--chdir", "/work/src",
"--setenv", "SRCDIR", "/work/src",
cmdline = state.args.cmdline if state.args.verb == Verb.build else []
- with complete_step(f"Running build script {script}…"):
+ with (
+ finalize_host_scripts(state, chroot) as d,\
+ complete_step(f"Running build script {script}…")\
+ ):
bwrap(
script_maybe_chroot(script, "/work/build-script") + cmdline,
network=state.config.with_network,
readonly=True,
options=finalize_mounts(state.config),
- scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+ scripts=d,
env=env | state.config.environment,
stdin=sys.stdin,
)
)
for script in state.config.postinst_scripts:
- chroot = chroot_cmd(
- state.root,
- options=[
- "--bind", script, "/work/postinst",
- "--bind", state.staging, "/work/out",
- "--bind", Path.cwd(), "/work/src",
- "--chdir", "/work/src",
- "--setenv", "SRCDIR", "/work/src",
- "--setenv", "OUTPUTDIR", "/work/out",
- "--setenv", "BUILDROOT", "/",
- ],
- )
-
- with complete_step(f"Running postinstall script {script}…"):
- bwrap(
- script_maybe_chroot(script, "/work/postinst") + ["final"],
- network=state.config.with_network,
- readonly=True,
- options=finalize_mounts(state.config),
- scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
- env=env | state.config.environment,
- stdin=sys.stdin,
+ with finalize_chroot_scripts(state) as d:
+ chroot = chroot_cmd(
+ state.root,
+ options=[
+ "--bind", script, "/work/postinst",
+ "--bind", state.staging, "/work/out",
+ "--bind", Path.cwd(), "/work/src",
+ "--bind", d, "/work/scripts",
+ "--chdir", "/work/src",
+ "--setenv", "SRCDIR", "/work/src",
+ "--setenv", "OUTPUTDIR", "/work/out",
+ "--setenv", "BUILDROOT", "/",
+ ],
)
+ with (
+ finalize_host_scripts(state, chroot) as d,\
+ complete_step(f"Running postinstall script {script}…")\
+ ):
+ bwrap(
+ script_maybe_chroot(script, "/work/postinst") + ["final"],
+ network=state.config.with_network,
+ readonly=True,
+ options=finalize_mounts(state.config),
+ scripts=d,
+ env=env | state.config.environment,
+ stdin=sys.stdin,
+ )
+
def run_finalize_scripts(state: MkosiState) -> None:
if not state.config.finalize_scripts:
)
for script in state.config.finalize_scripts:
- chroot = chroot_cmd(
- state.root,
- options=[
- "--bind", script, "/work/finalize",
- "--bind", state.staging, "/work/out",
- "--bind", Path.cwd(), "/work/src",
- "--chdir", "/work/src",
- "--setenv", "SRCDIR", "/work/src",
- "--setenv", "OUTPUTDIR", "/work/out",
- "--setenv", "BUILDROOT", "/",
- ],
- )
-
- with complete_step(f"Running finalize script {script}…"):
- bwrap(
- script_maybe_chroot(script, "/work/finalize"),
- network=state.config.with_network,
- readonly=True,
- options=finalize_mounts(state.config),
- scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
- env=env | state.config.environment,
- stdin=sys.stdin,
+ with finalize_chroot_scripts(state) as d:
+ chroot = chroot_cmd(
+ state.root,
+ options=[
+ "--bind", script, "/work/finalize",
+ "--bind", state.staging, "/work/out",
+ "--bind", Path.cwd(), "/work/src",
+ "--bind", d, "/work/scripts",
+ "--chdir", "/work/src",
+ "--setenv", "SRCDIR", "/work/src",
+ "--setenv", "OUTPUTDIR", "/work/out",
+ "--setenv", "BUILDROOT", "/",
+ ],
)
+ with (
+ finalize_host_scripts(state, chroot) as d,\
+ complete_step(f"Running finalize script {script}…")\
+ ):
+ bwrap(
+ script_maybe_chroot(script, "/work/finalize"),
+ network=state.config.with_network,
+ readonly=True,
+ options=finalize_mounts(state.config),
+ scripts=d,
+ env=env | state.config.environment,
+ stdin=sys.stdin,
+ )
+
def run_openssl(args: Sequence[PathString], stdout: _FILE = None) -> CompletedProcess:
with tempfile.NamedTemporaryFile(prefix="mkosi-openssl.cnf") as config:
import signal
import subprocess
import sys
-import tempfile
-import textwrap
import threading
from collections.abc import Awaitable, Collection, Iterator, Mapping, Sequence
from pathlib import Path
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SHELL, die
from mkosi.types import _FILE, CompletedProcess, PathString, Popen
-from mkosi.util import INVOKING_USER, flock, make_executable, one_zero
+from mkosi.util import INVOKING_USER, flock, one_zero
CLONE_NEWNS = 0x00020000
CLONE_NEWUSER = 0x10000000
readonly: bool = False,
options: Sequence[PathString] = (),
log: bool = True,
- scripts: Mapping[str, Sequence[PathString]] = {},
+ scripts: Optional[Path] = None,
env: Mapping[str, str] = {},
stdin: _FILE = None,
stdout: _FILE = None,
"--setenv", "SYSTEMD_OFFLINE", one_zero(network),
]
- with tempfile.TemporaryDirectory(prefix="mkosi-scripts") as d:
-
- for name, script in scripts.items():
- # Make sure we don't end up in a recursive loop when we name a script after the binary it execs
- # by removing the scripts directory from the PATH when we execute a script.
- (Path(d) / name).write_text(
- textwrap.dedent(
- f"""\
- #!/bin/sh
- PATH="$(echo $PATH | tr ':' '\\n' | grep -v {Path(d)} | tr '\\n' ':')"
- export PATH
- exec {shlex.join(str(s) for s in script)} "$@"
- """
- )
- )
-
- make_executable(Path(d) / name)
-
- cmdline += [
- "--setenv", "PATH", f"{d}:{os.environ['PATH']}",
- *options,
- "sh", "-c", "chmod 1777 /dev/shm && exec $0 \"$@\"",
- ]
+ cmdline += [
+ "--setenv", "PATH", f"{scripts or ''}:{os.environ['PATH']}",
+ *options,
+ "sh", "-c", "chmod 1777 /dev/shm && exec $0 \"$@\"",
+ ]
- if setpgid := find_binary("setpgid"):
- cmdline += [setpgid, "--foreground", "--"]
+ if setpgid := find_binary("setpgid"):
+ cmdline += [setpgid, "--foreground", "--"]
- try:
- result = run([*cmdline, *cmd], env=env, log=False, stdin=stdin, stdout=stdout, input=input)
- except subprocess.CalledProcessError as e:
- if log:
- c = shlex.join(os.fspath(s) for s in cmd)
- logging.error(f"\"{c}\" returned non-zero exit code {e.returncode}.")
- if ARG_DEBUG_SHELL.get():
- run([*cmdline, "sh"], stdin=sys.stdin, check=False, env=env, log=False)
- raise e
+ try:
+ result = run([*cmdline, *cmd], env=env, log=False, stdin=stdin, stdout=stdout, input=input)
+ except subprocess.CalledProcessError as e:
+ if log:
+ c = shlex.join(os.fspath(s) for s in cmd)
+ logging.error(f"\"{c}\" returned non-zero exit code {e.returncode}.")
+ if ARG_DEBUG_SHELL.get():
+ run([*cmdline, "sh"], stdin=sys.stdin, check=False, env=env, log=False)
+ raise e
- return result
+ return result
def finalize_passwd_mounts(root: Path) -> list[PathString]:
"--dev-bind", root, "/",
"--setenv", "container", "mkosi",
"--setenv", "HOME", "/",
- "--setenv", "PATH", "/usr/bin:/usr/sbin",
+ "--setenv", "PATH", "/work/scripts:/usr/bin:/usr/sbin",
]
resolve = Path("etc/resolv.conf")