#!/bin/bash
# aur-chroot - build packages with systemd-nspawn
[[ -v AUR_DEBUG ]] && set -o xtrace
set -o errexit
argv0=chroot
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
machine=$(uname -m)

# default arguments
directory=/var/lib/aurbuild/$machine
makechrootpkg_args=(-cu)  # XXX: allow unsetting these options?
makechrootpkg_makepkg_args=()

# default options
update=0 build=0 create=0 status=0 print_path=0

args_csv() {
    # shellcheck disable=SC2155
    local str=$(printf '%s,' "$@")
    printf '%s' "${str%,}"
}

# XXX: a missing makepkg.conf usually indicates a missing devtools, whereas
# a missing pacman.conf usually indicates the local repository was not configured
diag_makepkg_conf() {
    echo >&2 'Error:'

    cat <<EOF | pr -to 4 >&2
aur-$argv0 could not find a makepkg.conf(5) file for container usage. Before
using aur-$argv0, make sure this file is created and valid. See OPTIONS in
aur-$argv0(1) for configuration details.

The following file paths were checked:
EOF
    printf '%s\n' "${@@Q}" | pr -to 8 >&2
}

diag_pacman_conf() {
    echo >&2 'Error:'

    cat <<EOF | pr -to 4 >&2
aur-$argv0 could not find a pacman.conf(5) file for container usage. Before
using aur-$argv0, make sure this file is created and valid. See OPTIONS in
aur-$argv0(1) for configuration details.

The following file paths were checked:
EOF
    printf '%s\n' "${@@Q}" | pr -to 8 >&2
}

usage() {
    printf 'usage: %s [-BU] [--create] [-CDM path] [package...]\n' "$argv0"
    exit 1
}

opt_short='C:D:M:x:ABNTU'
opt_long=('directory:' 'pacman-conf:' 'makepkg-conf:' 'build' 'update'
          'create' 'bind:' 'bind-rw:' 'user:' 'makepkg-args:'
          'ignorearch' 'namcap' 'checkpkg' 'temp' 'makechrootpkg-args:'
          'margs:' 'cargs:' 'nocheck' 'suffix:')
opt_hidden=('dump-options' 'status' 'path')

if opts=$(getopt -o "$opt_short" -l "$(args_csv "${opt_long[@]}" "${opt_hidden[@]}")" -n "$argv0" -- "$@"); then
    eval set -- "$opts"
else
    usage
fi

unset bindmounts_ro bindmounts_rw makepkg_conf pacman_conf suffix
while true; do
    case "$1" in
        -x|--suffix)
            shift; suffix=$1 ;;
        -B|--build)
            build=1 ;;
        -U|--update)
            update=1 ;;
        --create)
            create=1 ;;
        --status)
            status=1 ;;
        --path)  # deprecated
            print_path=1 ;;
        # makepkg options (`--build`)
        -A|--ignorearch)
            makechrootpkg_makepkg_args+=(--ignorearch) ;;
        --nocheck)
            makechrootpkg_makepkg_args+=(--nocheck) ;;
        # XXX: cannot take arguments that contain commas (e.g. for file paths)
        --makepkg-args|--margs)
            shift; IFS=, read -a arg -r <<< "$1"
            makechrootpkg_makepkg_args+=("${arg[@]}") ;;
        # devtools options (`--build`)
        -C|--pacman-conf)
            shift; pacman_conf=$1 ;;
        -D|--directory)
            shift; directory=$1 ;;
        -M|--makepkg-conf)
            shift; makepkg_conf=$1 ;;
        --bind)
            shift; bindmounts_ro+=("$1") ;;
        --bind-rw)
            shift; bindmounts_rw+=("$1") ;;
        -N|--namcap)
            makechrootpkg_args+=(-n) ;;
        --checkpkg)
            makechrootpkg_args+=(-C) ;;
        -T|--temp)
            makechrootpkg_args+=(-T) ;;
        --user)
            shift; makechrootpkg_args+=(-U "$1") ;;
        # XXX: cannot take arguments that contain commas (e.g. for file paths)
        --makechrootpkg-args|--cargs)
            shift; IFS=, read -a arg -r <<< "$1"
            makechrootpkg_args+=("${arg[@]}") ;;
        # other options
        --dump-options)
            printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
            printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
            exit ;;
        --) shift; break ;;
    esac
    shift
done

# XXX: default paths can be set through the Makefile (`aur-chroot.in`)
etcdir=/etc/aurutils shrdir=/usr/share/devtools

# The pacman configuration in the chroot may contain a local repository that
# is not configured on the host. Therefore, $db_name is only used for the
# default paths below when specified on the command-line or through `AUR_REPO`.
if [[ -v suffix ]]; then
    default_pacman_paths=("$etcdir/pacman-$suffix.conf"
                          "$etcdir/pacman-$machine.conf"
                          "$shrdir/pacman.conf.d/$suffix.conf"
                          "$shrdir/pacman.conf.d/aurutils-$machine.conf")

    default_makepkg_paths=("$etcdir/makepkg-$suffix.conf"
                           "$etcdir/makepkg-$machine.conf"
                           "$shrdir/makepkg.conf.d/$suffix.conf"
                           "$shrdir/makepkg.conf.d/$machine.conf")
else
    default_pacman_paths=("$etcdir/pacman-$machine.conf"
                          "$shrdir/pacman.conf.d/aurutils-$machine.conf")

    default_makepkg_paths=("$etcdir/makepkg-$machine.conf"
                           "$shrdir/makepkg.conf.d/$machine.conf")
fi

# Change the default /usr/share/devtools/pacman-extra.conf in aur-chroot to
# /etc/aurutils/pacman-<repo>.conf or /etc/aurutils/pacman-<uname>.conf in
# aur-build, and pass it on to aur-chroot (#824, #846)
for def in "${default_pacman_paths[@]}"; do
    if [[ -f $def ]] && [[ ! -v pacman_conf ]]; then
        pacman_conf=$def
        break
    fi
done

# The same as above but for /etc/aurutils/makepkg-<repo>.conf or
# /etc/aurutils/makepkg-<uname>.conf. If the file is not found, fallback to
# makepkg.conf files in /usr/share/devtools.
for def in "${default_makepkg_paths[@]}"; do
    if [[ -f $def ]] && [[ ! -v makepkg_conf ]]; then
        makepkg_conf=$def
        break
    fi
done

# No pacman configuration is available for the container, or it points to a
# non-existing file. Print a matching diagnostic and exit.
if [[ ! -v pacman_conf ]]; then
    diag_pacman_conf "${default_pacman_paths[@]}"
    exit 2
elif [[ ! -f $pacman_conf ]]; then
    diag_pacman_conf "$pacman_conf"
    exit 2
elif [[ ! -v makepkg_conf ]]; then
    diag_makepkg_conf "${default_makepkg_paths[@]}"
    exit 2
elif [[ ! -f $makepkg_conf ]]; then
    diag_makepkg_conf "$makepkg_conf"
    exit 2
fi

# Print paths to container and used makepkg/pacman paths. This does
# not require a priorly created container.
if (( status )) || ! (( update + build + create + print_path )); then
    printf 'chroot:%s\npacman:%s\nmakepkg:%s\n' "$directory" "$pacman_conf" "$makepkg_conf"
    exit 0
fi

# Custom elevation command (#1024)
unset auth_args
AUR_PACMAN_AUTH=${AUR_PACMAN_AUTH:-sudo}

case $AUR_PACMAN_AUTH in
    sudo) auth_args+=('--preserve-env=GNUPGHOME,SSH_AUTH_SOCK,SRCDEST,PKGDEST,LOGDEST') ;;
esac

# bind mount file:// paths to container (#461)
# required for update/build steps
while read -r key _ value; do
    case $key=$value in
        Server=file://*)
            bindmounts_rw+=("${value#file://}") ;;
    esac
done < <(pacman-conf --config "$pacman_conf")
wait "$!"

# create new container, required for update/build steps
if (( create )); then
    # default to base-devel or multilib-devel, unless packages are
    # specified on the command-line.
    # available packages depend on the configured pacman configuration
    if (( $# )); then
        base_packages=("$@")

    # XXX: use pacini to not process Include directives in pacman.conf
    # (not supported by devtools)
    elif [[ $(pacini --section=multilib "$pacman_conf") ]] && [[ $machine == "x86_64" ]]; then
        base_packages=('base-devel' 'multilib-devel')
    else
        base_packages=('base-devel')
    fi

    # parent path is not created by mkarchroot (#371)
    if [[ ! -d $directory ]]; then
        # shellcheck disable=SC2086
        $AUR_PACMAN_AUTH install -d "$directory" -m 755 -v
    fi

    if [[ ! -d $directory/root ]]; then
        # shellcheck disable=SC2086
        $AUR_PACMAN_AUTH mkarchroot -C "$pacman_conf" -M "$makepkg_conf" "$directory"/root "${base_packages[@]}"
    else
        create=0
    fi
fi >&2

# arch-nspawn makes no distinction between a missing working directory
# and one which does not exist
if [[ ! -d $directory/root ]]; then
    printf >&2 '%s: %q is not a directory\n' "$argv0" "$directory"/root
    printf >&2 '%s: did you run aur chroot --create?\n' "$argv0"
    exit 20
fi

if (( update )); then
    # locking is done by systemd-nspawn
    # shellcheck disable=SC2086
    $AUR_PACMAN_AUTH arch-nspawn -C "$pacman_conf" -M "$makepkg_conf" "$directory"/root \
         "${bindmounts_ro[@]/#/--bind-ro=}" \
         "${bindmounts_rw[@]/#/--bind=}" pacman -Syu --noconfirm "$@"
fi >&2

# print path for processing by other tools (e.g. makepkg --packagelist)
if (( print_path )); then
    realpath -- "$directory"/root
    exit $?
fi

if (( build )); then
    # use makechrootpkg -c as default build command (sync /root container)
    # arguments after -- are used as makechrootpkg arguments
    # shellcheck disable=SC2086
    $AUR_PACMAN_AUTH "${auth_args[@]}" makechrootpkg -r "$directory" \
         "${bindmounts_ro[@]/#/-D}" "${bindmounts_rw[@]/#/-d}" \
         "${makechrootpkg_args[@]}" -- "${makechrootpkg_makepkg_args[@]}"
fi >&2

# vim: set et sw=4 sts=4 ft=sh:
