#!/bin/sh
# initMAX Alpine installer for Zabbix widget packages (apk) - the Docker path.
#
# Published at https://repo.initmax.com/zabbix/release/setup-alpine.sh and
# meant for the frontend stage of a customer's own Dockerfile:
#
#   RUN curl -fsSL https://repo.initmax.com/zabbix/release/setup-alpine.sh -o /tmp/initmax-repo.sh \
#    && sh /tmp/initmax-repo.sh --edition free -- apk add --no-cache zabbix-module-<product> \
#    && rm -f /tmp/initmax-repo.sh
#
#   RUN --mount=type=secret,id=initmax_repo_token,required=true \
#       sh /tmp/initmax-repo.sh --edition pro --token-file /run/secrets/initmax_repo_token \
#         -- apk add --no-cache zabbix-module-<product>
#
# What it does, and deliberately nothing more:
#   1. verifies root, Alpine, a supported architecture and that the wrapped
#      command is `apk add ... zabbix-module-*`;
#   2. installs the initMAX APK public key into /etc/apk/keys (pinned by
#      SHA256 below) - the only change that outlives this script;
#   3. downloads the signed repository index and the requested packages into a
#      temporary local repository, authenticating with the repo token when one
#      is given (never on the command line, never in a persistent file);
#   4. runs the apk command against that temporary repository with network
#      access off, so apk verifies the index signature and every package;
#   5. removes the temporary repository. /etc/apk/repositories, the apk cache
#      and the apk world are left as they were - other repositories and
#      earlier initMAX products stay untouched, and a rebuild re-runs the
#      whole block, which is how a Docker image is updated.
#
# Options
#   --edition free|pro       repository channel (required)
#   --token-file FILE        initMAX repo token (portal.initmax.com/profile/tokens);
#                            required for pro, optional for a gated free channel
#   --base URL               repository base (default https://repo.initmax.com/zabbix)
#   --                       everything after it is the apk command to run
set -eu
umask 077

BASE="${INITMAX_REPO_BASE:-https://repo.initmax.com/zabbix}"
KEY_NAME="packages@initmax.com-7033a517.rsa.pub"
KEY_SHA256="7033a51794a7f96e7bb383014e08abf652dd1ccb5a9752e03e7ac09b320ecdf2"
EDITION=""
TOKEN_FILE=""

usage() {
    echo "usage: setup-alpine.sh --edition free|pro [--token-file FILE] [--base URL] -- apk add [options] zabbix-module-<product>..." >&2
    exit 2
}
log() { echo "initMAX: $*" >&2; }

while [ "$#" -gt 0 ]; do
    case "$1" in
        --edition)    [ "$#" -ge 2 ] || usage; EDITION=$2; shift 2 ;;
        --token-file) [ "$#" -ge 2 ] || usage; TOKEN_FILE=$2; shift 2 ;;
        --base)       [ "$#" -ge 2 ] || usage; BASE=$2; shift 2 ;;
        --)           shift; break ;;
        -h|--help)    usage ;;
        *)            log "unknown option: $1"; usage ;;
    esac
done
case "$EDITION" in free|pro) ;; *) log "--edition must be free or pro"; usage ;; esac
[ "$#" -ge 3 ] && [ "$1" = apk ] && [ "$2" = add ] || { log "the wrapped command must be: apk add [options] zabbix-module-<product>..."; usage; }
shift 2
PACKAGES=""
for arg in "$@"; do
    case "$arg" in
        -*) ;;
        *)
            printf '%s' "$arg" | grep -Eq '^zabbix-module-[a-z0-9-]+(=[A-Za-z0-9._-]+)?$' \
                || { log "not an initMAX package: $arg"; exit 2; }
            PACKAGES="$PACKAGES $arg" ;;
    esac
done
[ -n "$PACKAGES" ] || { log "no zabbix-module-* package named"; usage; }

[ "$(id -u)" = 0 ] || { log "run as root (USER root before this RUN, USER 1997 after it)"; exit 1; }
[ -f /etc/alpine-release ] || { log "this installer is for Alpine based images; use the DEB/RPM repository on Debian/Ubuntu/RHEL based images"; exit 1; }
command -v apk >/dev/null 2>&1 || { log "apk not found"; exit 1; }
command -v curl >/dev/null 2>&1 || { log "curl is required (apk add --no-cache curl)"; exit 1; }
ARCH=$(apk --print-arch)
case "$ARCH" in x86_64|aarch64) ;; *) log "unsupported architecture: $ARCH"; exit 1 ;; esac

TMP=$(mktemp -d /tmp/initmax-apk.XXXXXX)
cleanup() { rm -rf "$TMP"; }
trap cleanup EXIT HUP INT TERM
mkdir -p "$TMP/repo/$ARCH"
CURL_CONF="$TMP/curl.conf"
: > "$CURL_CONF"
if [ -n "$TOKEN_FILE" ]; then
    [ -r "$TOKEN_FILE" ] || { log "token file not readable: $TOKEN_FILE"; exit 1; }
    TOKEN=$(tr -d '[:space:]' < "$TOKEN_FILE")
    printf '%s' "$TOKEN" | grep -Eq '^imxk_[0-9a-f]{32}$' || { log "the token file does not hold an initMAX repo token (imxk_...)"; exit 1; }
    printf 'user = "token:%s"\n' "$TOKEN" > "$CURL_CONF"
    unset TOKEN
elif [ "$EDITION" = pro ]; then
    log "the PRO channel needs --token-file (a Docker build secret, see portal.initmax.com/profile/tokens)"
    exit 1
fi

# TLS 1.2+ and https only for the real repository; a plain-http base is
# accepted solely for an internal mirror or a test fixture and says so.
PROTO='=https'
case "$BASE" in http://*) PROTO='=http,https'; log "WARNING: plain-http repository base $BASE" ;; esac

fetch() {  # fetch <relative path> <output>; the token travels only in the curl config
    if ! curl --fail --silent --show-error --location --proto "$PROTO" --tlsv1.2 \
            --retry 3 --retry-delay 2 --config "$CURL_CONF" "$BASE/$1" -o "$2"; then
        log "download failed: $BASE/$1"
        [ -s "$CURL_CONF" ] || log "a 401 means this channel needs --token-file"
        exit 1
    fi
}

# 1. public key, pinned. Installed system-wide so apk trusts the index and the
#    packages; a public key is not a secret and this is the only persistent change.
fetch "release/$KEY_NAME" "$TMP/$KEY_NAME"
printf '%s  %s\n' "$KEY_SHA256" "$TMP/$KEY_NAME" | sha256sum -c - >/dev/null \
    || { log "public key checksum mismatch - refusing to continue"; exit 1; }
if ! cmp -s "$TMP/$KEY_NAME" "/etc/apk/keys/$KEY_NAME" 2>/dev/null; then
    install -m 0644 "$TMP/$KEY_NAME" "/etc/apk/keys/$KEY_NAME"
fi

# 2. signed index of the channel
fetch "$EDITION/apk/$ARCH/APKINDEX.tar.gz" "$TMP/repo/$ARCH/APKINDEX.tar.gz"
printf '%s\n' "$TMP/repo" > "$TMP/repositories"
INDEX=$(tar -xzOf "$TMP/repo/$ARCH/APKINDEX.tar.gz" APKINDEX)

# 3. the packages the command names, newest version unless pinned (name=version)
for spec in $PACKAGES; do
    name=${spec%%=*}
    want=""
    [ "$spec" = "$name" ] || want=${spec#*=}
    version=$(printf '%s\n' "$INDEX" | awk -v p="$name" -F: '
        $1 == "P" { pkg = $2 }
        $1 == "V" && pkg == p { print $2 }' | tail -1)
    [ -n "$want" ] && version=$want
    [ -n "$version" ] || { log "package not in the $EDITION channel: $name"; exit 1; }
    fetch "$EDITION/apk/$ARCH/$name-$version.apk" "$TMP/repo/$ARCH/$name-$version.apk"
done

# 4. install from the temporary repository only; apk verifies signatures itself
apk --repositories-file "$TMP/repositories" --no-network --no-progress add --upgrade "$@"
log "installed from the initMAX $EDITION channel:$PACKAGES"
