#!/bin/bash
set -eu # we don't want "pipefail" to implement idempotency

###
#
# Publishes all maven modules in the specified directory to maven.
#
# Usage: ./publib-maven [DIR]
#
# DIR is the root directory of a local maven repository (default `dist/java`).
#
# MAVEN_STAGING_PROFILE_ID         - Maven Central (sonatype) staging profile ID (e.g. 68a05363083174)
# MAVEN_USERNAME                   - User name for Sonatype
# MAVEN_PASSWORD                   - Password for Sonatype
# MAVEN_GPG_PRIVATE_KEY            - GPG private key where newlines are encoded as "\n"
# MAVEN_GPG_PRIVATE_KEY_FILE       - GPG private key file (mutually exclusive with MAVEN_GPG_PRIVATE_KEY)
# MAVEN_GPG_PRIVATE_KEY_PASSPHRASE - The passphrase of the provided key.
# MAVEN_DRYRUN                     - Set to "true" for a dry run
# MAVEN_SERVER_ID                  - Used in maven settings for credential lookup
# MAVEN_REPOSITORY_URL             - Deployment repository when not deploying to maven central
#
###

###
#
# HOW TO CREATE A GPG KEY?
#
#   gpg --full-generate-key (use RSA, 4096, passphrase)
#
# Export and publish the public key:
#   1. gpg -a --export > public.pem
#   2. Go to https://keyserver.ubuntu.com/ and submit the public key
#
# Export and use the private key:
#   1. gpg -a --export-secret-keys <fingerprint> > private.pem
#   2. Set MAVEN_GPG_PRIVATE_KEY_FILE =t point to `private.pem`
#      or
#   3. You can also export the private key to a single line where newlines are encoded
#      as "\n" and then assign it to MAVEN_GPG_PRIVATE_KEY.
#      MAVEN_GPG_PRIVATE_KEY="$(echo $(cat -e private.pem) | sed 's/\$ /\\n/g' | sed 's/\$$//')"
#
###

readonly CENTRAL_SERVER="ossrh"

# -------------------------------------------------------------------------------------------------
# we need this monstrosity because this script is installed as a symlink to node_modules/.bin
# and we need a way to find it's real location so we can reference stuff that's not in .bin
# https://stackoverflow.com/a/246128
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
scriptdir="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
# -------------------------------------------------------------------------------------------------

error() { echo "❌ $@"; exit 1; }

validate_central_parameters() {
    [[ -z "${MAVEN_STAGING_PROFILE_ID:-}" ]] && error "MAVEN_STAGING_PROFILE_ID is required"
    return 0
}

validate_non_central_parameters() {
    [[ -z "${MAVEN_SERVER_ID:-}" ]] && error "MAVEN_SERVER_ID is required"
    [[ -z "${MAVEN_REPOSITORY_URL:-}" ]] && error "MAVEN_REPOSITORY_URL is required"
    return 0
}

validate_signing_parameters() {
    [[ -z "${MAVEN_GPG_PRIVATE_KEY_PASSPHRASE:-}" ]] && error "MAVEN_GPG_PRIVATE_KEY_PASSPHRASE is required"
    [[ -z "${MAVEN_GPG_PRIVATE_KEY_FILE:-}" ]] && [[ -z "${MAVEN_GPG_PRIVATE_KEY:-}" ]] && error "MAVEN_GPG_PRIVATE_KEY_FILE or MAVEN_GPG_PRIVATE_KEY is required"
    [[ -n "${MAVEN_GPG_PRIVATE_KEY:-}" ]] && [[ -n "${MAVEN_GPG_PRIVATE_KEY_FILE:-}" ]] && error "Cannot specify both MAVEN_GPG_PRIVATE_KEY and MAVEN_GPG_PRIVATE_KEY_FILE"
    return 0
}

validate_parameters() {
    [[ -z "${MAVEN_USERNAME:-}" ]] && error "MAVEN_USERNAME is required"
    [[ -z "${MAVEN_PASSWORD:-}" ]] && error "MAVEN_PASSWORD is required"

    if ${is_central}; then
        validate_central_parameters
    else
        validate_non_central_parameters
    fi

    if (${is_signed} && ! ${is_central}); then
        error "Package signing is currently only supported for Maven Central"
    fi

    if (${is_signed} || ${is_central}); then
        validate_signing_parameters
    fi
}

import_gpg_key() {
    #---------------------------------------------------------------------------------------------------------------------------------------
    # Import private GPG key
    #---------------------------------------------------------------------------------------------------------------------------------------

    echo "Importing GPG key..." >&2

    # GnuPG will occasionally bail out with "gpg: <whatever> failed: Inappropriate ioctl for device", the following attempts to fix
    export GNUPGHOME=$(mktemp -d)
    export GPG_TTY=$(tty)

    if [ -n "${MAVEN_GPG_PRIVATE_KEY:-}" ]; then
        MAVEN_GPG_PRIVATE_KEY_FILE="${GNUPGHOME}/private.pem"
        echo -e "${MAVEN_GPG_PRIVATE_KEY}" > ${MAVEN_GPG_PRIVATE_KEY_FILE}
    fi

    gpg --allow-secret-key-import --batch --yes --no-tty --import "${MAVEN_GPG_PRIVATE_KEY_FILE}" || {
        error "GPG key import failed"
    }

    gpg_key_id=$(gpg --list-keys --with-colons | grep pub | cut -d: -f5)
    echo "gpg_key_id=${gpg_key_id}"
}

create_maven_settings() {
    # Create a settings.xml file with the user+password for maven
    mvn_settings="${workdir}/mvn-settings.xml"
    if ${is_signed}; then
        cat > ${mvn_settings} <<-EOF
<?xml version="1.0" encoding="UTF-8" ?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                            http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <servers>
    <server>
      <id>${server_id}</id>
      <username>\${env.MAVEN_USERNAME}</username>
      <password>\${env.MAVEN_PASSWORD}</password>
    </server>
    <server>
      <id>gpg.passphrase</id>
      <passphrase>\${env.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE}</passphrase>
    </server>
  </servers>
</settings>
EOF
    else
        cat > ${mvn_settings} <<-EOF
<?xml version="1.0" encoding="UTF-8" ?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                            http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <servers>
    <server>
      <id>${server_id}</id>
      <username>\${env.MAVEN_USERNAME}</username>
      <password>\${env.MAVEN_PASSWORD}</password>
    </server>
  </servers>
</settings>
EOF
    fi
}

sign_artifacts() {
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo " Preparing repository"
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

    # on a mac, --pinentry-mode to "loopback" are required and I couldn't find a
    # way to do so via -Dgpg.gpgArguments or the settings file, so here we are.
    local gpg_exec="gpg"
    if [[ "$(uname)" == "Darwin" ]]; then
        gpg_exec="/tmp/publib-gpg.sh"
        echo "#!/bin/bash" > $gpg_exec
        echo "exec gpg --pinentry-mode loopback \"\$@\"" >> $gpg_exec
        chmod +x $gpg_exec
    fi

    # Sign and stage our artifacts into a local directory
    for pom in ${poms}; do
        $mvn --settings=${mvn_settings} gpg:sign-and-deploy-file                    \
            -X                                                                      \
            -Durl=file://${staging}                                                 \
            -DrepositoryId=${server_id}                                             \
            -Dgpg.homedir=${GNUPGHOME}                                              \
            -Dgpg.keyname=0x${gpg_key_id}                                           \
            -Dgpg.executable=${gpg_exec}                                            \
            -DpomFile=${pom}                                                        \
            -Dfile=${pom/.pom/.jar}                                                 \
            -Dsources=${pom/.pom/-sources.jar}                                      \
            -Djavadoc=${pom/.pom/-javadoc.jar}
    done
}

deploy_central() {
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo " Deploying and closing repository..."
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

    staging_output="${workdir}/deploy-output.txt"
    $mvn --settings=${mvn_settings}                                                    \
        org.sonatype.plugins:nexus-staging-maven-plugin:1.6.13:deploy-staged-repository \
        -DrepositoryDirectory=${staging}                                               \
        -DnexusUrl=${MAVEN_ENDPOINT:-https://oss.sonatype.org}                         \
        -DserverId=${server_id}                                                        \
        -DautoReleaseAfterClose=true                                                   \
        -DstagingProgressTimeoutMinutes=30                                             \
        -DstagingProfileId=${MAVEN_STAGING_PROFILE_ID} | tee ${staging_output}

    # we need to consule PIPESTATUS sinec "tee" is the last command
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
        error "Repository deployment failed"
    fi

    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo " Releasing repository"
    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"

    # Extract the ID of the closed repository from the log output of "deploy-staged-repository"
    # This is because "deploy-staged-repository" doesn't seem to support autoReleaseAfterClose
    # See https://issues.sonatype.org/browse/OSSRH-42487
    if $dry_run; then
        echo 'Closing staging repository with ID "dummyrepo"' > ${staging_output}
    fi

    repository_id="$(cat ${staging_output} | grep "Closing staging repository with ID" | cut -d'"' -f2)"
    if [ -z "${repository_id}" ]; then
        echo "❌ Unable to extract repository ID from deploy-staged-repository output."
        echo "This means it failed to close or there was an unexpected problem."
        echo "At any rate, we can't release it. Sorry"
        exit 1
    fi

    echo "Repository ID: ${repository_id}"

    # Create a dummy pom.xml because the "release" goal needs one, but it doesn't care about it at all
    release_pom="${workdir}/release-pom.xml"
    cat > ${release_pom} <<HERE
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>dummy</groupId>
  <artifactId>dummy</artifactId>
  <version>0.0.0</version>
</project>
HERE

    # Release!
    release_output="${workdir}/release-output.txt"
    $mvn --settings ${mvn_settings} -f ${release_pom}                 \
        org.sonatype.plugins:nexus-staging-maven-plugin:1.6.13:release \
        -DserverId=${server_id}                                       \
        -DnexusUrl=${MAVEN_ENDPOINT:-https://oss.sonatype.org}        \
        -DstagingProfileId=${MAVEN_STAGING_PROFILE_ID}                \
        -DstagingProgressTimeoutMinutes=30                            \
        -DstagingRepositoryId=${repository_id} | tee ${release_output}

    # If release failed, check if this was caused because we are trying to publish
    # the same version again, which is not an error. The magic string "does not
    # allow updating artifact" for a ".pom" file indicates that we are trying to
    # override an existing version. Otherwise, fail!
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
        if cat ${release_output} | grep "does not allow updating artifact" | grep -q ".pom"; then
            echo "⚠️ Artifact already published. Skipping"
        else
            error "Release failed"
        fi
    fi
}

deploy_non_central() {
    release_output="${workdir}/release-output.txt"
    for pom in ${poms}; do
        $mvn --settings=${mvn_settings} deploy:deploy-file          \
            -Durl=${MAVEN_REPOSITORY_URL}                           \
            -DrepositoryId=${server_id}                             \
            -Dfile=${pom/.pom/.jar}                                 \
            -DpomFile=${pom}                                        \
            -Dsources=${pom/.pom/-sources.jar}                      \
            -Djavadoc=${pom/.pom/-javadoc.jar} | tee ${release_output}

        # If release failed, check if this was caused because we are trying to publish
        # the same version again, which is not an error. The magic string "409 Conflict"
        # indicates that we are trying to
        # override an existing version. Otherwise, fail!
        if [ ${PIPESTATUS[0]} -ne 0 ]; then
            if cat ${release_output} | grep -q "409 Conflict"; then
                echo "⚠️ Artifact already published. Skipping"
            else
                error "Release failed"
            fi
        fi
    done
}

main() {
    cd "${1:-"dist/java"}"

    server_id="${MAVEN_SERVER_ID:-"${CENTRAL_SERVER}"}"
    if [[ "${server_id}" == "${CENTRAL_SERVER}" ]]; then
        is_central=true
    else
        is_central=false
    fi

    if [[ -n "${MAVEN_GPG_PRIVATE_KEY_FILE:-}" ]] || [[ -n "${MAVEN_GPG_PRIVATE_KEY:-}" ]]; then
        is_signed=true
    else
        is_signed=false
    fi

    validate_parameters

    if [[ -n "${MAVEN_DRYRUN:-}" ]]; then
        echo "==========================================="
        echo "            🏜️ DRY-RUN MODE 🏜️"
        echo "==========================================="
        mvn="echo mvn"
        dry_run=true
    else
        mvn=mvn
        dry_run=false
    fi

    if ${is_signed}; then
        import_gpg_key
    fi

    poms="$(find . -name '*.pom')"
    if [ -z "${poms}" ]; then
        error "No JARS to publish: no .pom files found under $PWD"
    fi

    if ${is_central}; then
        echo "📦 Publishing to Maven Central"
    else
        echo "📦 Publishing to ${server_id}"
    fi
    
    staging=$(mktemp -d)
    workdir=$(mktemp -d)
    echo ${workdir}

    create_maven_settings

    if ${is_signed}; then
        # This currently only works with Maven Central
        sign_artifacts
    fi

    if ${is_central}; then
        deploy_central
    else
        deploy_non_central
    fi

    echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
    echo "✅ All Done!"
}

main "$@"; exit
