1
0
Fork 1
mirror of https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git synced 2025-07-13 11:29:46 +03:00
pmbootstrap/pmb/chroot/init.py
Casey Connolly 9f8edf539d treewide: split chroots from workdir
Introduce a new "cache" subdirectory in the pmbootstrap workdir, all the
cache and config bits go in here, anything that needs to be accessible
from inside a chroot. The whole dir is then bind-mounted into the chroot
as /cache with appropriate symlinks.

This dir is in the config as config.cache.

In addition, all the cache_* and other config dirs are renamed to
be closer to the names of the equivalent dirs in the chroot (e.g.
abuild-config) and to avoid redundant naming since they are now under a
"cache" dir.

Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
2025-07-11 19:36:23 +02:00

191 lines
6.5 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import enum
import filecmp
from pmb.meta import Cache
from pmb.helpers import logging
import os
import pmb.chroot
import pmb.config
import pmb.config.workdir
import pmb.helpers.apk_static
import pmb.helpers.apk
import pmb.helpers.repo
import pmb.helpers.run
from pmb.core import Chroot, ChrootType
from pmb.core.context import get_context
from pmb.types import PathString
class UsrMerge(enum.Enum):
"""
Merge /usr while initializing chroot.
https://systemd.io/THE_CASE_FOR_THE_USR_MERGE/
"""
AUTO = 0
ON = 1
OFF = 2
def copy_resolv_conf(chroot: Chroot) -> None:
"""
Use pythons super fast file compare function (due to caching)
and copy the /etc/resolv.conf to the chroot, in case it is
different from the host.
If the file doesn't exist, create an empty file with 'touch'.
"""
host = "/etc/resolv.conf"
resolv_path = chroot / host
if os.path.exists(host):
if not resolv_path.exists() or not filecmp.cmp(host, resolv_path):
pmb.helpers.run.root(["cp", host, resolv_path])
else:
pmb.helpers.run.root(["touch", resolv_path])
def mark_in_chroot(chroot: Chroot = Chroot.native()) -> None:
"""
Touch a flag so we can know when we're running in chroot (and
don't accidentally flash partitions on our host). This marker
gets removed in pmb.chroot.shutdown (pmbootstrap shutdown).
"""
in_chroot_file = chroot / "in-pmbootstrap"
if not in_chroot_file.exists():
pmb.helpers.run.root(["touch", in_chroot_file])
def init_keys(chroot: Chroot) -> None:
"""
All Alpine and postmarketOS repository keys are shipped with pmbootstrap.
Copy them into $WORK/keys, which gets mounted inside the various
chroots as /etc/apk/keys.
This is done before installing any package, so apk can verify APKINDEX
files of binary repositories even though alpine-keys/postmarketos-keys are
not installed yet.
"""
target = chroot / "etc/apk/keys/"
target.mkdir(exist_ok=True, parents=True)
for key in pmb.config.apk_keys_path.glob("*.pub"):
shutil.copy(key, target)
for key in (get_context().config.cache / "keys").glob("*.pub"):
shutil.copy(key, target)
def init_usr_merge(chroot: Chroot) -> None:
logging.info(f"({chroot}) merge /usr")
script = f"{pmb.config.pmb_src}/pmb/data/merge-usr.sh"
pmb.helpers.run.root(["sh", "-e", script, "CALLED_FROM_PMB", chroot.path])
@Cache()
def warn_if_chroots_outdated() -> None:
outdated = pmb.config.workdir.chroots_outdated()
if outdated:
days_warn = int(pmb.config.chroot_outdated / 3600 / 24)
msg = ""
if Chroot.native() in outdated:
msg += "your native"
if Chroot.rootfs(get_context().config.device) in outdated:
msg += " and rootfs chroots are"
else:
msg += " chroot is"
elif Chroot.rootfs(get_context().config.device) in outdated:
msg += "your rootfs chroot is"
else:
msg += "some of your chroots are"
logging.warning(
f"WARNING: {msg} older than {days_warn} days. Consider running 'pmbootstrap zap'."
)
def setup_cache_path(chroot: Chroot):
# Set up the apk cache to point to the working cache
cache_target = chroot / "etc/apk/cache"
if not cache_target.is_symlink():
cache_target.symlink_to(f"/cache/apk_{chroot.arch}")
@Cache("chroot")
def init(chroot: Chroot, usr_merge: UsrMerge = UsrMerge.AUTO) -> None:
"""
Initialize a chroot by copying the resolv.conf and updating
/etc/apk/repositories. If /bin/sh is missing, create the chroot from
scratch.
:param usr_merge: set to ON to force having a merged /usr. With AUTO it is
only done if the user chose to install systemd in
pmbootstrap init.
"""
# When already initialized: just prepare the chroot
arch = chroot.arch
# We plan to ship systemd with split /usr until the /usr merge is complete
# in Alpine. Let's not drop all our code yet but just forcefully disable
# it.
usr_merge = UsrMerge.OFF
config = get_context().config
# If the channel is wrong and the user has auto_zap_misconfigured_chroots
# enabled, zap the chroot and reinitialize it
if chroot.exists():
zap = pmb.config.workdir.chroot_check_channel(chroot)
if zap:
pmb.chroot.del_chroot(chroot.path, confirm=False)
pmb.config.workdir.clean()
pmb.chroot.mount(chroot)
mark_in_chroot(chroot)
if chroot.exists():
pmb.helpers.apk.update_repository_list(chroot.path)
copy_resolv_conf(chroot)
warn_if_chroots_outdated()
setup_cache_path(chroot)
return
# Fetch apk.static
pmb.helpers.apk_static.init()
logging.info(f"({chroot}) Creating chroot")
# Initialize /etc/apk/keys/, resolv.conf, repositories
init_keys(chroot)
# Also creates /etc
pmb.helpers.apk.update_repository_list(chroot.path)
copy_resolv_conf(chroot)
setup_cache_path(chroot)
pmb.config.workdir.chroot_save_init(chroot)
# Install minimal amount of things to get a functional chroot.
# We don't use alpine-base since it depends on openrc, and neither
# postmarketos-base, since that's quite big (e.g: contains an init system)
pmb.helpers.repo.update(arch)
pkgs = ["alpine-baselayout", "apk-tools", "busybox", "musl-utils"]
cmd: list[PathString] = ["--initdb"]
pmb.helpers.apk.run([*cmd, "add", *pkgs], chroot)
# Merge /usr
if usr_merge is UsrMerge.AUTO and pmb.config.is_systemd_selected(config):
usr_merge = UsrMerge.ON
if usr_merge is UsrMerge.ON:
init_usr_merge(chroot)
# Building chroots: create "pmos" user, add symlinks to /home/pmos
if not chroot.type == ChrootType.ROOTFS:
pmb.chroot.root(
["adduser", "-s", "/bin/sh", "-D", "pmos", "-u", pmb.config.chroot_uid_user], chroot
)
# Create the links (with subfolders if necessary)
for src_template, link_name in pmb.config.chroot_home_symlinks.items():
target = Path(src_template.replace("$ARCH", str(arch)))
(chroot / link_name).parent.mkdir(exist_ok=True, parents=True)
(chroot / target).mkdir(exist_ok=True, parents=True)
(chroot / link_name).symlink_to(target, target_is_directory=True)
shutil.chown(
chroot / target, int(pmb.config.chroot_uid_user), int(pmb.config.chroot_uid_user)
)