Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --installroot #1308

Open
polarathene opened this issue Dec 28, 2024 · 3 comments
Open

Support --installroot #1308

polarathene opened this issue Dec 28, 2024 · 3 comments

Comments

@polarathene
Copy link

polarathene commented Dec 28, 2024

I don't expect much progress addressing this request as the feature is probably quite niche for users of Paru.

Feel free to close this issue. I have detailed everything below as a form of documentation, not all of it was straight-forward to troubleshoot, so it may help others.

The --chroot feature seems like the wrong approach, even if it's bugs were resolved. paru --root is roughly equivalent and works, so long as there is no buildtime deps for an AUR package.

Additionally, Arch's packages aren't exactly tailored to be minimal in size (glibc is 50MB, mainly excess in /usr/{include,lib,share}), due to packages not being split as much or having -dev / -devel variants for headers (I am aware that pacman.conf can take a similar approach to the chisel tool but manually via declaring NoExtract patterns, although excluding /usr/include/* this way doesn't work if you have buildtime deps).


Details

This feature request is similar to an attempt by a user described here with --root.

The intent is to install packages to a new rootfs location without any package manager or other packages that weren't part of the package resolution.

That rootfs could then be used as a minimal container image for running a service:

FROM fedora:41 AS rootfs
RUN <<HEREDOC
  dnf5 --installroot /root-fs --use-host-config --setopt=install_weak_deps=0 \
    install -y glibc
  dnf5 --installroot /root-fs clean all
HEREDOC

FROM scratch
ARG TARGETARCH
COPY --from=rootfs /root-fs /
COPY --from=builder /build/app-${TARGETARCH} /app
ENTRYPOINT ["/app"]

As I've detailed below pacman seems to provide similar functionality with --root, although this needs some extra workarounds with Paru for AUR packages.

Paru could improve:

  1. The UX with --root by automating the preparation required to use --root.
  2. AUR packages requiring a build step could potentially work without issue if they installed missing packages on the system root instead (since these are distinct via makedepends?), there's already --removemake which I assume could be paired with it.
  3. The --chroot feature is broken, it seems necessary to run chmod 644 /var/lib/aurbuild/x86_64/root/etc/pacman.conf to leverage it (only seems to be used with AUR packages). If it was also intended to be a clean root environment each time, that doesn't seem to be the case (see the cado-nfs-git notes below).

Reference - Equivalent feature support in other package managers

In Fedora (dnf) and OpenSUSE (zypper), these RPM package managers support this feature via --installroot:

$ docker run --rm -it fedora:41

# 15.7 MB - Fedora 41:
# NOTE: 7.48 MB if manually stripping away redundant content
$ dnf --installroot /rootfs --use-host-config \
  install -y --setopt=install_weak_deps=0 glibc

# Unlike with OpenSUSE, DNF related cache exists at the install root (approx 50MB), strip it away:
$ dnf --installroot /rootfs --use-host-config clean all
$ docker run --rm -it registry.suse.com/bci/bci-base

# 10.7 MB - OpenSUSE Leap 15.6 (Sep 2024):
$ zypper --releasever 15.6 --installroot /rootfs --gpg-auto-import-keys \
  install -y --no-recommends glibc

One disadvantage with Zypper is due to some default security constraints to container environments (reduced capabilities/privilege for the root user than the actual root user on the host), the way it installs packages with this feature doesn't always go smoothly. DNF doesn't have this issue AFAIK.

Canonical has been working on a similar tool chisel, for their DEB packages that is also focused on the bare minimum package contents. Fedora and OpenSUSE with the --installroot approach for Python 3.12 instead would bring in glibc and other necessary deps and weighs in at 80-100MB. For comparison chisel is 27MB:

# 27MB Any distro running the chisel CLI tool:
# NOTE: The `_standard` variant is 40MB

# Chisel presently does not make the location if it doesn't yet exist:
$ mkdir /rootfs

$ chisel cut --release ubuntu-24.04 --root /rootfs python3.12_core

nix can sort of do the same, but doesn't play as well in a container environment with maintainers refusing to support a --installroot equivalent feature (instead deferring to their internal image builder tooling, rather than creating an image via nix within a container).

@polarathene
Copy link
Author

polarathene commented Dec 28, 2024

Attempts with Paru

Preparation is a bit more involved vs earlier alternative tooling mentioned, but this is mostly specific to pacman + AUR rather than paru itself:

# `base-devel` + `git` packages needed for AUR support:
$ pacman --noconfirm -Syu git >/dev/null

# Installing AUR packages as root is forbidden, create a user as a workaround:
$ useradd --system --create-home --shell /usr/bin/nologin automated

# Grant passwordless sudo access for the `automated` user:
# NOTE: Alternatively only permit passwordless for the commands used:
# - Basic support: `NOPASSWD: /usr/bin/pacman`
# - `--chroot` support: `NOPASSWD: ^/usr/bin/(pacman|install|mkarchroot|cp|arch-nspawn)$`
$ printf 'automated ALL=(ALL) NOPASSWD: ALL\n' | tee /etc/sudoers.d/50-passwordless

# Get Paru (quicker than manually building):
$ PARU_URL=https://github.com/Morganamilo/paru/releases/download/v2.0.4/paru-v2.0.4-x86_64.tar.zst \
  && curl -fsSL "${PARU_URL}" \
  | tar --extract --zstd --directory /usr/local/bin paru

# Equivalent to the current `paru-bin` AUR package:
$ paru --version
paru v2.0.4 - libalpm v15.0.0

# NOTE: Installing a package to `--root` requires pacman DB initialized:
$ mkdir -p /rootfs/var/lib/pacman/ && pacman --root /rootfs -Sy

--root can then be used, with paru run as a non-root user:

sudo --user automated paru --root /rootfs --noconfirm -S vscodium-bin

This doesn't quite work though, TLDR is:

  • --root can work as intended when paired with --nodeps to workaround dependency checks mistakenly checking at the system root instead of --root.
    • That's apparently an issue for pacman to improve on rather than paru?
    • Not sufficient for AUR packages that have a build step.
  • --chroot is dependent upon systemd compatibility which isn't always an option in containers like Docker.
    • Even with compatibility it requires relaxing security of the container to add required privileges.
    • This feature supposedly would fix the the build step failure with --root usage (EDIT: Confirmed).
    • While Podman can use it's systemd integration support to leverage --chroot, it'd not be usable in a Dockerfile to my knowledge given the process detailed below.

With paru --root /rootfs

Pacman has --root which does install the packages to the rootfs location provided (although it needs a little more of helping hand than the earlier mentioned chisel tool does):

$ sudo --user automated paru --root /rootfs --noconfirm -S cado-nfs-git
error: failed to initialize alpm: root=/rootfs dbpath=/rootfs/var/lib/pacman/: could not find or read directory

$ mkdir -p /rootfs/var/lib/pacman
$ sudo --user automated paru --root /rootfs --noconfirm -S cado-nfs-git
error: failed to initialize alpm: root=/rootfs dbpath=/rootfs/var/lib/pacman/: could not create database

# DB initialization must be performed as root,
# thus technically this UX issue is due to pacman not paru:
$ pacman --root /rootfs --noconfirm -Sy

The problem is while the packages do actually get installed to the provided /rootfs location, the dependency check steps during installation for AUR packages fail.

Despite paru having installed the package deps at the provided --root location, it seems the package deps are failing dependency checks as they're mistakenly expected to be installed at the system root instead?:

# Package ref: https://aur.archlinux.org/packages/vscodium-bin
$ sudo --user automated paru --root /rootfs --noconfirm -S vscodium-bin

# ...

==> Making package: vscodium-bin 1.96.0.24347-1 (Tue Dec 17 22:21:12 2024)
==> Checking runtime dependencies...
==> Missing dependencies:
  -> fontconfig
  -> libxtst
  -> gtk3
  -> python
  -> cairo
  -> alsa-lib
  -> nss
  -> libnotify
  -> libxss
==> Checking buildtime dependencies...
==> ERROR: Could not resolve all dependencies.
error: failed to build 'vscodium-bin-1.96.0.24347-1'

# However these dependencies are installed at the `/rootfs` location:
$ pacman --root /rootfs --noconfirm -S --needed --asdeps \
  fontconfig libxtst gtk3 python cairo alsa-lib nss libnotify

warning: fontconfig-2:2.15.0-2 is up to date -- skipping
warning: libxtst-1.2.5-1 is up to date -- skipping
warning: gtk3-1:3.24.43-4 is up to date -- skipping
warning: python-3.12.7-1 is up to date -- skipping
warning: cairo-1.18.2-2 is up to date -- skipping
warning: alsa-lib-1.2.13-1 is up to date -- skipping
warning: nss-3.107-1 is up to date -- skipping
warning: libnotify-0.8.3-1 is up to date -- skipping
 there is nothing to do

While --nodeps seems to be a viable workaround, it's insufficient for packages that declare buildtime dependencies (example reference: cado-nfs-git):

$ sudo --user automated paru --root /rootfs --noconfirm -S cado-nfs-git

==> Making package: cado-nfs-git 20241217.f185219fd-1 (Tue Dec 17 22:35:52 2024)
==> WARNING: Skipping dependency checks.
==> WARNING: Using existing $srcdir/ tree
==> Starting pkgver()...
==> Starting build()...
./scripts/build_environment.sh: line 65: hostname: command not found
CMake not found
No cmake binary was found on your system.

make: *** [Makefile:10: cmake] Error 1
==> ERROR: A failure occurred in build().
    Aborting...
error: failed to build 'cado-nfs-git-20240318.a24829267-1'

The package builds with call_cmake.sh calling build_environment.sh, which attempts to call cmake, which in this case is not present on the system root (or rather via PATH env?) so it fails to run.

It's probably safe to assume that this issue would happen with most packages attempting to build with commands from packages not installed at the system root?

I was able to make some progress working around that, but not enough (it's unrelated to packages at the /rootfs location, as even after adding basel-devel git the build will still encounter the failure shown below):

# In addition to needing to modify PATH for /rootfs discovery,
# LD_LIBRARY_PATH will also be necessary to resolve the correct libraries:
ldd /rootfs/usr/bin/cmake | grep 'not found'
        libjsoncpp.so.26 => not found
        librhash.so.1 => not found
        libuv.so.1 => not found
        libcppdap.so => not found

# Adjust ENV to prepend PATH with precedence to `/rootfs` equivalents:
sudo --user automated bash -c '\
  export LD_LIBRARY_PATH=/rootfs/usr/lib PATH="/rootfs$(echo "${PATH}" | sed "s|:|:/rootfs|g"):${PATH}"; \
  paru --root /rootfs --noconfirm -S --nodeps cado-nfs-git'

# ...

[ 21%] Building CXX object sieve/CMakeFiles/las_core_b.dir/las-memory.cpp.o
/home/automated/.cache/paru/clone/cado-nfs-git/src/cado-nfs/sieve/las-memory.cpp: In destructor ‘las_memory_accessor::~las_memory_accessor()’:
/home/automated/.cache/paru/clone/cado-nfs-git/src/cado-nfs/sieve/las-memory.cpp:31:40: error: ‘free_aligned’ was not declared in this scope
   31 |     for(auto x : large_pages_for_pool) free_aligned(x);
      |                                        ^~~~~~~~~~~~
/home/automated/.cache/paru/clone/cado-nfs-git/src/cado-nfs/sieve/las-memory.cpp: In member function ‘void* las_memory_accessor::alloc_frequent_size(size_t)’:
/home/automated/.cache/paru/clone/cado-nfs-git/src/cado-nfs/sieve/las-memory.cpp:38:16: error: ‘malloc_aligned’ was not declared in this scope
   38 |         return malloc_aligned(size, 128);
      |                ^~~~~~~~~~~~~~

make[2]: *** [sieve/CMakeFiles/las_core_b.dir/build.make:93: sieve/CMakeFiles/las_core_b.dir/las-memory.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:2811: sieve/CMakeFiles/las_core_b.dir/all] Error 2
make: *** [Makefile:146: all] Error 2
make: *** [Makefile:7: all] Error 2
==> ERROR: A failure occurred in build().
    Aborting...
error: failed to build 'cado-nfs-git-20240318.a24829267-1'

Since those are buildtime deps only, I guess a better solution would have those packages installed at the system root instead of at the custom --root location? I don't think there is a flag for that though?

@polarathene
Copy link
Author

With paru --chroot

The paru --chroot feature might be a step in the right direction? Unfortunately it doesn't seem as compatible when choosing to run paru from within a container? (the command to prepare the chroot doesn't seem customizable?):

paru/src/chroot.rs

Lines 82 to 88 in d0329e3

let mut cmd = Command::new(&self.sudo);
cmd.arg("arch-nspawn")
.arg("-C")
.arg(tmp.path())
.arg("-M")
.arg(&self.makepkg_conf)
.arg(dir);

I presently don't have access to an Arch Linux host or guest (other than via a container), but presumably that feature may handle the buildtime deps failure shown in the prior comment with paru --root? (assuming these two options are compatible together?).

This is how --chroot fails in the container (host is Fedora 40 with Podman):

# Requires `mkarchroot` provided via `devtools` package:
$ sudo --user automated paru --chroot --noconfirm -S vscodium-bin >/dev/null
error: can not use chroot builds: devtools is not installed
$ pacman --noconfirm -Syu devtools >/dev/null

# 1st time:
$ sudo --user automated paru --chroot --noconfirm -S vscodium-bin >/dev/null

unshare: unshare failed: Operation not permitted
==> ERROR: Failed to install all packages
error: failed to run: sudo mkarchroot -C /tmp/.tmpIzQydV -M /etc/makepkg.conf /var/lib/aurbuild/x86_64/root base-devel

The error differs when run again:

# 2nd time:
$ sudo --user automated paru --chroot --noconfirm -S vscodium-bin >/dev/null

cp: cannot create directory '/var/lib/aurbuild/x86_64/root/var/lib/pacman/sync': No such file or directory
==> ERROR: '/var/lib/aurbuild/x86_64/root' does not appear to be an Arch chroot.
error: failed to run: sudo arch-nspawn -C /tmp/.tmpW3MrW2 -M /etc/makepkg.conf /var/lib/aurbuild/x86_64/root --bind /var/cache/pacman/pkg/ pacman -Syu --noconfirm

Even when /var/lib/aurbuild/x86_64/root/var/lib/pacman/sync is created it still fails detection as an "Arch chroot":

# Again but with the directory created:
$ mkdir -p /var/lib/aurbuild/x86_64/root/var/lib/pacman/sync
$ sudo --user automated paru --chroot --noconfirm -S vscodium-bin >/dev/null

==> ERROR: '/var/lib/aurbuild/x86_64/root' does not appear to be an Arch chroot.
error: failed to run: sudo arch-nspawn -C /tmp/.tmpFbpgmJ -M /etc/makepkg.conf /var/lib/aurbuild/x86_64/root --bind /var/cache/pacman/pkg/ pacman -Syu --noconfirm

Since this would later defer arch-nspawn to calling systemd-nspawn (Format: arch-nspawn <args> <chroot dir> <systemd-nspawn args>) and the container is not running systemd as PID 1 (not really viable for Docker AFAIK?), this approach for --chroot is likely incompatible.

paru --chroot troubleshooting notes

arch-nspawn seems to rely on an "Arch chroot" being created (via mkarchroot guide, mkarchroot source) which when run manually also fails, as unshare --mount requires granting the non-default capability CAP_SYS_ADMIN:

$ podman run --rm -it \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && mkdir $CHROOT && mkarchroot $CHROOT/root glibc >/devnull

error: restricting filesystem access failed because the landlock ruleset could not be applied!

unshare: unshare failed: Operation not permitted
==> ERROR: Failed to install all packages

Usage of mount seems to be restricted (even when the required CAP_SYS_ADMIN was already granted). AFAIK when capabilities alone are insufficient the cause tends to be security mechanisms like:

In this case the failure appears to be due to SELinux, which would need --security-opt label:disable to opt-out (a similar solution for AppArmor may also require opt-out of seccomp?):

# With `SYS_ADMIN` capability added

$ podman run --rm -it \
  --cap-add SYS_ADMIN \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && mkdir $CHROOT && mkarchroot $CHROOT/root glibc >/devnull

==> Creating install root at /tmp/rootfs/root
mount: /tmp/rootfs/root/proc: cannot mount proc read-only.
       dmesg(1) may have more information after failed mount system call.
==> ERROR: failed to setup chroot /tmp/rootfs/root
==> ERROR: Failed to install all packages

Now the problem is missing pacman.conf and makepkg.conf which can be copied via supported args to mkarchroot:

# With `--security-opt` added

$ podman run --rm -it \
  --cap-add SYS_ADMIN \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && mkdir $CHROOT && mkarchroot $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

error: config file /tmp/rootfs/root/etc/pacman.conf could not be read: No such file or directory
error parsing '/tmp/rootfs/root/etc/pacman.conf'
error: config file /tmp/rootfs/root/etc/pacman.conf could not be read: No such file or directory
error parsing '/tmp/rootfs/root/etc/pacman.conf'
sed: can't read /tmp/rootfs/root/etc/pacman.conf: No such file or directory
grep: /tmp/rootfs/root/etc/makepkg.conf: No such file or directory

Failed to parse --bind(-ro)= argument : Invalid argument

Also needs CAP_AUDIT_CONTROL:

# With `mkarchroot -C path -M path` added

$ podman run --rm -it \
  --cap-add SYS_ADMIN \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && mkdir $CHROOT && mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

Failed to reset audit login UID. This probably means that your kernel is too
old and you have audit enabled. Note that the auditing subsystem is known to
be incompatible with containers on old kernels. Please make sure to upgrade
your kernel or to off auditing with 'audit=0' on the kernel command line before
using systemd-nspawn. Sleeping for 5s... (Operation not permitted)

Failed to allocate scope: Unit devtools-.slice failed to load properly, please adjust/correct and reload service manager: Invalid argument
Attempted to remove disk file system under "/run/systemd/nspawn/propagate/arch-nspawn-346", and we can't allow that.

# Regarding the `systemd-nspawn` audit error,
# The kernel is not old.. This error is actually from missing `CAP_AUDIT_CONTROL`.
$ uname -a
Linux 375bc7bfc33e 6.10.6-200.fc40.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Aug 19 14:09:30 UTC 2024 x86_64 GNU/Linux

dbus-uuidgen is needed to populate /etc/machine-id? (the Arch chroot generates it's own /etc/machine-id regardless):

# With `AUDIT_CONTROL` capability added

$ podman run --rm -it \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && mkdir $CHROOT && mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

Failed to retrieve machine ID: No such file or directory
Attempted to remove disk file system under "/run/systemd/nspawn/propagate/arch-nspawn-311", and we can't allow that.

$ cat /etc/machine-id
cat: /etc/machine-id: No such file or directory

Now we have a failure related to interacting via DBUS:

# With `dbus-uuidgen` added

$ podman run --rm -it \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && dbus-uuidgen --ensure=/etc/machine-id \
  && mkdir $CHROOT && mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

Failed to open bus: No such file or directory
Attempted to remove disk file system under "/run/systemd/nspawn/propagate/arch-nspawn-454", and we can't allow that.

# DBUS socket was created, but DBUS is not running:
$ ls /run/dbus
containers

$ ps -ax | grep dbus
    350 pts/0    S+     0:00 grep dbus

# DBUS on the host system:
$ exit
$ ps -ax | grep dbus
    719 ?        Ss     0:40 /usr/bin/dbus-broker-launch --scope system --audit
    726 ?        S     53:54 dbus-broker --log 4 --controller 9 --machine-id 6c8d4e00f69f439e82010ed67591a30f --max-bytes 536870912 --max-fds 4096 --max-matches 131072 --audit
3746378 pts/0    S+     0:00 grep --color=auto dbus

Bind mount the expected socket (NOTE: Bind mounting the entire directory as read-only would prevent the container creating the /run/dbus/containers directory):

# With `--volume` added

$ podman run --rm -it \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && dbus-uuidgen --ensure=/etc/machine-id \
  && mkdir $CHROOT && mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

Failed to allocate scope: Unit devtools-.slice failed to load properly, please adjust/correct and reload service manager: Invalid argument
Attempted to remove disk file system under "/run/systemd/nspawn/propagate/arch-nspawn-597", and we can't allow that.

NOTE: If instead running the container with --volume /run/dbus:/run/dbus:rw, you'll get the same output with an additional error after the Invalid argument line: Parent died too early (even when using the exact same /etc/machine-id as the host).

NOTE: The error "Failed to allocate scope" seems to be reported in this 2017 bug report for devtools arch-nspawn command, regarding the args for systemd-nspawn. However as --keep-unit isn't compatible with slices, modifying the arch-nspawn shell script as they suggest won't help.


This specific snippet from the arch-nspawn script shows the default systemd-nspawn args declared:

# /usr/sbin/arch-nspawn

nspawn_args=(
        --quiet
        --directory="$working_dir"
        --setenv="PATH=/usr/local/sbin:/usr/local/bin:/usr/bin"
        --register=no
        --slice="devtools-$(systemd-escape "${SUDO_USER:-$USER}")"
        --machine="arch-nspawn-$$"
        --as-pid2
        --console=autopipe
        --timezone=off
)

We can see that the --slice arg defining the name devtools-.slice was intended to include a user as a suffix.

Both SUDO_USER and USER environment variables are populated when sudo is called as root (according to the sudoers man page). Since they're obviously not set, either one of those needs to be set in advance or instead leverage using sudo (as one might when using paru).

# With `USER=root` added

$ podman run --rm -it \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro \
  --env CHROOT=/tmp/rootfs \
  archlinux:base-devel bash

$ pacman --noconfirm -Syu devtools >/dev/null \
  && dbus-uuidgen --ensure=/etc/machine-id \
  && mkdir $CHROOT && USER=root mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc >/devnull

Initializing machine ID from random generator.

Failed to get our control group: Protocol driver not attached
Attempted to remove disk file system under "/run/systemd/nspawn/propagate/arch-nspawn-2550", and we can't allow that.

The "Failed to get our control group: Protocol driver not attached" error is from systemd-nspawn. I have no clue how to progress from this point, this is probably as far as you can "easily" get with Docker?

This complication compared to the support from alternative package managers should be demonstrated quite clearly above. Granted this seems to only be an issue for packages that are built, not only installed (which AFAIK is only what the other package managers support).

@polarathene
Copy link
Author

polarathene commented Dec 28, 2024

paru --chroot with Podman using systemd integration

This Red Hat developers blog post (2019) demonstrates how to use this feature.

That advice still works at the end of 2024, but I did not need to run setsebool -P container_manage_cgroup true (even though getsebool container_manage_cgroup returned false on Fedora 40).

# From the linked blog post:
FROM fedora:41
RUN dnf install -y systemd httpd
RUN systemctl enable httpd
CMD ["/sbin/init"]

With Arch systemd is already installed, but successful init will require two extra files to be created:

# Equivalent with ArchLinux:
FROM archlinux:base-devel
# systemd already provided with `base` / `base-devel`,images
# httpd service is via the `apache` package
RUN pacman --noconfirm -Syu apache
RUN systemctl enable httpd
# These files must exist (even if blank) at runtime to avoid an error and prompt:
RUN touch /etc/machine-id /etc/localtime
# Starts systemd as PID 1 (assuming no ENTRYPOINT):
CMD ["/sbin/init"]

Build the image and start it with podman and we can access the web service at port 80, or shell into the container and verify if that helps us continue with mkarchroot:

$ podman build --tag localhost/archlinux:systemd .
# NOTE: If you run this with `-it` instead of `-d` you'll be presented with a login prompt,
# You cannot exit that prompt via input (it'd require another TTY),
# so run the command detached with `-d`.
$ podman run --rm -d \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  --name chroot-test \
  localhost/archlinux:systemd
# Shell into container:
$ podman exec -it chroot-test bash


# Continue with `mkarchroot` attempt to install `glibc` to `/tmp/rootfs`:
# NOTE: No volume mount or `dbus-uuidgen` command required for DBUS now.
$ mkdir $CHROOT
$ USER=root mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root glibc > /dev/null
Initializing machine ID from random generator.
execv(locale-gen) failed: No such file or directory


# Run the command directly, similar to how `mkarchroot` would:
$ USER=root arch-nspawn -C /etc/pacman.conf -M /etc/makepkg.conf $CHROOT/root locale-gen > /dev/null
execv(locale-gen) failed: No such file or directory

locale-gen is documented at the ArchWiki and we can see that it is called in the final lines of the mkarchroot shell script which enforce running locale-gen:

# /usr/sbin/mkarchroot

printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$working_dir/etc/locale.gen"
echo 'LANG=C.UTF-8' > "$working_dir/etc/locale.conf"
echo "$CHROOT_VERSION" > "$working_dir/.arch-chroot"

systemd-machine-id-setup --root="$working_dir"

exec arch-nspawn \
        "${nspawn_args[@]}" \
        "$working_dir" locale-gen

That should be fine as glibc does install this locale-gen, but theres a few caveats for why it's not working:

# Executable file does exist:
$ ls -l /tmp/rootfs/root/usr/bin/locale-gen
-rwxr-xr-x 1 root root 1057 Aug  5 20:00 /tmp/rootfs/root/usr/bin/locale-gen


# It's a shell script (surprise!), not a binary... so a shell is needed!
$ head -n 1 /tmp/rootfs/root/usr/bin/locale-gen
#!/bin/sh


# Add packages to support running `locale-gen`:
# NOTE: `bash` brings in lib-gcc which is rather large.
$ rm -rf /tmp/rootfs && mkdir /tmp/rootfs \
  && USER=root mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf /tmp/rootfs/root \
    glibc bash sed coreutils >/dev/null
warning: dependency cycle detected:
warning: systemd-libs will be installed before its libcap dependency
Initializing machine ID from random generator.
[error] character map file `UTF-8' not found: No such file or directory
[error] default character map file `ANSI_X3.4-1968' not found: No such file or directory


# Those files exist but it turns out that error is actually due to missing gzip:
$ rm -rf /tmp/rootfs && mkdir /tmp/rootfs \
  && USER=root mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf /tmp/rootfs/root \
    glibc bash sed coreutils gzip >/dev/null
warning: dependency cycle detected:
warning: systemd-libs will be installed before its libcap dependency
Initializing machine ID from random generator.
[error] cannot open locale definition file `de_DE': No such file or directory


# The images `/etc/pacman.conf` has `NoExtract` patterns that exclude most locales,
# including German, so remove `de_DE` from the `mkarchroot` script:
$ sed -i 's/en_US de_DE/en_US/' /usr/sbin/mkarchroot
$ rm -rf /tmp/rootfs && mkdir /tmp/rootfs \
  && USER=root mkarchroot -C /etc/pacman.conf -M /etc/makepkg.conf /tmp/rootfs/root \
    glibc bash sed coreutils gzip >/dev/null
warning: dependency cycle detected:
warning: systemd-libs will be installed before its libcap dependency
Initializing machine ID from random generator.

Now that mkarchroot is finally successful with a basic example, we can switch over to paru --chroot:

FROM archlinux:base-devel
# Add paru:
RUN <<EOF
  # Temporary user account for installing AUR package via paru without password input:
  useradd --system --create-home --shell /usr/bin/nologin automated
  printf 'automated ALL=(ALL) NOPASSWD: ALL\n' | tee /etc/sudoers.d/50-passwordless

  # Grab paru and add it's dependencies:
  pacman --noconfirm -Syu git devtools bat >/dev/null
  PARU_URL=https://github.com/Morganamilo/paru/releases/download/v2.0.4/paru-v2.0.4-x86_64.tar.zst \
    && curl -fsSL "${PARU_URL}" \
    | tar --extract --zstd --directory /usr/local/bin paru

  # Support for `paru --root`:
  mkdir -p /rootfs/var/lib/pacman/ && pacman --root /rootfs -Sy

  # Support for `paru --chroot` (Arch image minimizes locales in `/etc/pacman.conf`):
  sed -i 's/en_US de_DE/en_US/' /usr/sbin/mkarchroot
EOF
# Support for running systemd init:
RUN touch /etc/machine-id /etc/localtime
CMD ["/sbin/init"]
# Build and run the new image covering everything up to this point:
$ podman build --tag localhost/archlinux:systemd .
$ podman run --rm -d \
  --cap-add SYS_ADMIN,AUDIT_CONTROL \
  --security-opt label:disable \
  --env CHROOT=/tmp/rootfs \
  --name chroot-test \
  localhost/archlinux:systemd
$ podman exec -it chroot-test bash


# AUR packages of course aren't going to work with root:
$ paru --chroot --root /rootfs --noconfirm -S vscodium-bin > /dev/null
error: can't install AUR package as root


# Install a package with paru via the "automated" user:
$ sudo --user automated paru --chroot --root /rootfs --noconfirm -S vscodium-bin > /dev/null

warning: dependency cycle detected:
warning: systemd-libs will be installed before its libcap dependency
==> Synchronizing chroot copy [/var/lib/aurbuild/x86_64/root] -> [automated]...done
    vscodium-bin.desktop ... Passed
    vscodium-bin-url-handler.desktop ... Passed
    vscodium-bin.install ... Passed
    vscodium-bin.sh ... Passed
    vscodium-bin-wayland.desktop ... Passed
    VSCodium-linux-x64-1.96.2.24355.tar.gz ... Passed
error: config file /etc/pacman.conf could not be read: Permission denied
==> ERROR: 'pacman' returned a fatal error (1):
==> ERROR: Build failed, check /var/lib/aurbuild/x86_64/automated/build
error: failed to download sources for '{}': failed to run: makechrootpkg -cu -r /var/lib/aurbuild/x86_64 -d /var/cache/pacman/pkg/ -- -ofA:
error: packages failed to build: vscodium-bin-1.96.2.24355-1


# Original pacman allows read:
$ ls -l /etc/pacman.conf
-rw-r--r-- 1 root root 3526 Dec  8 00:04 /etc/pacman.conf

# Paru's chroot copy does not:
$ ls -l /var/lib/aurbuild/x86_64/root/etc/pacman.conf
-rw------- 1 root root 1832 Dec 24 23:24 /var/lib/aurbuild/x86_64/root/etc/pacman.conf

# Fix that so the `automated` user in the "other" perms group has read access:
$ chmod 644 /var/lib/aurbuild/x86_64/root/etc/pacman.conf

That seems like a bit of a bug? AUR packages need to be installed by a user that isn't root, but the pacman.conf at the chroot location is copied with permissions set to 600 (RW) with the user and group remaining as root?

AUR packages with build deps like input-remapper-git or warehouse-git seem to install properly this way too, unlike with just --root due to their build deps.

sudo --user automated paru --chroot --root /rootfs --noconfirm -S input-remapper-git

However this wasn't compatible for cado-nfs-git due to the current package missing recent new deps... it didn't seem possible to specify them in a way that was compatible with --chroot, presumably because it was building each package individually?

It does work when installing those missing deps via paru --root /var/lib/aurbuild/x86_64/root prior to using paru --chroot for cado-nfs-git. Although that would suggest the --chroot feature isn't exactly building packages from a clean slate then?:

# NOTE: Since I opened this issue, the cado-nfs-git package has added these missing deps,
# thus this extra step isn't required anymore.
sudo --user automated paru --root /var/lib/aurbuild/x86_64/root --noconfirm -S \
  --needed --asdeps python-flask python-requests

sudo --user automated paru --chroot --root /rootfs --noconfirm -S cado-nfs-git

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant