#!/usr/bin/sh
#
# Copyright (C) 2014, 2017, 2021, 2025  Etersoft
# Copyright (C) 2014, 2017, 2021, 2025  Vitaly Lipatov <lav@etersoft.ru>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

# Check if any argument is a valid task format: 123456, #123456, 123456/pkg, task/123456, task/123456/pkg
is_taskarg() {
    local arg
    for arg in "$@"; do
        [[ "$arg" =~ ^[[:space:]]*#?[0-9]+$ ]] && return 0
        [[ "$arg" =~ ^[0-9]+/[^/]+$ ]] && return 0
        [[ "$arg" =~ ^task/[0-9]+(/[^/]+)?$ ]] && return 0
    done
    return 1
}


is_taskurl()
{
    local pkg_urls="$1"
    [ -n "$pkg_urls" ] || return

    echo "$pkg_urls" | grep -q -E "https://packages.altlinux.org/ru/tasks/[0-9]+/*$" || \
    echo "$pkg_urls" | grep -q -E "https://git.altlinux.org/tasks/[0-9]+/*$" || \
    echo "$pkg_urls" | grep -q -E "https://git.altlinux.org/tasks/archive/done/_[0-9]+/[0-9]+/*$"
}

# Extract task number from various formats: 123456, #123456, 123456/pkg, task/123456, task/123456/pkg
get_tasknumber_from_arg()
{
    local arg="$1"
    local task=""
    # strip leading spaces and #
    arg="$(echo "$arg" | sed -e 's|^ *#*||')"
    case "$arg" in
        task/*/*)
            task=$(printf "%s" "$arg" | cut -d/ -f2)
            ;;
        task/*)
            task=$(printf "%s" "$arg" | cut -d/ -f2)
            ;;
        */*)
            task=$(printf "%s" "$arg" | cut -d/ -f1)
            ;;
        *)
            task="$arg"
            ;;
    esac
    isnumber "$task" && echo "$task"
}

# Extract package name from task arg formats: 123456/pkg, task/123456/pkg
get_pkgname_from_taskarg()
{
    local arg="$1"
    case "$arg" in
        task/*/*)
            printf "%s" "$arg" | cut -d/ -f3
            ;;
        task/[0-9]*)
            # task/123456 - no package name
            ;;
        [0-9]*/*)
            # 123456/pkg - first part is task number
            printf "%s" "$arg" | cut -d/ -f2
            ;;
    esac
}

ALTTASKURL="http://git.altlinux.org/tasks"

get_task_state()
{
    local tn="$1"
    fetch_url "$ALTTASKURL/$tn/task/state" 2>/dev/null
}

get_task_status()
{
    local tn="$1"
    docmd eget --check-url $ALTTASKURL/$tn/plan/add-bin
}

# print the branch of the task (sisyphus, p11, c10f2, ...)
get_task_repo()
{
    local tn="$1"
    fetch_url "$ALTTASKURL/$tn/task/repo" 2>/dev/null
}

# check if apt vendor (the key for [vendor] in sources.list) is defined in vendors.list
__alt_has_vendor()
{
    grep -qs "^[[:space:]]*simple-key[[:space:]]*\"$1\"" /etc/apt/vendors.list /etc/apt/vendors.list.d/*.list
}

# print [vendor] for the task repo: girar signs it by the key of the task branch
get_task_sign()
{
    local tn="$1"
    local branch vendor
    branch="$(get_task_repo "$tn")"
    case "$branch" in
        sisyphus)
            vendor="alt"
            ;;
        p[0-9]*)
            vendor="$branch"
            ;;
        c[0-9]*)
            vendor="updates"
            ;;
        *)
            return
            ;;
    esac
    if ! __alt_has_vendor "$vendor" ; then
        warning 'Vendor $vendor is missed in vendors.list, the signature of task $tn repo will not be checked'
        return
    fi
    echo "[$vendor]"
}

get_task_arepo_status()
{
    local tn="$1"
    docmd eget --check-url $ALTTASKURL/$tn/plan/arepo-add-x86_64-i586
}

get_task_packages_list()
{
    local tn="$1"
    local res

    res="$(fetch_url $ALTTASKURL/$tn/plan/add-bin)" || return
    echo "$res" | cut -f1-3 | grep -E "(noarch|$DISTRARCH)$" | cut -f1
}

get_task_arepo_packages_list()
{
    local tn="$1"
    local res

    res="$(fetch_url $ALTTASKURL/$tn/plan/arepo-add-x86_64-i586)" || return
    echo "$res" | cut -f1
}


get_task_packages()
{
    local arg tn
    for arg in "$@" ; do
        is_taskarg "$arg" || continue
        tn=$(get_tasknumber_from_arg "$arg")
        if ! get_task_status "$tn" ; then
            local state
            state="$(get_task_state "$tn")"
            if [ -n "$state" ] ; then
                warning 'Task $tn state is $state, packages are not yet available.'
            else
                warning 'Task $tn not found.'
            fi
            continue
        fi
        get_task_packages_list "$tn"
        [ "$DISTRARCH" = "x86_64" ] || continue
        get_task_arepo_status "$tn" || continue
        get_task_arepo_packages_list "$tn"
    done
}

# check if dnf5 takes repos from /etc/apt/sources.list* (libdnf5-plugin-apt)
__alt_dnf_uses_apt_sources()
{
    [ "$PMTYPE" = "dnf5-rpm" ] || return
    local conf=/etc/dnf/libdnf5-plugins/apt.conf
    grep -qsE '^[[:space:]]*enabled[[:space:]]*=[[:space:]]*(1|yes|true|on)[[:space:]]*$' $conf || return
    ! grep -qsE '^[[:space:]]*sources[[:space:]]*=[[:space:]]*(0|no|false|off)[[:space:]]*$' $conf
}

# Honors EPM_YUM_REPOS_DIR for test isolation
__get_yum_repos_dir()
{
    echo "${EPM_YUM_REPOS_DIR:-/etc/yum.repos.d}"
}

# Print "type baseurl" for enabled repos in /etc/yum.repos.d (type is apt-rpm or rpm-md)
__get_alt_dnf_repos()
{
    local f
    for f in "$(__get_yum_repos_dir)"/*.repo ; do
        [ -r "$f" ] || continue
        awk '
            function flush() { if (url != "" && enabled) print (type == "apt-rpm" ? type : "rpm-md"), url }
            /^[[:space:]]*\[/ { flush(); url = ""; type = ""; enabled = 1; next }
            /^[[:space:]]*baseurl[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); url = $1; next }
            /^[[:space:]]*type[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); type = $1; next }
            /^[[:space:]]*enabled[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); enabled = ($1 ~ /^(1|yes|true|on)$/); next }
            END { flush() }
        ' "$f"
    done
}

# print type of repos in /etc/yum.repos.d for dnf on ALT:
# apt-rpm if APT-RPM repos (type=apt-rpm) are configured, rpm-md otherwise
# EPM_DNF_REPO_TYPE overrides the detection (to keep the type while repos are recreated)
__alt_dnf_repo_type()
{
    if [ -n "$EPM_DNF_REPO_TYPE" ] ; then
        echo "$EPM_DNF_REPO_TYPE"
        return
    fi
    if grep -qsE '^[[:space:]]*type[[:space:]]*=[[:space:]]*apt-rpm[[:space:]]*$' "$(__get_yum_repos_dir)"/*.repo ; then
        echo "apt-rpm"
        return
    fi
    echo "rpm-md"
}

# check if dnf uses APT-RPM repos (type=apt-rpm) from /etc/yum.repos.d
__alt_dnf_uses_aptrpm_repos()
{
    case "$PMTYPE" in
        dnf-rpm|dnf5-rpm)
            ;;
        *)
            return 1
            ;;
    esac
    [ "$(__alt_dnf_repo_type)" = "apt-rpm" ]
}

# check if dnf reads APT-RPM repos (from sources.list via libdnf5-plugin-apt or type=apt-rpm repo files)
__alt_dnf_supports_aptrpm()
{
    __alt_dnf_uses_apt_sources || __alt_dnf_uses_aptrpm_repos
}

# print vendor (from /etc/apt/vendors.list) for ALT repo
# args: sisyphus|p11|...|deferred*
__alt_dnf_aptrpm_vendor()
{
    local repo="$1"
    local vendor
    case "$repo" in
        sisyphus|deferred*)
            vendor="alt"
            ;;
        c[0-9]*)
            vendor="cert8"
            ;;
        *)
            vendor="$repo"
            ;;
    esac
    if ! __alt_has_vendor "$vendor" ; then
        warning 'Vendor $vendor is missed in vendors.list, the signature of $repo repo will not be checked'
        return
    fi
    echo "$vendor"
}

# print root URL (with ALTLinux path) of the known ALT mirror used by APT-RPM repos of dnf
# (sources.list for libdnf5-plugin-apt or type=apt-rpm repo files), default ftp.basealt.ru
# EPM_DNF_APTRPM_MIRROR overrides the detection (to keep the mirror while repos are recreated)
# args: [repo] to prefer sources.list line for the same branch
__alt_dnf_aptrpm_mirror()
{
    if [ -n "$EPM_DNF_APTRPM_MIRROR" ] ; then
        echo "$EPM_DNF_APTRPM_MIRROR"
        return
    fi
    load_helper epm-sh-backend
    local url host mirror
    if __alt_dnf_uses_apt_sources && __get_alt_mirror_baseurl "$1" 2>/dev/null ; then
        return
    fi
    for url in $(__get_alt_dnf_repos | sed -n 's|^apt-rpm ||p') ; do
        host="$(echo "$url" | sed -nE 's|^https?://([^/]+).*|\1|p')"
        [ -n "$host" ] || continue
        mirror="$(__get_alt_mirror_url_by_host "$host")"
        [ -n "$mirror" ] && echo "$mirror" && return
    done
    echo "http://ftp.basealt.ru/pub/distributions/ALTLinux"
}

# print base url (without arch) of APT-RPM repo for dnf on ALT
# args: sisyphus|p11|...|deferred|deferred.org|deferred-devel|deferred-beta
__alt_dnf_aptrpm_baseurl()
{
    case "$1" in
        sisyphus|Sisyphus)
            echo "$(__alt_dnf_aptrpm_mirror sisyphus)/Sisyphus"
            ;;
        deferred)
            echo "https://download.etersoft.ru/pub/Etersoft/Sisyphus/Deferred"
            ;;
        deferred.org)
            echo "http://mirror.eterfund.org/pub/Etersoft/Sisyphus/Deferred"
            ;;
        deferred-devel)
            echo "https://download.etersoft.ru/pub/Etersoft/Sisyphus/Deferred_DEVEL"
            ;;
        deferred-beta)
            echo "https://download.etersoft.ru/pub/Etersoft/Sisyphus/Deferred_BETA"
            ;;
        p[0-9]*|c[0-9]*)
            echo "$(__alt_dnf_aptrpm_mirror "$1")/$1/branch"
            ;;
        *)
            return 1
            ;;
    esac
}

# print "arch component" for APT-RPM repo at URL with arch subdirs (f.i. task repo)
# args: <url>
__alt_dnf_aptrpm_probe_url()
{
    local url="$1"
    local arch rd comp
    for arch in $(__alt_dnf_archlist) ; do
        eget --check-url "$url/$arch/base/" 2>/dev/null || continue
        rd="$(eget --list "$url/$arch/RPMS.*" 2>/dev/null)"
        [ -n "$rd" ] || continue
        comp="$(echo "$rd" | head -n1 | sed -e 's|/*$||' -e 's|.*\.||')"
        [ "$comp" = "*" ] && continue
        echo "$arch $comp"
    done
}

# print dnf options to use APT-RPM repo without repo file
# args: <repo id> <baseurl> <component> [vendor]
__alt_dnf_aptrpm_repo_options()
{
    local id="$1"
    echo "--repofrompath=$id,$2 --setopt=$id.type=apt-rpm --setopt=$id.apt_component=$3${4:+ --setopt=$id.apt_vendor=$4}"
}

# export ALT vendor keys from alt-gpgkeys for dnf gpgcheck
# TODO: use a package with ALT keys in /etc/pki/rpm-gpg when it appears
__epm_addrepo_alt_dnf_gpgkey()
{
    local keyfile="$1"
    [ -s "$keyfile" ] && return

    local keyring=/usr/lib/alt-gpgkeys
    local vendors=/etc/apt/vendors.list.d/alt.list
    [ -d "$keyring" ] || fatal 'Missed $keyring (install alt-gpgkeys package).'
    assure_exists gpg gnupg2

    # 80EF7625 (ALT Security Team) is not in vendors list, but still signs some old Sisyphus packages
    local keys="80EF7625"
    [ -s "$vendors" ] && keys="$keys $(sed -n 's/.*Fingerprint "\([0-9A-F]*\)".*/\1/p' $vendors | sort -u)"

    local tmpfile
    tmpfile=$(mktemp) || fatal
    remove_on_exit $tmpfile
    a='' gpg --homedir $keyring --export --armor $keys >$tmpfile 2>/dev/null
    [ -s "$tmpfile" ] || fatal 'Can'\''t export ALT keys from $keyring'
    chmod 644 $tmpfile
    sudocmd mkdir -p "$(dirname "$keyfile")"
    sudocmd cp $tmpfile "$keyfile"
}

# rpm-md metadata for dnf on ALT is generated only on Etersoft mirror
ALT_DNF_MIRROR="http://download.etersoft.ru/pub"
# the key of repomd.xml.asc signatures on the mirror
ALT_DNF_MIRROR_KEYURL="$ALT_DNF_MIRROR/Etersoft/RPM-GPG-KEY-etersoft-mirror-metadata"
ALT_DNF_MIRROR_KEYFPR="24F2AD07095B62CC48D5AC7C81C29C26E0056291"
ALT_DNF_MIRROR_KEYFILE="/etc/pki/rpm-gpg/RPM-GPG-KEY-etersoft-mirror-metadata"

# print base url (without arch) of rpm-md repo for dnf on ALT
# args: sisyphus|p11|...|deferred|deferred.org|deferred-devel|deferred-beta
__alt_dnf_baseurl()
{
    case "$1" in
        sisyphus|Sisyphus)
            echo "$ALT_DNF_MIRROR/ALTLinux/Sisyphus"
            ;;
        deferred)
            echo "$ALT_DNF_MIRROR/Etersoft/Sisyphus/Deferred"
            ;;
        deferred.org)
            echo "http://mirror.eterfund.org/pub/Etersoft/Sisyphus/Deferred"
            ;;
        deferred-devel)
            echo "$ALT_DNF_MIRROR/Etersoft/Sisyphus/Deferred_DEVEL"
            ;;
        deferred-beta)
            echo "$ALT_DNF_MIRROR/Etersoft/Sisyphus/Deferred_BETA"
            ;;
        p[0-9]*|c[0-9]*)
            echo "$ALT_DNF_MIRROR/ALTLinux/$1/branch"
            ;;
        *)
            return 1
            ;;
    esac
}

# print arch subdirs of ALT repo
__alt_dnf_archlist()
{
    echo "noarch"
    echo "$DISTRARCH"
    [ "$DISTRARCH" = "x86_64" ] && echo "x86_64-i586"
    return 0
}

# print dnf option to install kernel modules alongside (like kernel-image), as apt does with Allow-Duplicated
# args: [module package names] (installed kernel-modules-* are added)
__alt_dnf_installonly_opt()
{
    local cur
    cur="$(a='' $DNFCMD --dump-main-config 2>/dev/null | sed -n 's/^installonlypkgs = //p')"
    [ -n "$cur" ] || cur="kernel"
    local modules
    modules="$(estrlist union "$* $(a='' rpm -qa --qf '%{NAME}\n' 'kernel-modules-*' 2>/dev/null)")"
    [ -n "$modules" ] || return 0
    echo "--setopt=installonlypkgs=$cur,$(echo $modules | tr ' ' ',')"
}
