clean_package_manager_metadata,
finalize_package_manager_mounts,
package_manager_scripts,
+ rchown_package_manager_cache_dirs,
)
from mkosi.kmod import gen_required_kernel_modules, process_kernel_modules
from mkosi.log import ARG_DEBUG, complete_step, die, log_notice, log_step
"--cache-only", str(context.config.cache_only),
"--output-dir", str(context.workspace / "initrd"),
*(["--workspace-dir", str(context.config.workspace_dir)] if context.config.workspace_dir else []),
- "--cache-dir", str(context.cache_dir),
+ *(["--cache-dir", str(context.config.cache_dir)] if context.config.cache_dir else []),
+ *(["--package-cache-dir", str(context.config.package_cache_dir)] if context.config.package_cache_dir else []),
*(["--local-mirror", str(context.config.local_mirror)] if context.config.local_mirror else []),
"--incremental", str(context.config.incremental),
"--acl", str(context.config.acl),
*(["--output-dir", str(config.output_dir)] if config.output_dir else []),
*(["--workspace-dir", str(config.workspace_dir)] if config.workspace_dir else []),
*(["--cache-dir", str(config.cache_dir)] if config.cache_dir else []),
+ *(["--package-cache-dir", str(config.package_cache_dir)] if config.package_cache_dir else []),
"--incremental", str(config.incremental),
"--acl", str(config.acl),
*([f"--package={package}" for package in config.tools_tree_packages]),
with complete_step(f"Clearing out build directory of {config.name()} image…"):
rmtree(*config.build_dir.iterdir())
- if remove_package_cache and config.cache_dir and config.cache_dir.exists() and any(config.cache_dir.iterdir()):
+ if (
+ remove_package_cache and
+ config.package_cache_dir and
+ config.package_cache_dir.exists() and
+ any(config.package_cache_dir.iterdir())
+ ):
with complete_step(f"Clearing out package cache of {config.name()} image…"):
rmtree(
*(
- config.cache_dir / p / d
+ config.package_cache_dir / p / d
for p in ("cache", "lib")
for d in ("apt", "dnf", "libdnf5", "pacman", "zypp")
),
if Path(d).exists():
run(["mount", "--rbind", d, d, "--options", "ro"])
+ # Create these as the invoking user to make sure they're owned by the user running mkosi.
+ for p in (
+ config.output_dir,
+ config.cache_dir,
+ config.package_cache_dir_or_default(),
+ config.package_state_dir_or_default(),
+ config.build_dir,
+ config.workspace_dir,
+ ):
+ if p:
+ INVOKING_USER.mkdir(p)
+
with (
complete_step(f"Building {config.name()} image"),
prepend_to_environ_path(config),
+ acl_toggle_build(config, INVOKING_USER.uid),
+ rchown_package_manager_cache_dirs(config),
):
- # After tools have been mounted, check if we have what we need
check_tools(config, Verb.build)
-
- # Create these as the invoking user to make sure they're owned by the user running mkosi.
- for p in (
- config.output_dir,
- config.cache_dir,
- config.build_dir,
- config.workspace_dir,
- ):
- if p:
- run(["mkdir", "--parents", p], user=INVOKING_USER.uid, group=INVOKING_USER.gid)
-
- with acl_toggle_build(config, INVOKING_USER.uid):
- build_image(args, config, resources=resources)
+ build_image(args, config, resources=resources)
def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
output_dir: Optional[Path]
workspace_dir: Optional[Path]
cache_dir: Optional[Path]
+ package_cache_dir: Optional[Path]
build_dir: Optional[Path]
image_id: Optional[str]
image_version: Optional[str]
if self.workspace_dir:
return self.workspace_dir
- if (cache := os.getenv("XDG_CACHE_HOME")) and Path(cache).exists():
- return Path(cache)
-
- # If we're running from /home and there's a cache or output directory in /home, we want to use a workspace
- # directory in /home as well as /home might be on a separate partition or subvolume which means that to take
- # advantage of reflinks and such, the workspace directory has to be on the same partition/subvolume.
- if (
- Path.cwd().is_relative_to(INVOKING_USER.home()) and
- (INVOKING_USER.home() / ".cache").exists() and
- (
- self.cache_dir and self.cache_dir.is_relative_to(INVOKING_USER.home()) or
- self.output_dir and self.output_dir.is_relative_to(INVOKING_USER.home())
- )
- ):
- return INVOKING_USER.home() / ".cache"
+ if (cache := INVOKING_USER.cache_dir()) and cache != Path("/var/cache"):
+ return cache
return Path("/var/tmp")
+ def package_cache_dir_or_default(self) -> Path:
+ return self.package_cache_dir or INVOKING_USER.cache_dir()
+
+ def package_state_dir_or_default(self) -> Path:
+ return self.package_cache_dir or INVOKING_USER.state_dir()
+
def tools(self) -> Path:
return self.tools_tree or Path("/")
section="Output",
parse=config_make_path_parser(required=False),
paths=("mkosi.cache",),
- help="Package cache path",
+ help="Incremental cache directory",
+ ),
+ ConfigSetting(
+ dest="package_cache_dir",
+ metavar="PATH",
+ name="PackageCacheDirectory",
+ section="Output",
+ parse=config_make_path_parser(required=False),
+ help="Package cache directory",
),
ConfigSetting(
dest="build_dir",
Output Directory: {config.output_dir_or_cwd()}
Workspace Directory: {config.workspace_dir_or_default()}
Cache Directory: {none_to_none(config.cache_dir)}
+ Package Cache Directory: {none_to_default(config.package_cache_dir)}
Build Directory: {none_to_none(config.build_dir)}
Image ID: {config.image_id}
Image Version: {config.image_version}
self.pkgmngr.mkdir()
self.packages.mkdir()
self.install_dir.mkdir(exist_ok=True)
- self.cache_dir.mkdir(parents=True, exist_ok=True)
@property
def root(self) -> Path:
def packages(self) -> Path:
return self.workspace / "packages"
- @property
- def cache_dir(self) -> Path:
- return self.config.cache_dir or (self.workspace / "cache")
-
@property
def install_dir(self) -> Path:
return self.workspace / "dest"
with (
# The deb paths will be in the form of "/var/cache/apt/<deb>" so we transform them to the corresponding
# path in mkosi's package cache directory.
- open(context.cache_dir / Path(deb).relative_to("/var"), "rb") as i,
+ open(context.config.package_cache_dir_or_default() / Path(deb).relative_to("/var/cache"), "rb") as i,
tempfile.NamedTemporaryFile() as o
):
run(["dpkg-deb", "--fsys-tarfile", "/dev/stdin"], stdin=i, stdout=o, sandbox=context.sandbox())
# SPDX-License-Identifier: LGPL-2.1+
+import contextlib
import os
+from collections.abc import Iterator
from pathlib import Path
-from mkosi.config import ConfigFeature
+from mkosi.config import Config, ConfigFeature
from mkosi.context import Context
+from mkosi.log import complete_step
from mkosi.sandbox import apivfs_cmd, finalize_crypto_mounts
from mkosi.tree import rmtree
from mkosi.types import PathString
+from mkosi.user import INVOKING_USER
from mkosi.util import flatten
]
mounts += flatten(
- ["--bind", context.cache_dir / d, Path("/var") / d]
- for d in (
- "lib/apt",
- "cache/apt",
- f"cache/{dnf_subdir(context)}",
- f"lib/{dnf_subdir(context)}",
- "cache/pacman/pkg",
- "cache/zypp",
- )
- if (context.cache_dir / d).exists()
+ ["--bind", context.config.package_cache_dir_or_default() / d, Path("/var/cache") / d]
+ for d in ("apt", dnf_subdir(context), "pacman/pkg", "zypp")
+ if (context.config.package_cache_dir_or_default() / d).exists()
+ )
+
+ mounts += flatten(
+ ["--bind", context.config.package_state_dir_or_default() / d, Path("/var/lib") / d]
+ for d in ("apt", dnf_subdir(context))
+ if (context.config.package_state_dir_or_default() / d).exists()
)
return mounts
+
+
+@contextlib.contextmanager
+def rchown_package_manager_cache_dirs(config: Config) -> Iterator[None]:
+ try:
+ yield
+ finally:
+ if INVOKING_USER.is_regular_user():
+ with complete_step("Fixing ownership of package manager cache directories"):
+ for p in ("apt", "dnf", "libdnf5", "pacman", "zypp"):
+ for d in (config.package_cache_dir_or_default(), config.package_state_dir_or_default()):
+ INVOKING_USER.rchown(d / p)
from mkosi.run import find_binary, run
from mkosi.sandbox import apivfs_cmd
from mkosi.types import PathString
+from mkosi.user import INVOKING_USER
from mkosi.util import sort_packages, umask
(context.root / "var/lib/dpkg").mkdir(parents=True, exist_ok=True)
(context.root / "var/lib/dpkg/status").touch()
- (context.cache_dir / "lib/apt").mkdir(exist_ok=True, parents=True)
- (context.cache_dir / "cache/apt").mkdir(exist_ok=True, parents=True)
+ INVOKING_USER.mkdir(context.config.package_cache_dir_or_default() / "apt")
+ INVOKING_USER.mkdir(context.config.package_state_dir_or_default() / "apt")
# We have a special apt.conf outside of pkgmngr dir that only configures "Dir::Etc" that we pass to APT_CONFIG to
# tell apt it should read config files from /etc/apt in case this is overridden by distributions. This is required
from mkosi.run import find_binary, run
from mkosi.sandbox import apivfs_cmd
from mkosi.types import PathString
+from mkosi.user import INVOKING_USER
from mkosi.util import sort_packages
(context.pkgmngr / "etc/dnf/vars").mkdir(exist_ok=True, parents=True)
(context.pkgmngr / "etc/yum.repos.d").mkdir(exist_ok=True, parents=True)
- (context.cache_dir / "cache" / dnf_subdir(context)).mkdir(exist_ok=True, parents=True)
- (context.cache_dir / "lib" / dnf_subdir(context)).mkdir(exist_ok=True, parents=True)
+ INVOKING_USER.mkdir(context.config.package_cache_dir_or_default() / dnf_subdir(context))
+ INVOKING_USER.mkdir(context.config.package_state_dir_or_default() / dnf_subdir(context))
config = context.pkgmngr / "etc/dnf/dnf.conf"
from mkosi.run import run
from mkosi.sandbox import apivfs_cmd
from mkosi.types import PathString
+from mkosi.user import INVOKING_USER
from mkosi.util import sort_packages, umask
from mkosi.versioncomp import GenericVersion
with umask(~0o755):
(context.root / "var/lib/pacman").mkdir(exist_ok=True, parents=True)
- (context.cache_dir / "cache/pacman/pkg").mkdir(parents=True, exist_ok=True)
+ INVOKING_USER.mkdir(context.config.package_cache_dir_or_default() / "pacman/pkg")
config = context.pkgmngr / "etc/pacman.conf"
if config.exists():
from mkosi.run import run
from mkosi.sandbox import apivfs_cmd
from mkosi.types import PathString
+from mkosi.user import INVOKING_USER
from mkosi.util import sort_packages
config = context.pkgmngr / "etc/zypp/zypp.conf"
config.parent.mkdir(exist_ok=True, parents=True)
- (context.cache_dir / "cache/zypp").mkdir(exist_ok=True, parents=True)
+ INVOKING_USER.mkdir(context.config.package_cache_dir_or_default() / "zypp")
# rpm.install.excludedocs can only be configured in zypp.conf so we append
# to any user provided config file. Let's also bump the refresh delay to
`CacheDirectory=`, `--cache-dir=`
-: Takes a path to a directory to use as package cache for the
- distribution package manager used. If this option is not used, but a
- `mkosi.cache/` directory is found in the local directory it is
- automatically used for this purpose.
+: Takes a path to a directory to use as the incremental cache directory
+ for the incremental images produced when the `Incremental=` option is
+ enabled. If this option is not used, but a `mkosi.cache/` directory is
+ found in the local directory it is automatically used for this
+ purpose.
+
+`PackageCacheDirectory=`, `--package-cache-dir`
+
+: Takes a path to a directory to use as the package cache directory for
+ the distribution package manager used. If unset, a suitable directory
+ in the user's home directory or system is used.
`BuildDirectory=`, `--build-dir=`
from pathlib import Path
from mkosi.log import die
-from mkosi.run import spawn
+from mkosi.run import run, spawn
from mkosi.util import flock
SUBRANGE = 65536
def home(cls) -> Path:
return Path(f"~{cls.name()}").expanduser()
+ @classmethod
+ def is_regular_user(cls) -> bool:
+ return cls.uid >= 1000
+
+ @classmethod
+ def cache_dir(cls) -> Path:
+ if (cache := os.getenv("XDG_CACHE_HOME") or (cache := os.getenv("CACHE_DIRECTORY"))):
+ return Path(cache)
+
+ if (cls.is_regular_user() and Path.cwd().is_relative_to(INVOKING_USER.home())):
+ return INVOKING_USER.home() / ".cache"
+
+ return Path("/var/cache")
+
+ @classmethod
+ def state_dir(cls) -> Path:
+ if (state := os.getenv("XDG_STATE_HOME") or (state := os.getenv("STATE_DIRECTORY"))):
+ return Path(state)
+
+ if (cls.is_regular_user() and Path.cwd().is_relative_to(INVOKING_USER.home())):
+ return INVOKING_USER.home() / ".local/state"
+
+ return Path("/var/lib")
+
+ @classmethod
+ def mkdir(cls, path: Path) -> None:
+ user = cls.uid if cls.is_regular_user() and path.is_relative_to(cls.home()) else os.getuid()
+ group = cls.gid if cls.is_regular_user() and path.is_relative_to(cls.home()) else os.getgid()
+ run(["mkdir", "--parents", path], user=user, group=group)
+
+ @classmethod
+ def rchown(cls, path: Path) -> None:
+ if cls.is_regular_user() and path.is_relative_to(INVOKING_USER.home()) and path.exists():
+ run(["chown", "--recursive", f"{INVOKING_USER.uid}:{INVOKING_USER.gid}", path])
+
def read_subrange(path: Path) -> int:
uid = str(os.getuid())
"Output": "outfile",
"OutputDirectory": "/your/output/here",
"Overlay": true,
+ "PackageCacheDirectory": "/a/b/c",
"PackageDirectories": [],
"PackageManagerTrees": [
{
output_dir = Path("/your/output/here"),
output_format = OutputFormat.uki,
overlay = True,
+ package_cache_dir = Path("/a/b/c"),
package_directories = [],
package_manager_trees = [ConfigTree(Path("/foo/bar"), None)],
packages = [],