#!/usr/bin/env bash
set -euo pipefail

ARCH_AMD64=amd64
ARCH_ARM64=arm64
ARCH=
VERSION=
GITHUB_TOKEN=${GITHUB_TOKEN:-}
GCB_URL=https://storage.googleapis.com/cri-o

usage() {
    printf "Usage: %s [-a ARCH] [ -t TAG|SHA ] [ -b BUCKET ] [ -h ]\n\n" "$(basename "$0")"
    echo "Possible arguments:"
    printf "  -a\tArchitecture to retrieve (defaults to the local system)\n"
    printf "  -t\tVersion tag or full length SHA to be used (defaults to the latest available main)\n"
    printf "  -b\tName of the GCS bucket for downloading artifacts (defaults to 'cri-o')\n"
    printf "  -h\tShow this help message\n"
}

parse_args() {
    echo "Welcome to the CRI-O install script!"

    while getopts 'a:b:t:h' OPTION; do
        case "$OPTION" in
        a)
            ARCH="$OPTARG"
            echo "Using architecture: $ARCH"
            ;;
        t)
            VERSION="$OPTARG"
            echo "Using version: $VERSION"
            ;;
        b)
            GCB_URL="https://storage.googleapis.com/$OPTARG"
            echo "Using GCS bucket: gs://$OPTARG"
            ;;
        h)
            usage
            exit 0
            ;;
        ?)
            usage
            exit 1
            ;;
        esac
    done

    if [[ $ARCH == "" ]]; then
        LOCAL_ARCH=$(uname -m)
        if [[ "$LOCAL_ARCH" == x86_64 ]]; then
            ARCH=$ARCH_AMD64
        elif [[ "$LOCAL_ARCH" == aarch64 ]]; then
            ARCH=$ARCH_ARM64
        else
            echo "Unsupported local architecture: $LOCAL_ARCH"
            exit 1
        fi
        echo "No architecture provided, using: $ARCH"
    fi
}

verify_requirements() {
    CMDS=(curl jq tar)
    echo "Checking if all commands are available: ${CMDS[*]}"
    for CMD in "${CMDS[@]}"; do
        if ! command -v "$CMD" >/dev/null; then
            echo "Command $CMD not available but required"
            exit 1
        fi
    done
}

curl_retry() {
    curl -sSfL --retry 5 --retry-delay 3 "$@"
}

latest_version() {
    GH_API_URL="https://api.github.com/repos/cri-o/cri-o/actions/runs?per_page=100"

    GH_HEADERS=(-H "Accept: application/vnd.github.v3+json")
    if [[ $GITHUB_TOKEN != "" ]]; then
        GH_HEADERS+=(-H "Authorization: token $GITHUB_TOKEN")
    fi

    if [[ $VERSION == "" ]]; then
        echo Searching for latest version via marker file
        COMMIT=$(curl_retry "$GCB_URL/latest-main.txt")
        if [[ "$COMMIT" != "" ]]; then
            VERSION=$COMMIT
            echo "Found latest version $VERSION"
            return
        fi

        echo No version marker found, trying latest successful GitHub actions run
        echo Export a GITHUB_TOKEN environment variable to avoid GitHub API rate limits
        PAGE=0
        while true; do
            PAGE=$((PAGE + 1))
            echo Searching GitHub actions page $PAGE

            URL="${GH_API_URL}&page=$PAGE"
            JSON=$(curl_retry "${GH_HEADERS[@]}" "$URL")

            if echo "$JSON" | jq -e '.workflow_runs | length == 0' >/dev/null; then
                echo No more GitHub action runs available to search
                exit 1
            fi

            FOUND=$(echo "$JSON" |
                jq -r '.workflow_runs | map(select(.name == "test" and .head_branch == "main" and .conclusion == "success")) | first | .head_sha')

            if [[ $FOUND == null ]]; then
                continue
            fi

            VERSION=$FOUND
            echo "Using latest successful GitHub action main: $VERSION"
            break
        done

        if [[ $VERSION == "" ]]; then
            echo "Unable to find successful GitHub action"
            exit 1
        fi
    fi
}

prepare() {
    parse_args "$@"
    verify_requirements
    latest_version
}

prepare "$@"

TARBALL=cri-o.$ARCH.$VERSION.tar.gz
SPDX=$TARBALL.spdx
BASE_URL=$GCB_URL/artifacts

TMPDIR="$(mktemp -d)"
trap 'rm -rf -- "$TMPDIR"' EXIT

if command -v cosign >/dev/null; then
    echo "Found cosign, verifying signatures"
    pushd "$TMPDIR" >/dev/null

    FILES=(
        "$TARBALL"
        "$TARBALL.sig"
        "$TARBALL.cert"
        "$SPDX"
        "$SPDX.sig"
        "$SPDX.cert"
    )
    for FILE in "${FILES[@]}"; do
        echo "Downloading $FILE"
        curl_retry "$BASE_URL/$FILE" -o "$FILE"
    done

    BLOBS=(
        "$TARBALL"
        "$SPDX"
    )
    for BLOB in "${BLOBS[@]}"; do
        echo "Verifying blob $BLOB"
        COSIGN_EXPERIMENTAL=1 cosign verify-blob "$BLOB" \
            --certificate-identity-regexp '.*' \
            --certificate-oidc-issuer-regexp '.*' \
            --signature "$BLOB.sig" \
            --certificate "$BLOB.cert"
    done

    tar xfz "$TARBALL"
    TARBALL_DIR=cri-o

    if command -v bom >/dev/null; then
        echo "Found bom tool, verifying bill of materials"
        bom validate -e "$SPDX" -d "$TARBALL_DIR"
    fi

    pushd "$TARBALL_DIR"
else
    TARBALL_URL=$BASE_URL/$TARBALL
    echo "Downloading $TARBALL_URL to $TMPDIR"
    curl_retry "$TARBALL_URL" | tar xfz - --strip-components=1 -C "$TMPDIR"
    pushd "$TMPDIR"
fi

echo Installing CRI-O

# In this section we include the contents of
# contrib/bundle/install-paths and contrib/bundle/install

# INCLUDE
DESTDIR=${DESTDIR:-}
PREFIX=${PREFIX:-/usr/local}
ETCDIR=${ETCDIR:-/etc}
CONTAINERS_DIR=${CONTAINERS_DIR:-$ETCDIR/containers}
CNIDIR=${CNIDIR:-$ETCDIR/cni/net.d}
BINDIR=${BINDIR:-$PREFIX/bin}
MANDIR=${MANDIR:-$PREFIX/share/man}
OCIDIR=${OCIDIR:-$PREFIX/share/oci-umount/oci-umount.d}
BASHINSTALLDIR=${BASHINSTALLDIR:-$PREFIX/share/bash-completion/completions}
FISHINSTALLDIR=${FISHINSTALLDIR:-$PREFIX/share/fish/completions}
ZSHINSTALLDIR=${ZSHINSTALLDIR:-$PREFIX/share/zsh/site-functions}
SYSTEMDDIR=${SYSTEMDDIR:-$PREFIX/lib/systemd/system}
OPT_CNI_BIN_DIR=${OPT_CNI_BIN_DIR:-/opt/cni/bin}

SELINUX=
if selinuxenabled 2>/dev/null; then
    SELINUX=-Z
fi
ARCH=${ARCH:-amd64}
set -x
install $SELINUX -d -m 755 "$DESTDIR$CNIDIR"
install $SELINUX -D -m 755 -t "$DESTDIR$OPT_CNI_BIN_DIR" cni-plugins/*
install $SELINUX -D -m 644 -t "$DESTDIR$CNIDIR" contrib/11-crio-ipv4-bridge.conflist
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/conmon
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/crictl
install $SELINUX -d -m 755 "$DESTDIR$BASHINSTALLDIR"
install $SELINUX -d -m 755 "$DESTDIR$FISHINSTALLDIR"
install $SELINUX -d -m 755 "$DESTDIR$ZSHINSTALLDIR"
install $SELINUX -d -m 755 "$DESTDIR$CONTAINERS_DIR"
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/crio-status
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/crio
install $SELINUX -D -m 644 -t "$DESTDIR$ETCDIR" etc/crictl.yaml
install $SELINUX -D -m 644 -t "$DESTDIR$OCIDIR" etc/crio-umount.conf
install $SELINUX -D -m 644 -t "$DESTDIR$ETCDIR/crio" etc/crio.conf
install $SELINUX -D -m 644 -t "$DESTDIR$ETCDIR/crio/crio.conf.d" etc/10-crun.conf
install $SELINUX -D -m 644 -t "$DESTDIR$MANDIR/man5" man/crio.conf.5
install $SELINUX -D -m 644 -t "$DESTDIR$MANDIR/man5" man/crio.conf.d.5
install $SELINUX -D -m 644 -t "$DESTDIR$MANDIR/man8" man/crio-status.8
install $SELINUX -D -m 644 -t "$DESTDIR$MANDIR/man8" man/crio.8
install $SELINUX -D -m 644 -t "$DESTDIR$BASHINSTALLDIR" completions/bash/crio
install $SELINUX -D -m 644 -t "$DESTDIR$FISHINSTALLDIR" completions/fish/crio.fish
install $SELINUX -D -m 644 -t "$DESTDIR$ZSHINSTALLDIR" completions/zsh/_crio
install $SELINUX -D -m 644 -t "$DESTDIR$CONTAINERS_DIR" contrib/policy.json
install $SELINUX -D -m 644 -t "$DESTDIR$SYSTEMDDIR" contrib/crio.service
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/pinns
install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/crun

# only install runc if it's not already in the path
if ! command -v runc; then
    install $SELINUX -D -m 755 -t "$DESTDIR$BINDIR" bin/runc
fi

if [ -n "$SELINUX" ]; then
    if command -v chcon >/dev/null; then
        chcon -u system_u -r object_r -t container_runtime_exec_t \
            "$DESTDIR$BINDIR/crio" \
            "$DESTDIR$BINDIR/crio-status" \
            "$DESTDIR$BINDIR/crun"

        if [ "$ARCH" = amd64 ]; then
            chcon -u system_u -r object_r -t container_runtime_exec_t \
                "$DESTDIR$BINDIR/runc"
        fi

        chcon -u system_u -r object_r -t bin_t \
            "$DESTDIR$BINDIR/conmon" \
            "$DESTDIR$BINDIR/crictl" \
            "$DESTDIR$BINDIR/pinns"

        chcon -R -u system_u -r object_r -t bin_t \
            "$DESTDIR$OPT_CNI_BIN_DIR"

        chcon -R -u system_u -r object_r -t container_config_t \
            "$DESTDIR$ETCDIR/crio" \
            "$DESTDIR$OCIDIR/crio-umount.conf"

        chcon -R -u system_u -r object_r -t systemd_unit_file_t \
            "$DESTDIR$SYSTEMDDIR/crio.service"
    fi
fi
