#!/bin/sh
# shellcheck disable=SC3043
#
# autopkgtest-build-lxc is part of autopkgtest
# autopkgtest is a tool for testing Debian binary packages
#
# autopkgtest is Copyright (C) 2006-2014 Canonical Ltd.
#
# Build or update a container with the debian or ubuntu LXC template
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# See the file CREDITS for a full list of credits information (often
# installed as /usr/share/doc/autopkgtest/CREDITS).
set -e

# The KEYRING environment variable is consumed by the lxc-debian template.
case "$1" in
	--keyring)
		export KEYRING="$2"
		shift 2
	;;
	--keyring=*)
		export KEYRING="${1#*=}"
		shift
	;;
esac

DISTRO="$1"
RELEASE="$2"
if [ -z "$1" ] || [ -z "$2" ]; then
    echo "Usage: $0 [--keyring=path] debian|ubuntu|kali <release> [arch] [script]" >&2
    exit 1
fi

# check that LXC config has networking
if grep -q 'lxc.net.0.type *= *empty' /etc/lxc/default.conf ; then
    cat <<EOF >&2
ERROR: autopkgtest containers need networking; please set it up and adjust
lxc.net.0.type in /etc/lxc/default.conf
EOF
    exit 1
fi

ARCH=${3:-}
NAME="autopkgtest-${RELEASE}${ARCH:+-$ARCH}"

SCRIPT=${4:-}
if [ -n "$SCRIPT" ] && [ ! -r "$SCRIPT" ]; then
    echo "ERROR: can't read customization script $SCRIPT" >&2
    exit 1
fi

# fall back for older LXC option name
LXCDIR=$(lxc-config lxc.lxcpath) || LXCDIR=$(lxc-config lxcpath) || LXCDIR=/var/lib/lxc

LXC_ARGS="-t $DISTRO -- -r $RELEASE ${ARCH:+-a $ARCH}"

# packages_template defaults to "ssh,vim" in Ubuntu; don't install those
export packages_template=eatmydata
if type eatmydata >/dev/null 2>&1; then
    LXC_CREATE_PREFIX="eatmydata"
fi

# detect apt proxy
proxy_detect() {
    # support backwards compatible env var too
    AUTOPKGTEST_APT_PROXY=${AUTOPKGTEST_APT_PROXY:-${ADT_APT_PROXY:-}}
    if [ -z "$AUTOPKGTEST_APT_PROXY" ]; then
        RES=$(apt-config shell proxy Acquire::http::Proxy)
        eval "$RES"
        if echo "${proxy:-}" | grep -E -q '(localhost|127\.0\.0\.[0-9]*)'; then
            # set http_proxy for the initial debootstrap
            export http_proxy="$proxy"

            # translate proxy address to one that can be accessed from the
            # running container (let's not support more complex setups with
            # more than one network)
            local bridge_interface
            bridge_interface=$(awk '{ if ($1 == "lxc.net.0.link") print($3)}' /etc/lxc/default.conf)
            if [ -n "$bridge_interface" ]; then
                local bridge_ip
                bridge_ip=$(ip -4 a show dev "$bridge_interface" | awk '/ inet / {sub(/\/.*$/, "", $2); print $2}') || true
                if [ -n "$bridge_ip" ]; then
                    AUTOPKGTEST_APT_PROXY=$(echo "$proxy" | sed -r "s#localhost|127\.0\.0\.[0-9]*#$bridge_ip#")
                fi
            fi
            if [ -n "$AUTOPKGTEST_APT_PROXY" ]; then
                echo "Detected local apt proxy, using $AUTOPKGTEST_APT_PROXY as container proxy"
            fi
        elif [ -n "${proxy:-}" ]; then
            AUTOPKGTEST_APT_PROXY="$proxy"
            echo "Using $AUTOPKGTEST_APT_PROXY as container proxy"
            # set http_proxy for the initial debootstrap
            export http_proxy="$proxy"
        fi
    elif [ "$AUTOPKGTEST_APT_PROXY" = UNCONFIGURED ] ; then
        unset AUTOPKGTEST_APT_PROXY
    fi
}

wait_booted() {
    # find await-boot script
    for script in $(dirname "$(dirname "$0")")/lib/in-testbed/await-boot.sh \
                  /usr/share/autopkgtest/lib/in-testbed/await-boot.sh; do
        if [ -r "$script" ]; then
            await_boot=$script
            break
        fi
    done

    # Run the await-boot script. In other tools (e.g. autopkgtest-build-lxd)
    # we first wait for the container/VM to be able to execute commands by
    # trying to execute `true` in a loop until the execution succeeds. This
    # seems to be unnecessary with lxc containers: lxc-start does not appear
    # to be racy with lxc-attach calls that immediately follow.
    #
    # Doing `cat somefile | lxc-attach ...` is not "useless use of cat"
    # (SC2002): lxc changes ownership/permissions of stdin via fchown() and
    # fchmod(). Using a redirect like `lxc-attach ... < somefile` causes
    # unwanted changes to somefile ownership and permissions on the filesystem.
    # Also see: https://github.com/lxc/lxc/issues/4484.
    # shellcheck disable=SC2002
    if ! cat "$await_boot" | timeout 60 lxc-attach --name="$1" -- sh -s; then
        echo "Error or timeout waiting for container to boot" >&2
        lxc-stop --kill --name="$1" || true
        lxc-destroy --name="$1" || true
        exit 1
    fi
}

setup() {
    # a host's http_proxy for localhost does not work in the guest, apt proxy
    # needs to be set up separately there
    if [ "$http_proxy" != "${http_proxy#*localhost*}" ] || \
       [ "$http_proxy" != "${http_proxy#*127.0.0.*}" ]; then
       unset http_proxy
       unset https_proxy
    fi

    # set up apt proxy for the container
    if [ -n "$AUTOPKGTEST_APT_PROXY" ]; then
        echo "Acquire::http { Proxy \"$AUTOPKGTEST_APT_PROXY\"; };" > "$LXCDIR/$1/rootfs/etc/apt/apt.conf.d/01proxy"
    fi

    lxc-start --daemon --name="$1"
    wait_booted "$1"

    if [ -z "${AUTOPKGTEST_KEEP_APT_SOURCES:-}" ] && [ -n "${AUTOPKGTEST_APT_SOURCES_FILE:-}" ]; then
        # Transfer the apt sources from the host system to the environment
        AUTOPKGTEST_APT_SOURCES="$(cat "$AUTOPKGTEST_APT_SOURCES_FILE")"
        unset AUTOPKGTEST_APT_SOURCES_FILE
    fi

    # find setup-testbed script
    for script in $(dirname "$(dirname "$0")")/setup-commands/setup-testbed \
                  /usr/share/autopkgtest/setup-commands/setup-testbed; do
        if [ -r "$script" ]; then
            echo "Running setup script $script..."
            # not useless use of cat, see https://github.com/lxc/lxc/issues/4484
            # shellcheck disable=SC2002
            cat "${script}" | lxc-attach --name="$1" env \
                AUTOPKGTEST_KEEP_APT_SOURCES="${AUTOPKGTEST_KEEP_APT_SOURCES:-}" \
                AUTOPKGTEST_APT_SOURCES="${AUTOPKGTEST_APT_SOURCES:-}" \
                MIRROR="${MIRROR:-}" \
                RELEASE="$RELEASE" \
                sh
            break
        fi
    done

    # run customize script
    if [ -n "$SCRIPT" ]; then
        echo "Running customization script $SCRIPT..."
        # not useless use of cat, see https://github.com/lxc/lxc/issues/4484
        # shellcheck disable=SC2002
        cat "${SCRIPT}" | lxc-attach --name="$1" env MIRROR="${MIRROR:-}" sh
    fi

    lxc-stop --name="$1"
}

proxy_detect

# lxc templates for debian and ubuntu differ; ubuntu uses
# $RELEASE/rootfs-$ARCH, while debian uses debian/rootfs-$RELEASE-$ARCH
CACHE="$RELEASE"
if [ "$DISTRO" = debian ] || [ "$DISTRO" = kali ] ; then
    CACHE=$DISTRO
fi

if [ ! -e "$LXCDIR/$NAME" ]; then
    # first-time run: just create the container
    # shellcheck disable=SC2086
    $LXC_CREATE_PREFIX lxc-create -B best --name="$NAME" $LXC_ARGS
    setup "$NAME"
else
    # remove LXC rootfs caches; on btrfs this might be a subvolume, otherwise
    # rm it
    btrfs subvolume delete "/var/cache/lxc/$CACHE/rootfs-"* 2>/dev/null || rm -rf "/var/cache/lxc/$CACHE/rootfs-"*
    # remove leftover .new container if present
    if lxc-ls | grep -q "${NAME}.new" ; then
      lxc-destroy --force --name="${NAME}.new"
    fi
    # create a new rootfs in a temp container
    # shellcheck disable=SC2086
    $LXC_CREATE_PREFIX lxc-create -B best --name="${NAME}.new" $LXC_ARGS
    setup "${NAME}.new"
    sed -i "s/${NAME}.new/${NAME}/" "$LXCDIR/${NAME}.new/rootfs/etc/hostname" "$LXCDIR/${NAME}.new/rootfs/etc/hosts"
    # replace the original rootfs; can't make this more atomic unfortunately
    mv "$LXCDIR/${NAME}.new/rootfs" "$LXCDIR/${NAME}/rootfs.new"
    mv "$LXCDIR/${NAME}/rootfs" "$LXCDIR/${NAME}/rootfs.old"
    mv "$LXCDIR/${NAME}/rootfs.new" "$LXCDIR/${NAME}/rootfs"
    # replace the original config
    sed -e "s/${NAME}.new/${NAME}/g" "$LXCDIR/${NAME}.new/config" > "$LXCDIR/${NAME}/config"
    # old rootfs might contain btrfs subvolumes, remove them
    subvols=$(btrfs subvolume list -o "$LXCDIR/${NAME}/rootfs.old" 2>/dev/null | awk "/\/rootfs.old/ {print \$(NF)}") || true
    for vol in $subvols; do
        btrfs subvolume delete "/${vol#@}"
    done
    btrfs subvolume delete "$LXCDIR/${NAME}/rootfs.old" 2>/dev/null || rm -rf "$LXCDIR/${NAME}/rootfs.old"
    mkdir "$LXCDIR/${NAME}.new/rootfs"
    lxc-destroy --name="${NAME}.new"
fi
