#!/bin/bash
set -eE -o pipefail

BUILDDIRECTORY=/var/lib/repro

BOOTSTRAPMIRROR=https://mirror.archlinux.no/iso/latest
readonly bootstrap_img=archlinux-bootstrap-"$(date +%Y.%m)".01-"$(uname -m)".tar.gz
CONFIGDIR='/etc/archlinux-repro'

# HOSTMIRROR=$(curl -s 'https://www.archlinux.org/mirrorlist/?protocol=https' | awk '/^#Server/ {print $3; exit}')
## Hardcoded until further notice
HOSTMIRROR="http://mirror.neuf.no/archlinux/\$repo/os/\$arch"

# IMGDIRECTORY=$(mktemp -dt .arch_img)
IMGDIRECTORY="/tmp/arch_img"
mkdir -p $IMGDIRECTORY

DIFFOSCOPE="diffoscope"

# Desc: Escalates privileges
orig_argv=("$0" "$@")
src_owner=${SUDO_USER:-$USER}
function check_root() {
	(( EUID == 0 )) && return
	if type -P sudo >/dev/null; then
		exec sudo -- "${orig_argv[@]}"
	else
		exec su root -c "$(printf ' %q' "${orig_argv[@]}")"
	fi
}

# Desc: Sets the appropriate colors for output
function colorize() {
	# prefer terminal safe colored and bold text when tput is supported
	if tput setaf 0 &>/dev/null; then
		ALL_OFF="$(tput sgr0)"
		BOLD="$(tput bold)"
		BLUE="${BOLD}$(tput setaf 4)"
		GREEN="${BOLD}$(tput setaf 2)"
		RED="${BOLD}$(tput setaf 1)"
		YELLOW="${BOLD}$(tput setaf 3)"
	else
		ALL_OFF="\e[0m"
		BOLD="\e[1m"
		BLUE="${BOLD}\e[34m"
		GREEN="${BOLD}\e[32m"
		RED="${BOLD}\e[31m"
		YELLOW="${BOLD}\e[33m"
	fi
	readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
}
colorize

# Desc: Message format
function msg() {
	local mesg=$1; shift
    # shellcheck disable=SC2059
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

# Desc: Sub-message format
function msg2() {
	local mesg=$1; shift
    # shellcheck disable=SC2059
	printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

# Desc: Warning format
function warning() {
	local mesg=$1; shift
    # shellcheck disable=SC2059
	printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

# Desc: Error format
function error() {
	local mesg=$1; shift
    # shellcheck disable=SC2059
	printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

# Desc: Executes an command inside a given nspawn container
# 1: Container name
# 2: Command to execute
function exec_nspawn(){
    local container=$1
    systemd-nspawn -q \
		--as-pid2 \
		--register=no \
		--pipe \
		-E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" \
		-D "$BUILDDIRECTORY/$container" "${@:2}"
}

# Desc: Removes the root container
function cleanup_root_volume(){
    warning "Removing root container..."
    rm -rf "$BUILDDIRECTORY/root"
}

# Desc: Removes a given snapshot
# 1: Snapshot name
function remove_snapshot (){
    local build=$1
    msg2 "Delete snapshot for $build..."
    umount "$BUILDDIRECTORY/$build" || true
    rm -rf "${BUILDDIRECTORY:?}/${build}"
    rm -rf "${BUILDDIRECTORY:?}/${build}_upperdir"
    rm -rf "${BUILDDIRECTORY:?}/${build}_workdir"
    trap - ERR INT
}

# Desc: Creates a snapshot of the root container
# 1: name of container
function create_snapshot (){
    local build=$1

    trap "{ remove_snapshot $build ; exit 1; }" ERR INT

    msg2 "Create snapshot for $build..."
    mkdir -p "$BUILDDIRECTORY/"{"${build}","${build}_upperdir","${build}_workdir"}
    # shellcheck disable=SC2140
    mount -t overlay overlay \
        -o lowerdir="$BUILDDIRECTORY/root",upperdir="$BUILDDIRECTORY/${build}_upperdir",workdir="$BUILDDIRECTORY/${build}_workdir" \
        "$BUILDDIRECTORY/${build}"
    touch "$BUILDDIRECTORY/$build"
}

# Desc: Build a package inside a container
# 1: Container name
# 2: Container buildpath
function build_package(){
    local build=$1
    local builddir=${2:-"/startdir"}
    exec_nspawn "$build" \
        --bind="$PWD:/srcdest" \
bash <<-__END__
set -e
install -d -o builduser -g builduser /pkgdest
install -d -o builduser -g builduser /srcpkgdest
install -d -o builduser -g builduser /build
sudo -iu builduser bash -c 'cd /startdir; SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH makepkg -sc --noconfirm --skippgpcheck'
__END__
    mkdir -p "./$build"
    for pkgfile in "$BUILDDIRECTORY/$build"/pkgdest/*; do
        mv "$pkgfile" "./$build/"
    done
    chown -R "$src_owner" "./$build"
}

# Desc: Sets up a container with the correct files
function init_chroot(){
    set -e

    [ ! -d "$BUILDDIRECTORY" ] && mkdir -p "$BUILDDIRECTORY"

    # Prepare root chroot
    if [ ! -d "$BUILDDIRECTORY"/root ]; then

        msg "Preparing chroot"
        trap '{ cleanup_root_volume; exit 1; }' ERR
        trap '{ cleanup_root_volume; trap - INT; kill -INT $$; }' INT

        msg2 "Extracting image into container..."
        mkdir -p "$BUILDDIRECTORY/root"
        tar xvf "$IMGDIRECTORY/$bootstrap_img" -C "$BUILDDIRECTORY/root" --strip-components=1 > /dev/null

        printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
        printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$BUILDDIRECTORY"/root/etc/locale.gen
        printf 'LANG=en_US.UTF-8\n' > "$BUILDDIRECTORY"/root/etc/locale.conf

        systemd-machine-id-setup --root="$BUILDDIRECTORY"/root
        msg2 "Setting up keyring, this might take a while..."
        exec_nspawn root pacman-key --init &> /dev/null
        exec_nspawn root pacman-key --populate archlinux &> /dev/null

        msg2 "Updating and installing base & base-devel"
        exec_nspawn root pacman -Syu base-devel --noconfirm
        exec_nspawn root pacman -R arch-install-scripts --noconfirm
        exec_nspawn root locale-gen

        printf 'builduser ALL = NOPASSWD: /usr/bin/pacman\n' > "$BUILDDIRECTORY"/root/etc/sudoers.d/builduser-pacman
        exec_nspawn root useradd -m -G wheel -s /bin/bash builduser
        echo "keyserver-options auto-key-retrieve" | install -Dm644 /dev/stdin "$BUILDDIRECTORY/root"/home/builduser/.gnupg/gpg.conf
        exec_nspawn root chown -R builduser /home/builduser/.gnupg
    else
        printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist
        exec_nspawn root pacman -Syu --noconfirm
    fi

    trap - ERR INT
}

# Desc: Reproduces a package
# 1: Location of package
function cmd_check(){
    local pkg="${1}"
    local cachedir="cache"

    if [[ ! -f "${pkg}" ]]; then
      error "no package file given"
      exit 1
    fi

    trap - ERR INT

    msg "Starting build..."
    create_snapshot "build" 0
    build="build"

    declare -A buildinfo
    while IFS=$'=' read -r key value; do
        [[ "${key}" = [#!]* ]] || [[ "${key}" = "" ]] || buildinfo["${key}"]="${value}"
    done <<< "$(buildinfo -ff "${pkg}")"
    packager="${buildinfo[packager]}"
    builddir="${buildinfo[builddir]}"
    _pkgver="${buildinfo[pkgver]}"
    pkgrel=${_pkgver##*-}
    pkgver=${_pkgver%-*}
    pkgbase=${buildinfo[pkgbase]}
    options=${buildinfo[options]}
    buildenv=${buildinfo[buildenv]}

    pkgbuild_sha256sum="${buildinfo[pkgbuild_sha256sum]}"
    SOURCE_DATE_EPOCH="${buildinfo[builddate]}"

    {
        printf 'PKGDEST=/pkgdest\n'
        printf 'SRCPKGDEST=/srcpkgdest\n'
        printf 'BUILDDIR=%s\n' "${builddir}"
        printf 'PACKAGER=%s\n' "${packager@Q}"
        printf 'OPTIONS=(%s)\n' "${options}"
        printf 'BUILDENV=(%s)\n' "${buildenv}"
     } >> "$BUILDDIRECTORY/$build/etc/makepkg.conf"

    # Father I have sinned
    exec_nspawn "build" \
    bash <<-__END__
shopt -s globstar
install -d -o builduser -g builduser /startdir
pacman -S asp --noconfirm
asp checkout $pkgbase
cd $pkgbase
for rev in \$(git rev-list --all -- repos/); do
    pkgbuild_checksum=\$(git show \$rev:trunk/PKGBUILD | sha256sum -b)
    pkgbuild_checksum=\${pkgbuild_checksum%% *}
    if [ \$pkgbuild_checksum = $pkgbuild_sha256sum ]; then
        git checkout \$rev
        mv ./trunk/* /startdir
        pacman -Rs asp --noconfirm
        exit 0
    fi
done
exit 1
__END__

    msg2 "Preparing packages"
    mkdir -p "${cachedir}"
    mapfile -t packages < <(buildinfo -d "${cachedir}" "${pkg}")
    msg2 "Finished preparing packages"
    msg "Installing packages"
    # shellcheck disable=SC2086
    exec_nspawn build --bind="$(readlink -e ${cachedir}):/cache" bash -c \
        'yes y | pacman -U --overwrite "*" -- "$@"' -bash "${packages[@]}"

    build_package "build" "$builddir"
    remove_snapshot "build"

    msg "Comparing hashes..."
    if diff -q -- "$pkg" ./build/"$(basename "$pkg")" > /dev/null ; then
      msg "Package is reproducible!"
      exit 0
    else
      error "Package is not reproducible"
      if ((run_diffoscope)); then
          PYTHONIOENCODING=utf-8 $DIFFOSCOPE "$pkg" ./build/"$(basename "$pkg")" || true
      fi
      exit 1
    fi
}

# Desc: Fetches a bootstrap image and verifies the signature
function get_bootstrap_img() {
    trap '{ rm "$IMGDIRECTORY/$bootstrap_img"{,.sig} ; exit 1; }' ERR
    if [ ! -e "$IMGDIRECTORY/$bootstrap_img" ]; then
        msg "Downloading bootstrap image..."
        ( cd "$IMGDIRECTORY" && curl --remote-name-all "$BOOTSTRAPMIRROR/$bootstrap_img"{,.sig} )
        if ! gpg --verify "$IMGDIRECTORY/$bootstrap_img.sig" "$IMGDIRECTORY/$bootstrap_img"; then
            error "Can't verify image"
            exit 1
        fi
    fi
    trap - ERR
}

# Desc: Prints the help section
function print_help() {
cat <<__END__
Usage:
  repro [options]

General Options:
 -h                           Print this help message
 -d                           Run diffoscope if packages are not reproducible
__END__
}

hash buildinfo 2>/dev/null || { error "Require buildinfo in path! Aborting..."; exit 1; }

# Default options
run_diffoscope=0

repro_conf=$CONFIGDIR/repro.conf
if [[ -r $repro_conf ]]; then
    # shellcheck source=/dev/null
    source "$repro_conf"
fi

xdg_repro_dir="${XDG_CONFIG_HOME:-$HOME/.config}/archlinux-repro"
if [[ -r "$xdg_repro_dir/repro.conf" ]]; then
	# shellcheck source=/dev/null
	source "$xdg_repro_dir/repro.conf"
elif [[ -r "$HOME/.repro.conf" ]]; then
	# shellcheck source=/dev/null
	source "$HOME/.repro.conf"
fi


while getopts :hdoC:P:M: arg; do
    case $arg in
        h) print_help; exit 0;;
        d) run_diffoscope=1;;
        *) ;;
    esac
done

# Save command args (such as path to .pkg.tar.xz file)
shift $((OPTIND-1))

get_bootstrap_img
check_root
init_chroot
cmd_check "$@"
