#!/bin/sh

umask 077

PROGRAM='wormhole-repository'
BASE_URL='https://rpm.wormhole.dns.army'

EXPECTED_KEY_SHA256='7aa6f34f9f2690b3bf22b3e6cc5056a7f37196731b5534af9236982af186d4a0'

KEY_FILE='/etc/pki/rpm-gpg/RPM-GPG-KEY-WORMHOLE'

TEST_MODE="${WORMHOLE_REPOSITORY_TEST_MODE:-0}"

OS_RELEASE="${WORMHOLE_REPOSITORY_OS_RELEASE:-/etc/os-release}"
ARCH="${WORMHOLE_REPOSITORY_ARCH:-}"

REPO_FILE="${WORMHOLE_REPOSITORY_REPO_FILE:-/etc/yum.repos.d/wormhole.repo}"
STATE_FILE="${WORMHOLE_REPOSITORY_STATE_FILE:-/var/lib/wormhole-repository/wormhole.repo.managed}"
BACKUP_DIR="${WORMHOLE_REPOSITORY_BACKUP_DIR:-/var/backups/wormhole-repository}"

REPO_DIR="${REPO_FILE%/*}"
STATE_DIR="${STATE_FILE%/*}"

fail() {
    printf '%s: %s\n' "$PROGRAM" "$1" >&2
    exit "${2:-1}"
}

sha256_file() {
    FILE="$1"

    SHA="$(
        sha256sum "$FILE" 2>/dev/null
    )" || return 1

    printf '%s\n' "${SHA%% *}"
}

load_os_release() {
    [ -r "$OS_RELEASE" ] ||
        fail "cannot read OS release file: $OS_RELEASE"

    ID=''
    VERSION_ID=''
    NAME=''
    PRETTY_NAME=''
    VERSION=''

    . "$OS_RELEASE"

    [ -n "${ID:-}" ] ||
        fail 'ID is missing from os-release'

    [ -n "${VERSION_ID:-}" ] ||
        fail 'VERSION_ID is missing from os-release'
}

detect_architecture() {
    if [ -z "$ARCH" ]; then
        ARCH="$(uname -m)"
    fi

    case "$ARCH" in
        x86_64)
            ;;
        *)
            fail "unsupported architecture: $ARCH"
            ;;
    esac
}

detect_repository_path() {
    load_os_release
    detect_architecture

    case "$ID" in
        fedora)
            case "$VERSION_ID" in
                35|36|37|38|39|40|41|42|43|44|45|46|47)
                    printf 'fedora/%s/x86_64\n' "$VERSION_ID"
                    return 0
                    ;;
                *)
                    fail "unsupported Fedora version: $VERSION_ID"
                    ;;
            esac
            ;;

        centos)
            case "$VERSION_ID" in
                8)
                    CENTOS_TEXT="${NAME:-} ${PRETTY_NAME:-} ${VERSION:-}"

                    case "$CENTOS_TEXT" in
                        *"CentOS Stream"*)
                            printf '%s\n' 'centos-stream/8/x86_64'
                            ;;
                        *)
                            printf '%s\n' 'centos/8/x86_64'
                            ;;
                    esac

                    return 0
                    ;;
                *)
                    fail "unsupported CentOS version: $VERSION_ID"
                    ;;
            esac
            ;;

        *)
            fail "unsupported distribution ID: $ID"
            ;;
    esac
}

verify_embedded_key() {
    if [ "$TEST_MODE" = '1' ]; then
        return 0
    fi

    [ -f "$KEY_FILE" ] &&
    [ ! -L "$KEY_FILE" ] ||
        fail "embedded repository key missing or invalid: $KEY_FILE"

    ACTUAL_KEY_SHA256="$(
        sha256_file "$KEY_FILE"
    )" ||
        fail 'cannot calculate embedded repository-key SHA256'

    [ "$ACTUAL_KEY_SHA256" = "$EXPECTED_KEY_SHA256" ] ||
        fail 'embedded repository-key SHA256 mismatch'
}

generate_repo() {
    RELATIVE="$1"

    cat <<EOF
# Managed by wormhole-repository.
# Generated by /usr/lib/wormhole-repository/configure.
# Local edits are preserved on package removal.

[wormhole]
name=Wormhole Repository
baseurl=$BASE_URL/$RELATIVE
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-WORMHOLE
sslverify=1
metadata_expire=6h
skip_if_unavailable=0
EOF
}

remove_managed_repository() {
    if [ -L "$REPO_FILE" ]; then
        printf '%s: preserving symlink at %s\n' \
            "$PROGRAM" \
            "$REPO_FILE" \
            >&2
    elif [ -e "$REPO_FILE" ] && [ ! -f "$REPO_FILE" ]; then
        printf '%s: preserving non-regular file at %s\n' \
            "$PROGRAM" \
            "$REPO_FILE" \
            >&2
    elif [ -f "$REPO_FILE" ] &&
         [ -f "$STATE_FILE" ] &&
         [ ! -L "$STATE_FILE" ]
    then
        REPO_SHA="$(
            sha256_file "$REPO_FILE"
        )"

        STATE_SHA="$(
            sha256_file "$STATE_FILE"
        )"

        if [ -n "$REPO_SHA" ] &&
           [ "$REPO_SHA" = "$STATE_SHA" ]
        then
            rm -f "$REPO_FILE"

            printf '%s: removed managed repository file %s\n' \
                "$PROGRAM" \
                "$REPO_FILE"
        else
            printf '%s: repository file modified; preserving %s\n' \
                "$PROGRAM" \
                "$REPO_FILE" \
                >&2
        fi
    fi

    if [ -f "$STATE_FILE" ] &&
       [ ! -L "$STATE_FILE" ]
    then
        rm -f "$STATE_FILE"
    fi

    rmdir "$STATE_DIR" 2>/dev/null || :
    return 0
}

write_repository() {
    RELATIVE="$1"

    if [ "$TEST_MODE" != '1' ]; then
        [ "$(id -u)" -eq 0 ] ||
            fail 'must run as root'
    fi

    verify_embedded_key

    if [ -L "$REPO_FILE" ]; then
        fail "refusing symlink repository file: $REPO_FILE"
    fi

    if [ -e "$REPO_FILE" ] &&
       [ ! -f "$REPO_FILE" ]
    then
        fail "refusing non-regular repository file: $REPO_FILE"
    fi

    if [ -L "$STATE_FILE" ]; then
        fail "refusing symlink state file: $STATE_FILE"
    fi

    if [ -e "$STATE_FILE" ] &&
       [ ! -f "$STATE_FILE" ]
    then
        fail "refusing non-regular state file: $STATE_FILE"
    fi

    install -d -m 0755 "$REPO_DIR" ||
        fail "cannot create repository directory: $REPO_DIR"

    install -d -m 0700 "$BACKUP_DIR" ||
        fail "cannot create backup directory: $BACKUP_DIR"

    install -d -m 0755 "$STATE_DIR" ||
        fail "cannot create state directory: $STATE_DIR"

    TMP_REPO="$(
        mktemp "$REPO_DIR/.wormhole.repo.tmp.XXXXXX"
    )" ||
        fail 'cannot create temporary repository file'

    generate_repo "$RELATIVE" > "$TMP_REPO" ||
        fail 'cannot generate repository configuration'

    chmod 0644 "$TMP_REPO" ||
        fail 'cannot set temporary repository-file mode'

    if [ -f "$REPO_FILE" ]; then
        CURRENT_SHA="$(
            sha256_file "$REPO_FILE"
        )"

        NEW_SHA="$(
            sha256_file "$TMP_REPO"
        )"

        if [ "$CURRENT_SHA" != "$NEW_SHA" ]; then
            BACKUP_STAMP="$(
                TZ=UTC date '+%Y%m%dT%H%M%SZ'
            )"

            BACKUP_FILE="$BACKUP_DIR/wormhole.repo.${BACKUP_STAMP}.$$"

            cp -p "$REPO_FILE" "$BACKUP_FILE" ||
                fail "cannot back up existing repository file"

            chmod 0600 "$BACKUP_FILE" ||
                fail "cannot protect repository backup"

            printf '%s: backed up previous repository file to %s\n' \
                "$PROGRAM" \
                "$BACKUP_FILE"
        fi
    fi

    mv -f "$TMP_REPO" "$REPO_FILE" ||
        fail 'cannot atomically install repository file'

    chmod 0644 "$REPO_FILE" ||
        fail 'cannot set repository-file mode'

    TMP_STATE="$(
        mktemp "$STATE_DIR/.wormhole.repo.managed.tmp.XXXXXX"
    )" ||
        fail 'cannot create temporary state file'

    cp "$REPO_FILE" "$TMP_STATE" ||
        fail 'cannot populate managed repository state'

    chmod 0644 "$TMP_STATE" ||
        fail 'cannot set state-file mode'

    mv -f "$TMP_STATE" "$STATE_FILE" ||
        fail 'cannot atomically install state file'

    printf '%s: configured %s\n' \
        "$PROGRAM" \
        "$REPO_FILE"

    printf '%s: repository path: %s\n' \
        "$PROGRAM" \
        "$RELATIVE"

    return 0
}

MODE="${1:-configure}"

case "$MODE" in
    configure)
        RELATIVE="$(
            detect_repository_path
        )" || exit $?

        write_repository "$RELATIVE"
        ;;

    --print)
        RELATIVE="$(
            detect_repository_path
        )" || exit $?

        generate_repo "$RELATIVE"
        ;;

    --remove-managed)
        remove_managed_repository
        ;;

    *)
        fail "unknown argument: $MODE"
        ;;
esac

exit 0
