forked from Mirror/pmbootstrap
This is needed to bring up the v24.06 repositories at
build.postmarketos.org. With the latest apk version, apk refuses to
operate if an URL from /etc/apk/repositories cannot be fetched.
Before the repositories are created for the first time, they do not
exist, so we will just set PMB_APK_FORCE_MISSING_REPOSITORIES=1 in bpo
to be not blocked here.
I've also spent significant time on alternative implementations, but
they have problems:
- Let bpo create an empty APKINDEX before building the first package,
but this was a larger code change, leading to lots of adjustments in
the tests, and ultimately it seems it didn't work properly (it seems
apk/abuild doesn't create a valid signed APKINDEX for one that has no
packages).
- Do not set the --mirror-pmOS argument for the "final" repository, only
the "wip" repository, until the "final" repository is available for
the first time. This works fine for x86_64, but not for foreign arch
repositories because then the cross compilers from the x86_64
repository are not available. I've also tried to make a different env
var that ensures we don't write the non-existing repository to
/etc/apk/repositories from within pmbootstrap if initializing a
foreign arch chroot, but then we would find a sane way to do this only
for the "final" repository and not for the "wip" repository which
leads to a lot more complexity than this patch.
So this is not the nicest solution (apk still tries to fetch the indexes
and gets a 404), but it is the simplest one and unblocks us from working
on v24.06. Also it doesn't add more complexity which is important in the
middle of the feature freeze we are currently in.
Related: bpo issue 137
Related: d76213e643
Related: https://postmarketos.org/blog/2024/05/19/pmOS-update-2024-05/#pmbootstrap-230-and-feature-freeze
192 lines
6.9 KiB
Python
192 lines
6.9 KiB
Python
# Copyright 2023 Oliver Smith
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
import enum
|
|
import filecmp
|
|
import glob
|
|
import logging
|
|
import os
|
|
|
|
import pmb.chroot
|
|
import pmb.chroot.apk_static
|
|
import pmb.config
|
|
import pmb.config.workdir
|
|
import pmb.helpers.repo
|
|
import pmb.helpers.run
|
|
import pmb.parse.arch
|
|
|
|
cache_chroot_is_outdated = []
|
|
|
|
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(args, suffix="native"):
|
|
"""
|
|
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"
|
|
chroot = f"{args.work}/chroot_{suffix}{host}"
|
|
if os.path.exists(host):
|
|
if not os.path.exists(chroot) or not filecmp.cmp(host, chroot):
|
|
pmb.helpers.run.root(args, ["cp", host, chroot])
|
|
else:
|
|
pmb.helpers.run.root(args, ["touch", chroot])
|
|
|
|
|
|
def mark_in_chroot(args, suffix="native"):
|
|
"""
|
|
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 = f"{args.work}/chroot_{suffix}/in-pmbootstrap"
|
|
if not os.path.exists(in_chroot_file):
|
|
pmb.helpers.run.root(args, ["touch", in_chroot_file])
|
|
|
|
|
|
def setup_qemu_emulation(args, suffix):
|
|
arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
|
|
if not pmb.parse.arch.cpu_emulation_required(arch):
|
|
return
|
|
|
|
chroot = f"{args.work}/chroot_{suffix}"
|
|
arch_qemu = pmb.parse.arch.alpine_to_qemu(arch)
|
|
|
|
# mount --bind the qemu-user binary
|
|
pmb.chroot.binfmt.register(args, arch)
|
|
pmb.helpers.mount.bind_file(args, f"{args.work}/chroot_native"
|
|
f"/usr/bin/qemu-{arch_qemu}",
|
|
f"{chroot}/usr/bin/qemu-{arch_qemu}-static",
|
|
create_folders=True)
|
|
|
|
|
|
def init_keys(args):
|
|
"""
|
|
All Alpine and postmarketOS repository keys are shipped with pmbootstrap.
|
|
Copy them into $WORK/config_apk_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.
|
|
"""
|
|
for key in glob.glob(f"{pmb.config.apk_keys_path}/*.pub"):
|
|
target = f"{args.work}/config_apk_keys/{os.path.basename(key)}"
|
|
if not os.path.exists(target):
|
|
# Copy as root, so the resulting files in chroots are owned by root
|
|
pmb.helpers.run.root(args, ["cp", key, target])
|
|
|
|
|
|
def init_usr_merge(args, suffix):
|
|
logging.info(f"({suffix}) merge /usr")
|
|
script = f"{pmb.config.pmb_src}/pmb/data/merge-usr.sh"
|
|
pmb.helpers.run.root(args, ["sh", "-e", script, "CALLED_FROM_PMB",
|
|
f"{args.work}/chroot_{suffix}"])
|
|
|
|
|
|
def warn_if_chroot_is_outdated(args, suffix):
|
|
global cache_chroot_is_outdated
|
|
|
|
# Only check / display the warning once per session
|
|
if suffix in cache_chroot_is_outdated:
|
|
return
|
|
|
|
if pmb.config.workdir.chroots_outdated(args, suffix):
|
|
days_warn = int(pmb.config.chroot_outdated / 3600 / 24)
|
|
logging.warning(f"WARNING: Your {suffix} chroot is older than"
|
|
f" {days_warn} days. Consider running"
|
|
" 'pmbootstrap zap'.")
|
|
|
|
cache_chroot_is_outdated += [suffix]
|
|
|
|
|
|
def init(args, suffix="native", usr_merge=UsrMerge.AUTO,
|
|
postmarketos_mirror=True):
|
|
"""
|
|
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.
|
|
:param postmarketos_mirror: add postmarketos mirror URLs
|
|
"""
|
|
# When already initialized: just prepare the chroot
|
|
chroot = f"{args.work}/chroot_{suffix}"
|
|
arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
|
|
|
|
pmb.chroot.mount(args, suffix)
|
|
setup_qemu_emulation(args, suffix)
|
|
mark_in_chroot(args, suffix)
|
|
if os.path.islink(f"{chroot}/bin/sh"):
|
|
pmb.config.workdir.chroot_check_channel(args, suffix)
|
|
copy_resolv_conf(args, suffix)
|
|
pmb.chroot.apk.update_repository_list(args, suffix, postmarketos_mirror)
|
|
warn_if_chroot_is_outdated(args, suffix)
|
|
return
|
|
|
|
# Require apk-tools-static
|
|
pmb.chroot.apk_static.init(args)
|
|
|
|
logging.info(f"({suffix}) install alpine-base")
|
|
|
|
# Initialize cache
|
|
apk_cache = f"{args.work}/cache_apk_{arch}"
|
|
pmb.helpers.run.root(args, ["ln", "-s", "-f", "/var/cache/apk",
|
|
f"{chroot}/etc/apk/cache"])
|
|
|
|
# Initialize /etc/apk/keys/, resolv.conf, repositories
|
|
init_keys(args)
|
|
copy_resolv_conf(args, suffix)
|
|
pmb.chroot.apk.update_repository_list(args, suffix, postmarketos_mirror)
|
|
|
|
pmb.config.workdir.chroot_save_init(args, suffix)
|
|
|
|
# Install alpine-base
|
|
pmb.helpers.repo.update(args, arch)
|
|
pmb.chroot.apk_static.run(args, ["--root", chroot,
|
|
"--cache-dir", apk_cache,
|
|
"--initdb", "--arch", arch,
|
|
"add", "alpine-base"])
|
|
|
|
# Building chroots: create "pmos" user, add symlinks to /home/pmos
|
|
if not suffix.startswith("rootfs_"):
|
|
pmb.chroot.root(args, ["adduser", "-D", "pmos", "-u",
|
|
pmb.config.chroot_uid_user],
|
|
suffix, auto_init=False)
|
|
|
|
# Create the links (with subfolders if necessary)
|
|
for target, link_name in pmb.config.chroot_home_symlinks.items():
|
|
link_dir = os.path.dirname(link_name)
|
|
if not os.path.exists(f"{chroot}{link_dir}"):
|
|
pmb.chroot.user(args, ["mkdir", "-p", link_dir], suffix)
|
|
if not os.path.exists(f"{chroot}{target}"):
|
|
pmb.chroot.root(args, ["mkdir", "-p", target], suffix)
|
|
pmb.chroot.user(args, ["ln", "-s", target, link_name], suffix)
|
|
pmb.chroot.root(args, ["chown", "pmos:pmos", target], suffix)
|
|
|
|
# Merge /usr
|
|
if usr_merge is UsrMerge.AUTO and pmb.config.is_systemd_selected(args):
|
|
usr_merge = UsrMerge.ON
|
|
if usr_merge is UsrMerge.ON:
|
|
init_usr_merge(args, suffix)
|
|
|
|
# Upgrade packages in the chroot, in case alpine-base, apk, etc. have been
|
|
# built from source with pmbootstrap
|
|
command = ["--no-network", "upgrade", "-a"]
|
|
|
|
# Ignore missing repos before initial build (bpo#137)
|
|
if os.getenv("PMB_APK_FORCE_MISSING_REPOSITORIES") == 1:
|
|
command = ["--force-missing-repositories"] + command
|
|
|
|
pmb.chroot.root(args, ["apk"] + command, suffix)
|