#!/bin/sh
# Moneymoor installer — macOS and Linux.
#
# Generated by ariza from App::Moneymoor's ariza.toml. Do not edit this
# file: edit ariza's resources/templates/install-posix.sh.j2 and re-run
# `ariza installers --app=<this repository>`.
#
#   curl -fsSL https://raw.githubusercontent.com/m-doughty/App-Moneymoor/HEAD/install.sh | sh
#   curl -fsSL https://raw.githubusercontent.com/m-doughty/App-Moneymoor/HEAD/install.sh | sh -s -- --version v1.2.3
#   sh install.sh --help
#
# What it does:
#
#   1. Works out which prebuilt bundle this machine takes.
#   2. Downloads it from the m-doughty/App-Moneymoor releases and verifies its sha256.
#   3. Unpacks it beside any version already installed, then flips a
#      `current` symlink — so a failed download cannot break a working
#      install.
#   4. Links ~/.local/bin/moneymoor, and puts that directory on PATH
#      only if it is not on it already.
#   5. Runs it once, so that whatever the first launch has to do is done
#      here rather than the first time you want the program.
#
# Nothing is compiled, nothing needs root, and nothing outside
# ${XDG_DATA_HOME:-~/.local/share}/moneymoor and ~/.local/bin is
# written — bar one marked block in your shell rc files, which the
# uninstaller removes again.

set -eu

APP_DISPLAY='Moneymoor'
APP_EXEC='moneymoor'
APP_REPO='m-doughty/App-Moneymoor'

# The platforms App::Moneymoor publishes bundles for. Detection produces
# one of these strings or nothing at all; there is deliberately no
# "closest match", because a bundle for the wrong libc does not run.
ARIZA_SLUGS='macos-arm64 linux-x86_64-glibc'

ARIZA_PATH_MARKER="# >>> $APP_EXEC PATH >>>"
ARIZA_PATH_END="# <<< $APP_EXEC PATH <<<"

ARIZA_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/$APP_EXEC"
ARIZA_VERSIONS="$ARIZA_ROOT/versions"
ARIZA_BIN_DIR="$HOME/.local/bin"

# Set MONEYMOOR_BUNDLE_URL to install from somewhere other than the
# m-doughty/App-Moneymoor releases — a local file, a release candidate, a mirror.
# --url overrides it in turn.
ARIZA_SRC="${MONEYMOOR_BUNDLE_URL:-}"
ARIZA_TAG=''
ARIZA_VERSION=''
ARIZA_OVERRIDE=0
ARIZA_NO_VERIFY=0
ARIZA_TMP=''
ARIZA_STAGING=''

############################# common runtime ##################################
###############################################################################
# ariza installer runtime (POSIX)
#
# Inlined verbatim into install.sh and uninstall.sh at render time, so a
# generated installer is one self-contained file with nothing to source
# and nothing to download before it can start.
#
# Adapted from the pre-bundle installers' template/lib/common-posix.sh --
# the registry, the package-manager probes and the terminal detection are
# gone (a bundle installs none of that), the PATH marker machinery is
# kept, because getting it wrong duplicates blocks in people's rc files.
#
# Callers must have set: APP_DISPLAY, APP_EXEC, ARIZA_PATH_MARKER,
# ARIZA_PATH_END.
###############################################################################

# Colour only when someone is watching. `curl … | sh` in a CI log is not.
if [ -t 1 ]; then
    ariza_c_blue='\033[34m'; ariza_c_green='\033[32m'; ariza_c_red='\033[31m'
    ariza_c_yellow='\033[33m'; ariza_c_reset='\033[0m'
else
    ariza_c_blue=''; ariza_c_green=''; ariza_c_red=''
    ariza_c_yellow=''; ariza_c_reset=''
fi

ariza_log()  { printf "${ariza_c_blue}==>${ariza_c_reset} %s\n" "$*"; }
ariza_ok()   { printf "${ariza_c_green}ok${ariza_c_reset}  %s\n" "$*"; }
ariza_warn() { printf "${ariza_c_yellow}!!${ariza_c_reset}  %s\n" "$*" >&2; }
ariza_err()  { printf "${ariza_c_red}error${ariza_c_reset} %s\n" "$*" >&2; exit 1; }

ariza_have() { command -v "$1" >/dev/null 2>&1; }

# ---- downloading -------------------------------------------------------------

ariza_download() {
    # $1 = url, $2 = destination. Fails on any HTTP error rather than
    # writing a 404 page to disk and calling it an archive.
    if ariza_have curl; then
        curl --fail --location --silent --show-error --retry 3 -o "$2" "$1"
    elif ariza_have wget; then
        wget --quiet --tries=3 -O "$2" "$1"
    else
        ariza_err "need curl or wget to download $1"
    fi
}

# ---- checksums ---------------------------------------------------------------

ariza_sha256() {
    # $1 = file. Echoes the lowercase hex digest, or returns 1 if this
    # machine has no tool that can compute one.
    if ariza_have sha256sum; then
        sha256sum "$1" | cut -d' ' -f1
    elif ariza_have shasum; then
        shasum -a 256 "$1" | cut -d' ' -f1
    else
        return 1
    fi
}

ariza_first_word() {
    # $1 = a `<hex>  <filename>` checksum file, as shasum -c reads.
    awk 'NR == 1 { print $1; exit }' "$1"
}

ariza_verify_sha256() {
    # $1 = file, $2 = expected hex. Fatal on any disagreement.
    _actual=$(ariza_sha256 "$1" || true)
    [ -n "$_actual" ] || ariza_err "no sha256sum or shasum on this machine, so the download cannot be verified"
    if [ "$_actual" != "$2" ]; then
        ariza_err "checksum mismatch for $(basename "$1"): expected $2, got $_actual -- not installing"
    fi
}

# ---- shell rc PATH wiring ----------------------------------------------------
#
# One marker block, named after the executable rather than after the
# project, so uninstalling one app never removes the line another app is
# relying on. Re-running never duplicates it: the marker is grepped for
# first, and the whole block is skipped when the directory is already on
# PATH, which is the common case on a second install.

ariza_persist_path() {
    # $1 = directory to put on PATH.
    #
    # Records whether the directory was on PATH BEFORE this function
    # touched anything, in ARIZA_PATH_WAS_PRESENT. The parting message
    # keys on that record, not on $PATH: this function's last act is to
    # export the directory into this script's own PATH, so a check made
    # after it runs always says "present" and the tell-the-user branch
    # becomes unreachable — which is exactly how the first real install
    # ended with a command the user's terminal could not find and no
    # line telling them why.
    _dir=$1
    case ":${PATH:-}:" in
        *":$_dir:"*)
            # Already there for this shell, so a login shell almost
            # certainly has it too. Adding a block would be one more
            # thing to clean up later for no gain.
            ARIZA_PATH_WAS_PRESENT=1
            return 0
            ;;
    esac
    ARIZA_PATH_WAS_PRESENT=0

    _block=$(printf '%s\ncase ":$PATH:" in\n  *":%s:"*) ;;\n  *) PATH="%s:$PATH"; export PATH ;;\nesac\n%s\n' \
        "$ARIZA_PATH_MARKER" "$_dir" "$_dir" "$ARIZA_PATH_END")

    _rc_seen=0
    for _rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do
        [ -f "$_rc" ] || continue
        _rc_seen=$((_rc_seen + 1))
        grep -Fq "$ARIZA_PATH_MARKER" "$_rc" 2>/dev/null && continue
        # An unterminated last line would otherwise swallow the marker,
        # and a decorative blank line before the block would survive the
        # uninstaller — accumulating one per install/uninstall cycle.
        [ -n "$(tail -c 1 "$_rc")" ] && printf '\n' >>"$_rc"
        printf '%s\n' "$_block" >>"$_rc"
        ariza_ok "added $_dir to PATH in $_rc"
    done

    if [ "$_rc_seen" -eq 0 ]; then
        ariza_warn "no shell rc file found -- add this to yours by hand:"
        printf '        export PATH="%s:$PATH"\n' "$_dir" >&2
    fi

    PATH="$_dir:$PATH"
    export PATH
}

ariza_unpersist_path() {
    # Remove our own marker block, and only ours, from every rc file that
    # has one. Rewritten through the existing file rather than moved over
    # it, so the rc file keeps its mode and ownership.
    for _rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do
        [ -f "$_rc" ] || continue
        grep -Fq "$ARIZA_PATH_MARKER" "$_rc" 2>/dev/null || continue
        _tmp=$(mktemp "${TMPDIR:-/tmp}/ariza-rc.XXXXXX")
        awk -v m="$ARIZA_PATH_MARKER" -v em="$ARIZA_PATH_END" '
            $0 == m  { skip = 1; next }
            $0 == em { skip = 0; next }
            !skip    { print }
        ' "$_rc" >"$_tmp"
        cat "$_tmp" >"$_rc"
        rm -f "$_tmp"
        ariza_ok "removed the PATH block from $_rc"
    done
}

###############################################################################

ariza_usage() {
    cat <<USAGE
Usage: install.sh [options]

  --version TAG           install a specific release tag (e.g. --version v1.2.3)
                          instead of the latest one
  --url FILE-OR-URL       install this archive instead, bypassing GitHub
                          entirely. Also settable as MONEYMOOR_BUNDLE_URL.
  --insecure-no-verify    with --url only: proceed even when the source has
                          no sibling .sha256 to check it against
  -h, --help              this message

Installs to $ARIZA_ROOT/versions/<version>/ and links
$ARIZA_BIN_DIR/$APP_EXEC. Re-running with a version already installed
repairs the links and exits without downloading anything.
USAGE
}

ariza_parse_args() {
    while [ $# -gt 0 ]; do
        case $1 in
            --version)
                shift
                [ $# -gt 0 ] || ariza_err "--version needs a release tag, e.g. --version v1.2.3"
                ARIZA_TAG=$1
                ;;
            --version=*) ARIZA_TAG=${1#*=} ;;
            --url)
                shift
                [ $# -gt 0 ] || ariza_err "--url needs a file path or a URL"
                ARIZA_SRC=$1
                ;;
            --url=*) ARIZA_SRC=${1#*=} ;;
            --insecure-no-verify) ARIZA_NO_VERIFY=1 ;;
            -h|--help) ariza_usage; exit 0 ;;
            *) ariza_err "unknown option '$1' -- run with --help" ;;
        esac
        shift
    done
}

ariza_cleanup() {
    if [ -n "$ARIZA_TMP" ] && [ -d "$ARIZA_TMP" ]; then
        rm -rf "$ARIZA_TMP"
    fi
    if [ -n "$ARIZA_STAGING" ] && [ -d "$ARIZA_STAGING" ]; then
        rm -rf "$ARIZA_STAGING"
    fi
    return 0
}

# ---- which bundle this machine takes -----------------------------------------

ariza_detect_slug() {
    _sys=$(uname -s)
    _mach=$(uname -m)
    case $_sys in
        Darwin)
            case $_mach in
                arm64|aarch64) printf 'macos-arm64\n' ;;
                x86_64|amd64)  printf 'macos-x86_64\n' ;;
                *) return 1 ;;
            esac
            ;;
        Linux)
            # A glibc binary does not run on Alpine and a musl binary does
            # not run on Debian, so the libc is part of the name. The musl
            # loader's presence is conclusive; nothing else needs asking.
            _libc=glibc
            for _loader in /lib/ld-musl-*.so.1 /usr/lib/ld-musl-*.so.1; do
                if [ -e "$_loader" ]; then
                    _libc=musl
                    break
                fi
            done
            case $_mach in
                aarch64|arm64) printf 'linux-aarch64-%s\n' "$_libc" ;;
                x86_64|amd64)  printf 'linux-x86_64-%s\n' "$_libc" ;;
                *) return 1 ;;
            esac
            ;;
        *) return 1 ;;
    esac
}

ariza_slug_supported() {
    for _s in $ARIZA_SLUGS; do
        [ "$_s" = "$1" ] && return 0
    done
    return 1
}

ariza_version_from_name() {
    # $1 = a bundle directory or archive basename, which ariza names
    # <exec>-<version>-<slug>. Stripping a known slug off the end rather
    # than splitting on dashes is what makes a version containing one
    # (1.0-rc1) parse correctly.
    _n=$1
    _n=${_n%.tar.gz}
    _n=${_n%.tgz}
    _n=${_n%.zip}
    case $_n in
        "$APP_EXEC-"*) _n=${_n#"$APP_EXEC-"} ;;
        *) return 1 ;;
    esac
    for _s in $ARIZA_SLUGS; do
        case $_n in
            *"-$_s") printf '%s\n' "${_n%"-$_s"}"; return 0 ;;
        esac
    done
    return 1
}

ariza_latest_tag() {
    # The `location:` header of the releases/latest redirect names the
    # tag. That is one HEAD request against a static host, and needs no
    # JSON parser on a machine that may not have one.
    _url="https://github.com/$APP_REPO/releases/latest"
    if ariza_have curl; then
        _loc=$(curl -fsSI "$_url" | tr -d '\r' \
               | awk 'tolower($1) == "location:" { print $2 }' | tail -n 1)
    elif ariza_have wget; then
        _loc=$(wget --max-redirect=0 --spider --server-response "$_url" 2>&1 \
               | tr -d '\r' | awk 'tolower($1) == "location:" { print $2 }' | tail -n 1)
    else
        ariza_err "need curl or wget to find the latest $APP_DISPLAY release"
    fi
    [ -n "$_loc" ] || return 1
    printf '%s\n' "${_loc##*/}"
}

# ---- the install itself ------------------------------------------------------

ariza_point_current() {
    # $1 = version. The target is relative, so the whole data directory
    # can be moved without breaking it.
    _link="$ARIZA_ROOT/current"
    if [ -e "$_link" ] && [ ! -L "$_link" ]; then
        ariza_err "$_link exists and is not a symlink -- move it aside and re-run"
    fi
    ln -sfn "versions/$1" "$_link"
}

ariza_link_bin() {
    mkdir -p "$ARIZA_BIN_DIR"
    _bin="$ARIZA_BIN_DIR/$APP_EXEC"
    if [ -e "$_bin" ] && [ ! -L "$_bin" ]; then
        ariza_err "$_bin exists and is not a symlink -- move it aside and re-run"
    fi
    ln -sfn "$ARIZA_ROOT/current/bin/$APP_EXEC" "$_bin"
}

ariza_prune() {
    # $1 = the version just installed. Exactly one older version is kept,
    # so a bad release can be rolled back to the one before it without
    # keeping every bundle ever downloaded.
    _keep=$1
    _old=0
    for _v in $(ls -1t "$ARIZA_VERSIONS" 2>/dev/null); do
        [ -d "$ARIZA_VERSIONS/$_v" ] || continue
        [ "$_v" = "$_keep" ] && continue
        _old=$((_old + 1))
        if [ "$_old" -gt 1 ]; then
            rm -rf "$ARIZA_VERSIONS/$_v"
            ariza_log "removed superseded version $_v"
        fi
    done
}

ariza_check_existing() {
    # $1 = version. Returns 0 when it is already installed and usable, in
    # which case the links and PATH are repaired on the way past: a
    # re-run is the obvious thing to try when someone has deleted
    # $ARIZA_BIN_DIR/$APP_EXEC, and it should fix it.
    _v=$1
    _dir="$ARIZA_VERSIONS/$_v"
    [ -d "$_dir" ] || return 1
    if [ ! -x "$_dir/bin/$APP_EXEC" ]; then
        ariza_warn "$_dir is incomplete -- installing it again"
        rm -rf "$_dir"
        return 1
    fi
    ariza_point_current "$_v"
    ariza_link_bin
    ariza_persist_path "$ARIZA_BIN_DIR"
    ariza_ok "$APP_DISPLAY $_v is already installed"
    return 0
}

ariza_fetch_and_unpack() {
    # $1 = source (path or URL), $2 = digest source or empty.
    ARIZA_TMP=$(mktemp -d "${TMPDIR:-/tmp}/$APP_EXEC-install.XXXXXX")
    _archive="$ARIZA_TMP/$(basename "$1")"

    if [ -f "$1" ]; then
        cp "$1" "$_archive"
    else
        ariza_log "downloading $1"
        ariza_download "$1" "$_archive"
    fi

    _expected=''
    if [ -n "$2" ]; then
        if [ -f "$2" ]; then
            _expected=$(ariza_first_word "$2")
        elif [ ! -f "$1" ] && ariza_download "$2" "$_archive.sha256" 2>/dev/null; then
            _expected=$(ariza_first_word "$_archive.sha256")
        fi
    fi

    if [ -n "$_expected" ]; then
        ariza_verify_sha256 "$_archive" "$_expected"
        ariza_ok "sha256 verified"
    elif [ "$ARIZA_OVERRIDE" -eq 1 ] && [ "$ARIZA_NO_VERIFY" -eq 1 ]; then
        # Only reachable for a source the user named themselves, and only
        # when they asked for it in as many words. A published release
        # always has a checksum beside it, so its absence there is a
        # tampered or half-uploaded release and stays fatal.
        ariza_warn "NOT VERIFIED: no $(basename "$2") beside the source you gave, and --insecure-no-verify was passed"
    else
        ariza_err "no checksum published at $2 -- refusing to install a bundle that cannot be verified"
    fi

    # Unpacked inside the data directory rather than in /tmp, so the move
    # into place is a rename on the same filesystem: the new version
    # appears complete or not at all, and `current` is flipped only after
    # it is there.
    mkdir -p "$ARIZA_VERSIONS"
    ARIZA_STAGING="$ARIZA_ROOT/.staging.$$"
    rm -rf "$ARIZA_STAGING"
    mkdir -p "$ARIZA_STAGING"

    case $_archive in
        *.zip)
            ariza_have unzip || ariza_err "need unzip to unpack $(basename "$_archive")"
            unzip -q -o "$_archive" -d "$ARIZA_STAGING"
            ;;
        *)
            tar -x -z -f "$_archive" -C "$ARIZA_STAGING"
            ;;
    esac

    rm -rf "$ARIZA_TMP"
    ARIZA_TMP=''
}

ariza_install() {
    # The archive holds exactly one directory, named after the bundle.
    set -- "$ARIZA_STAGING"/*
    if [ $# -ne 1 ] || [ ! -d "$1" ]; then
        ariza_err "that archive does not look like a $APP_DISPLAY bundle (expected one directory inside it)"
    fi
    _top=$1

    # What the bundle says it is beats what the archive was called: a
    # versions/<x> directory that does not contain <x> would make every
    # later "already installed" check a lie.
    _actual=$(ariza_version_from_name "$(basename "$_top")" || true)
    if [ -z "$_actual" ]; then
        [ -n "$ARIZA_VERSION" ] || ariza_err "cannot tell which version $(basename "$_top") is -- expected ${APP_EXEC}-<version>-<platform>"
    else
        if [ -n "$ARIZA_VERSION" ] && [ "$_actual" != "$ARIZA_VERSION" ]; then
            ariza_warn "that archive is named $ARIZA_VERSION but contains $_actual -- installing it as $_actual"
        fi
        ARIZA_VERSION=$_actual
    fi

    if ariza_check_existing "$ARIZA_VERSION"; then
        rm -rf "$ARIZA_STAGING"
        ARIZA_STAGING=''
        return 0
    fi

    _dest="$ARIZA_VERSIONS/$ARIZA_VERSION"
    rm -rf "$_dest"
    mv "$_top" "$_dest"
    # Stamped with the install time, not the build time: tar restores the
    # directory's original mtime, which is the same for every bundle cut
    # from one source tree, and pruning by age has to mean "the one I
    # installed before this one".
    touch "$_dest"
    rm -rf "$ARIZA_STAGING"
    ARIZA_STAGING=''

    [ -x "$_dest/bin/$APP_EXEC" ] || ariza_err "the unpacked bundle has no runnable bin/$APP_EXEC -- not touching your existing install"

    ariza_point_current "$ARIZA_VERSION"
    ariza_link_bin
    ariza_persist_path "$ARIZA_BIN_DIR"
    ariza_prune "$ARIZA_VERSION"
    ariza_ok "$APP_DISPLAY $ARIZA_VERSION installed"
}

ariza_warm() {
    # Run the app once, now, through the same path the user's shell will
    # take: the `current` symlink this script has just repointed. A first
    # launch pages a few hundred megabytes off a cold disk and builds
    # whatever per-user state the app keeps; doing it here means it
    # happens while an installer is on screen saying so, instead of the
    # first time somebody actually wants the program.
    #
    # Never fatal. The bundle is installed and its sha256 was checked
    # before anything was moved into place, so a warm-up that fails on
    # one machine is far more likely to be that machine — no terminal, a
    # sandbox, a policy — than a broken release. Refusing to finish the
    # install over it would take a working program away from a user who
    # has one.
    _warm_bin="$ARIZA_ROOT/current/bin/$APP_EXEC"
    [ -x "$_warm_bin" ] || return 0

    # The arguments come from the app's ariza.toml and are written out
    # already quoted, once as the command line and once as the single
    # word the message names.
    _warm_what='--version'

    ariza_log 'warming up -- the first launch does the work the rest never repeat'
    if "$_warm_bin" '--version' >/dev/null 2>&1; then
        ariza_ok 'ready'
    else
        ariza_warn "warm-up failed: $APP_EXEC $_warm_what did not complete"
        ariza_warn "$APP_DISPLAY is installed and its download was verified -- try running it; the first launch may just take longer"
    fi
}

ariza_report() {
    printf '\n'
    printf '    run it:        %s\n' "$APP_EXEC"
    printf '    installed in:  %s\n' "$ARIZA_VERSIONS/$ARIZA_VERSION"
    printf '    uninstall:     curl -fsSL %s/uninstall.sh | sh\n' 'https://raw.githubusercontent.com/m-doughty/App-Moneymoor/HEAD'
    # Keyed on what ariza_persist_path found BEFORE it ran, never on
    # $PATH here: persisting exports the directory into this script's
    # own PATH, so checking now would always answer "present" and this
    # message would never print for the one user who needs it. The
    # default is the quiet arm, so a run that never persisted (--help,
    # an early exit) stays quiet.
    if [ "${ARIZA_PATH_WAS_PRESENT:-1}" -eq 0 ]; then
        printf '\n    %s is now on your PATH for new terminals.\n' "$ARIZA_BIN_DIR"
        printf '    To use %s in THIS one first, paste:\n' "$APP_EXEC"
        printf '\n        export PATH="%s:$PATH"\n' "$ARIZA_BIN_DIR"
    fi
}

main() {
    ariza_parse_args "$@"
    trap ariza_cleanup EXIT HUP INT TERM

    _digest_src=''

    if [ -n "$ARIZA_SRC" ]; then
        # An explicit source bypasses GitHub completely. It is how a
        # release candidate, an air-gapped copy or an offline test gets
        # installed, so it accepts a plain file path as readily as a URL.
        ARIZA_OVERRIDE=1
        _digest_src="$ARIZA_SRC.sha256"
        ARIZA_VERSION=$(ariza_version_from_name "$(basename "$ARIZA_SRC")" || true)
        ariza_log "$APP_DISPLAY from $ARIZA_SRC"
    else
        _slug=$(ariza_detect_slug 2>/dev/null || true)
        if [ -z "$_slug" ] || ! ariza_slug_supported "$_slug"; then
            ariza_err "no prebuilt $APP_DISPLAY bundle for $(uname -s) $(uname -m) yet -- see https://github.com/$APP_REPO/releases for what is published"
        fi

        if [ -z "$ARIZA_TAG" ]; then
            ARIZA_TAG=$(ariza_latest_tag || true)
            [ -n "$ARIZA_TAG" ] || ariza_err "could not read the latest release tag from https://github.com/$APP_REPO/releases/latest -- pass --version to name one"
        fi
        ARIZA_VERSION=${ARIZA_TAG#v}
        ARIZA_SRC="https://github.com/$APP_REPO/releases/download/$ARIZA_TAG/$APP_EXEC-$ARIZA_VERSION-$_slug.tar.gz"
        _digest_src="$ARIZA_SRC.sha256"
        ariza_log "$APP_DISPLAY $ARIZA_VERSION for $_slug"
    fi

    # Both arms end the same way: warm the install, then say the piece.
    # Every path that reaches the parting message goes through the
    # warm-up first, including the one where nothing was downloaded --
    # a re-run is what somebody tries when the last one did not take.
    if [ -n "$ARIZA_VERSION" ] && ariza_check_existing "$ARIZA_VERSION"; then
        ariza_warm
        ariza_report
        exit 0
    fi

    ariza_fetch_and_unpack "$ARIZA_SRC" "$_digest_src"
    ariza_install
    ariza_warm
    ariza_report
}

# Everything above is a definition, and nothing has run yet: a script
# read from a pipe is executed as it arrives, so a single call at the end
# is what makes `curl … | sh` safe against a truncated download.
main "$@"
