#!/usr/bin/env bash
#
# Copyright (C) 2024 Laurent Jourden <laurent85@enarel.fr>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Script to build the ZFS packages against the linux kernel 
# for Arch Linux.

set -e -u -o pipefail

WD="$(pwd)"
appname="${0##*/}"
pkgbuild_dir="/usr/share/archuseriso/pkgbuild"
auiwork=""
ar64=""
help="no"
yes="no"
linuxversion=""
modulesversion=""
pkgdest=""
pkgdest_makepkg=""
release=""
SCRIPTUSER="${SUDO_USER:-}"
zfsversion=""
# https://openzfs.github.io/openzfs-docs/Project%20and%20Community/Signing%20Keys.html
# Tony Hutter, Brian Behlendorf (same fingerprints as pkgbuild/*/PKGBUILD validpgpkeys)
ZFSPUBKEYS=(4F3BA9AB6D1F8D683DC2DFB56AD860EED4598027
            C33DF142657ED1F7C328A2960AB9E991C6AF658B)
declare -a zfsmissingkeys=()
declare -a zfspackages
declare -a zfssources

_usage () {
    echo
    echo "${appname}, ZFS packages creation tool."
    echo
    echo 'Synopsis:'
    echo "${appname} [options]"
    echo
    echo 'Help:'
    echo "${appname} --help"
    echo
    exit "${1}"
}

_help () {
    echo
    echo "${appname}, ZFS packages creation tool."
    echo
    echo 'Synopsis:'
    echo "${appname} [options]"
    echo
    echo 'Options:'
    echo '-h, --help                Command line help'
    echo "-D, --pkgbuild-dir=<path> Path to pkgbuild directory (default: ${pkgbuild_dir})"
    echo '--pkgdest=<path>          Packages destination directory. Default: ./out'
    echo '-r, --release             OpenZFS release to build (2.1.2 or zfs-2.1.2).'
    echo "                          Default: latest. Example: ${appname} --release=2.1.2"
    echo '-y, --yes                 Retrieve missing OpenZFS public keys without prompting'
    echo
    exit "${1}"
}

_msg_info() {
    local _msg="${1}"
    printf '[%s] INFO: %s\n' "${appname}" "${_msg}"
}

_cleanup() {
    if [[ -d "${auiwork}" ]]; then
       rm -r -- "${auiwork}"
    fi
}

_init () {
    local _json _link _tag _url
    _link="https://api.github.com/repos/openzfs/zfs/releases"
    if ! pacman -Q devtools &> /dev/null; then
        echo 'devtools package not installed, aborting!'
        exit 1
    fi
    if ! pacman -Q pacman-contrib &> /dev/null; then
        echo 'pacman-contrib package not installed, aborting!'
        exit 1
    else
        # check host PKGDEST setting in /etc/makepkg.conf
        # where packages created have to be placed
        pkgdest_makepkg="$(grep -s '^PKGDEST=' /etc/makepkg.conf || true)"
        pkgdest_makepkg="${pkgdest_makepkg#PKGDEST=}"
    fi
    if ! pacman -Q jq &> /dev/null; then
        echo 'jq package not installed, aborting!'
        exit 1
    fi
    pkgdest_makepkg="${pkgdest_makepkg:-"${WD}/out"}"
    pkgdest="${pkgdest:-${pkgdest_makepkg}}"
    mkdir -p -- "${ar64}" "${pkgdest}"
    if [[ -z "${release}" ]]; then
        _url="${_link}/latest"
    else
        _tag="${release}"
        [[ "${_tag}" == zfs-* ]] || _tag="zfs-${_tag}"
        _url="${_link}/tags/${_tag}"
    fi
    if ! _json="$(curl --silent --retry 3 --retry-connrefused --fail -- "${_url}")"; then
        if [[ -n "${release}" ]]; then
            echo "release ${release} not found, aborting!"
        else
            echo 'Retrieving ZFS data failed, aborting!'
        fi
        exit 1
    fi
    zfsversion="$(jq -r '.tag_name' <<< "${_json}")"
    zfsversion="${zfsversion#zfs-}"
    mapfile -t zfssources < <(jq -r '.assets[].browser_download_url' <<< "${_json}")
    if [[ -z "${zfsversion}" || "${zfsversion}" == "null" || -z "${zfssources:-}" ]]; then
        echo 'Retrieving ZFS data failed, aborting!'
        exit 1
    fi
}

_create_archroot64 () {
    _msg_info "Creating chroot environment!"
    LC_ALL=C mkarchroot \
        -C "${pkgbuild_dir}/pacman.conf" \
        -M /usr/share/devtools/makepkg.conf.d/x86_64.conf \
        -c /var/cache/pacman/pkg \
        -- "${ar64}/root" base linux linux-headers base-devel > /dev/null
    _msg_info "Done!"
}

_build_zfs () {
    local _zfs_install=() _zfspkg
    linuxversion=$(pacman --sysroot "${ar64}/root" -Q linux | cut -d' ' -f2)
    if [[ "$(cut -d'.' -f3 <<< "${linuxversion}")" =~ 'arch' ]]; then
        modulesversion="${linuxversion%.arch*}.0-${linuxversion##*.}"
    else
        modulesversion="${linuxversion%.arch*}-${linuxversion##*.}"
    fi
    cp -arT -- "${pkgbuild_dir}/zfs-utils/" "${auiwork}/zfs-utils/"
    cp -arT -- "${pkgbuild_dir}/zfs-linux/" "${auiwork}/zfs-linux/"
    sed -i -- "s/%ZFSVERSION%/${zfsversion}/;
               s/%LINUXVERSION%/${linuxversion}/;
               s/%MODULESVERSION%/${modulesversion}/" \
              "${auiwork}/zfs-utils/PKGBUILD" "${auiwork}/zfs-linux/PKGBUILD"
    cd -- "${auiwork}/zfs-utils/"
    for _zfslink in "${zfssources[@]}"; do
            curl --silent --retry 3 --retry-connrefused --fail -L -O "${_zfslink}"
    done
    cp -a -- "zfs-${zfsversion}.tar.gz"{,.asc} ../zfs-linux
    chown -R -- "${SCRIPTUSER}": "${auiwork}/"{zfs-utils,zfs-linux}

    _msg_info "Building zfs-utils & zfs-utils-debug"
    LC_ALL=C makechrootpkg -r "${ar64}" -- PKGDEST="" --cleanbuild --clean --force --syncdeps --needed --noconfirm --noprogressbar
    mapfile -t zfspackages < <(find -- "${auiwork}/zfs-utils" -type f \
        \( -name 'zfs-utils-*.pkg.tar.zst' -o -name 'zfs-utils-*.pkg.tar.xz' \) \
        ! -name 'zfs-utils-debug-*' | sort)
    if [[ ${#zfspackages[@]} -eq 0 ]]; then
        echo 'zfs-utils package not found, aborting!'
        exit 1
    fi
    _msg_info "Done!"

    cd -- "${auiwork}/zfs-linux/"
    _msg_info "Building zfs-linux & zfs-linux-headers"
    for _zfspkg in "${zfspackages[@]}"; do
        _zfs_install+=(-I "${_zfspkg}")
    done
    LC_ALL=C makechrootpkg -r "${ar64}" "${_zfs_install[@]}" -- PKGDEST="" --cleanbuild --clean --force --syncdeps --needed --noconfirm --noprogressbar
    mapfile -t zfspackages < <(find -- "${auiwork}/zfs-utils" "${auiwork}/zfs-linux" -type f \
        \( -name '*.pkg.tar.zst' -o -name '*.pkg.tar.xz' \) | sort)
    if [[ ${#zfspackages[@]} -eq 0 ]]; then
        echo 'ZFS packages not found, aborting!'
        exit 1
    fi
    _msg_info "Done!"

    cp -- "${zfspackages[@]}" "${pkgdest}"
    rm -r -- "${auiwork}"
}

if ! OPTS=$(getopt -o 'D:,h,r:,y' \
        -l 'help,pkgbuild-dir:,pkgdest:,release:,yes' \
        -n "${appname}" -- "$@"); then
    _usage 1
fi
eval set -- "${OPTS}"
unset OPTS

while true; do
    case "${1}" in
        '-h'|'--help')
            help="yes"
            shift ;;
        '-D'|'--pkgbuild-dir')
            pkgbuild_dir="${2}"
            shift 2 ;;
        '--pkgdest')
            pkgdest="${2}"
            shift 2 ;;
        '-r'|'--release')
            release="${2}"
            shift 2 ;;
        '-y'|'--yes')
            yes="yes"
            shift ;;
        '--')
            shift
            break ;;
    esac
done

if [[ "${help}" == "yes" ]]; then
    _help 0
fi

if [[ ${EUID} -ne 0 ]]; then
    echo "This script must be run as root."
    echo
    echo "Get help:"
    echo "${appname} --help"
    exit 1
fi

if [[ "${SCRIPTUSER}" = 'root' || -z "${SUDO_USER:-}" ]]; then
    echo
    echo 'The script must be run from a _user session_ using sudo!'
    echo 'Aborting...'
    exit 1
fi


for _zfspubkey in "${ZFSPUBKEYS[@]}"; do
    if ! sudo --user "${SCRIPTUSER}" gpg --list-public-keys "${_zfspubkey}" &> /dev/null; then
        zfsmissingkeys+=("${_zfspubkey}")
    fi
done

if [[ -n "${zfsmissingkeys[*]}" ]]; then
    echo "Missing OpenZFS public keys: ${zfsmissingkeys[*]}"
    echo
    if [[ "${yes}" != "yes" ]]; then
        if [[ ! -t 0 ]]; then
            echo "Pass --yes to retrieve missing OpenZFS public keys, aborting!"
            exit 1
        fi
        read -r -n1 -p 'Retrieve missing OpenZFS public keys (N/y)? '
        echo
        if [[ ! "${REPLY}" =~ ^[Yy]$ ]]; then
            echo 'Operation canceled by user!'
            exit 0
        fi
    fi
    for _zfsmissingkey in "${zfsmissingkeys[@]}"; do
        if ! sudo --user "${SCRIPTUSER}" gpg --recv-keys "${_zfsmissingkey}"; then
            echo
            echo "Retrieving OpenZFS public key ${_zfsmissingkey} failed, aborting!"
            echo
            exit 1
        fi
    done
fi

auiwork="$(mktemp -d "${WD}/auiwork.XXXXXXXX")"
ar64="${auiwork}/archroot64"
mkdir -p -- "${ar64}"
chmod 0755 -- "${auiwork}" "${ar64}"
trap _cleanup EXIT
_init
_create_archroot64
_build_zfs

cd "${WD}"

echo
echo 'Done!'
echo
echo "${zfspackages[*]}" | sed 's|\s|\n|g' | sed 's|.*/||'
echo
echo 'ZFS packages directory location: '\'"${pkgdest}"\'
echo

# vim:ts=4:sw=4:et:
