Search code examples
bashenvironment-variablesgithub-actionsqemuchroot

Foreign chroot with QEMU user mode in Github Actions, how to recreate host environment


I am trying to build for ARM targets in Github Actions. Since the toolchain does not support cross-compilation, the workaround is a foreign chroot environment and QEMU.

Steps were mostly taken from https://www.hellion.org.uk/blog/posts/foreign-chroots-with-schroot-and-qemu/, except that I am skipping the schroot/sbuild part. Instead, I intend to run the build by executing all related commands one after another in a foreign bash. To allow this to happen as regular GHA steps, I have created a script, bash-wrapper, which I run as a custom shell and which forwards all commands to a bash process for the target architecture running inside the chroot.

Here is the excerpt with which I set up the chroot and QEMU, and do a brief smoke test:

- name: Set up cross-compilation environment
  shell: bash
  run: |
    outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
    CROSS_ROOT="/opt/chroot/armhf"
    CROSS_MIRROR="http://deb.debian.org/debian/"
    outputs CROSS_ROOT CROSS_MIRROR
    sudo apt-get -y install debootstrap qemu-user-static binfmt-support
    sudo mkdir -p $CROSS_ROOT
    sudo debootstrap --variant=buildd --include=fakeroot,build-essential,sudo --arch=${{ matrix.job.arch }} --foreign ${{ matrix.job.release }} $CROSS_ROOT $CROSS_MIRROR
    sudo mkdir -p $CROSS_ROOT/usr/bin
    # TODO this is not fully portable across all architectures
    sudo ln /usr/bin/qemu-arm-static $CROSS_ROOT/usr/bin/
    sudo chroot $CROSS_ROOT ./debootstrap/debootstrap --second-stage
    # we need to replicate users, groups and home dirs if we’re not running as root
    sudo ln -f /etc/passwd $CROSS_ROOT/etc/passwd
    sudo ln -f /etc/group $CROSS_ROOT/etc/group
    sudo mkdir -p $CROSS_ROOT/$HOME
    sudo rm -Rf $CROSS_ROOT/$HOME
    pushd /
    sudo mv $HOME $CROSS_ROOT/$HOME
    sudo ln -s $CROSS_ROOT/$HOME $HOME
    sudo chown `whoami`:`whoami` $HOME
    popd
    sudo mkdir -p $CROSS_ROOT/etc/sudoers.d
    echo 'Defaults  lecture = never' | sudo tee $CROSS_ROOT/etc/sudoers.d/lecture
    sudo chmod 440 $CROSS_ROOT/etc/sudoers.d/lecture
    sudo ln -f /etc/sudoers $CROSS_ROOT/etc/sudoers
    # TODO disable password prompt
    # prepare bash wrapper
    sudo cp .github/bash-wrapper /bin/
    sudo chown root:root /bin/bash-wrapper
    sudo chmod a+x /bin/bash-wrapper
    # TODO remove after debug
    set

- name: Test bash-wrapper
  shell: bash-wrapper {0}
  run: |
    echo Running as $(whoami) in $(pwd)
    whoami
    echo Target platform is $(uname -a)
    echo -n 'Path to sudo: '
    which sudo || echo not found
    set

bash-wrapper is a shell script with the following content:

#!/bin/bash
CROSS_ROOT=/opt/chroot/armhf

# FIXME for some reasons, GHA passes scripts but does not set the executable flag,
# so we need to work around this.
test -f $1 && sudo chmod a+x $1

sudo chroot --userspec=`whoami`:`whoami` $CROSS_ROOT qemu-arm-static /bin/bash -c "cd $GITHUB_WORKSPACE; $*"

Comparing set output on both host and guest systems, I notice a few differences:

  • GITHUB_* variables are missing in the guest
  • HOME and USER are set for root, even though bash is running as a different user, runner.

How can I pass the host environment to the guest, with only a few exceptions (BASH_*, MACHTYPE, OSTYPE)? Or is there an easier approach to set up a VM for a foreign architecture and execute GHA steps in it?


Solution

  • For a handful of known variables, export them when calling bash from bash-wrapper:

    sudo chroot --userspec=`whoami`:`whoami` $CROSS_ROOT qemu-arm-static /bin/bash -c "export HOME=$HOME; cd $GITHUB_WORKSPACE; $*"
    

    Limitations: you need to know which variables you need, and for a large number of variables, this method may become impractical.