#!/bin/bash
# =============================================================================
# lib/join/add-node.sh -- what `curl -fsSL https://join.<domain> | sudo bash'
# runs on a machine joining the fleet.
#
# Role: served by the fleet's join service (the mesh holder's agent,
#       fleet/mesh/join.lisp) at GET https://join.<domain>/, with the domain
#       baked in. Asks the bootstrap password and the node's storage, POSTs
#       them back to the join service, and runs the install it answers with
#       (lib/join/node.install.sh.tmpl, rendered with a one-off setup key).
#       The gateway serves no add-node (stagework/GATEWAY-REDESIGN.md).
# =============================================================================
set -e

# The fleet's domain, baked by the join service when it serves this.
ROOT_DOMAIN="hifrens.dedyn.io"
# The join service, behind NetBird's proxy and its certificate.
API_BASE="https://join.${ROOT_DOMAIN}"

# Fail if still unbaked. Prefix match only — never embed full placeholder token.
if [[ -z "$ROOT_DOMAIN" || "$ROOT_DOMAIN" == __SETUP_* ]]; then
    echo "!! ROOT_DOMAIN was not baked by the join service." >&2
    exit 1
fi

# Inline secret prompt with * echo.
# MUST read from /dev/tty — with `curl | bash`, stdin is the script pipe.
# Skips reading entirely when BOOTSTRAP_PASSWORD is pre-set (cloud-init /
# provision.sh non-interactive use, e.g. the Proxmox test environment).
prompt_secret_inline() {
    local __var="$1"
    local __prompt="$2"
    if [[ -n "${BOOTSTRAP_PASSWORD:-}" ]]; then
        printf -v "$__var" '%s' "$BOOTSTRAP_PASSWORD"
        return 0
    fi
    if command -v systemd-ask-password >/dev/null 2>&1; then
        local __sysd_secret
        __sysd_secret="$(systemd-ask-password --echo=masked "$__prompt" 2>/dev/tty)"
        printf -v "$__var" '%s' "$__sysd_secret"
        return 0
    fi
    local __secret=""
    local __saved
    # Configure the real terminal, not the curl pipe
    __saved="$(stty -g </dev/tty 2>/dev/null || true)"
    stty -echo -icanon min 1 time 0 </dev/tty 2>/dev/null || true
    # Prompt goes to the terminal
    printf '%s' "$__prompt" >/dev/tty
    while true; do
        # Read keystrokes from the controlling terminal
        IFS= read -r -n 1 __c </dev/tty
        if [[ -z "$__c" ]]; then break; fi
        if [[ "$__c" == $'\177' ]] || [[ "$__c" == $'\b' ]]; then
            if [[ ${#__secret} -gt 0 ]]; then
                __secret="${__secret%?}"
                printf '\b \b' >/dev/tty
            fi
            continue
        fi
        __secret+="$__c"
        printf '*' >/dev/tty
    done
    [[ -n "$__saved" ]] && stty "$__saved" </dev/tty 2>/dev/null || stty echo </dev/tty 2>/dev/null || true
    printf '\n' >/dev/tty
    printf -v "$__var" '%s' "$__secret"
}

# What this node gives the store cluster: FLEET_STORAGE non-interactively
# (`no' for a gateway that serves S3 and holds nothing, a number of GiB for
# a storage node of that size, empty for a storage node of the whole
# pool), else asked once at the terminal.
if [[ -z "${FLEET_STORAGE+x}" ]]; then
    if [[ -r /dev/tty ]]; then
        printf 'Storage on this node? [GiB to give, "no" for a gateway, empty for all] ' >/dev/tty
        IFS= read -r FLEET_STORAGE </dev/tty || FLEET_STORAGE=""
    else
        FLEET_STORAGE=""
    fi
fi

for __attempt in 1 2 3; do
    __pw=""
    prompt_secret_inline __pw "Bootstrap password: "
    # Capture body and HTTP status separately (do not use -f; we handle codes)
    __body="$(mktemp)"
    # The password reaches curl on stdin (name@-), never on its command
    # line, where every process on the machine could read it for the
    # request's duration.
    # A 429 is the fleet's cap on password checks from everyone together
    # (a flood elsewhere): the same password again once a minute has
    # passed, five times at most, never counted as a wrong one.
    for __wait in 1 2 3 4 5; do
        __code="$(printf '%s' "$__pw" | curl -sS --connect-timeout 15 --max-time 120 -o "$__body" -w "%{http_code}" -X POST \
            --data-urlencode "password@-" \
            --data-urlencode "pool=${FLEET_POOL_GIB:-}" \
            --data-urlencode "storage=${FLEET_STORAGE}" \
            "${API_BASE}/" || echo "000")"
        [[ "$__code" == 429 ]] || break
        echo "!! The join service is checking too many passwords just now; trying again in a minute (${__wait}/5)." >&2
        sleep 60
    done
    case "$__code" in
        200)
            # Auth OK — run the rendered install script.
            # Close curl-pipe stdin so child commands (incus create) do not
            # treat leftover script bytes as YAML config.
            # The password goes to the install in its environment, never on
            # a command line: `fleetctl join' opens the fleet key under it
            # (FLEET-REWORK-PLAN section A).
            FLEET_BOOTSTRAP_PASSWORD="$__pw" bash "$__body" </dev/null
            __rc=$?
            rm -f "$__body"
            exit "$__rc"
            ;;
        400)
            echo "!! The join service refused the storage choice:" >&2
            [[ -s "$__body" ]] && sed 's/^/   /' "$__body" >&2
            rm -f "$__body"
            exit 1
            ;;
        403)
            echo "!! Authentication failed: incorrect bootstrap password (${__attempt}/3)." >&2
            ;;
        429)
            echo "!! The join service stayed busy for five minutes; run this again later." >&2
            rm -f "$__body"
            exit 1
            ;;
        000)
            echo "!! Could not reach ${API_BASE}/ (network/DNS error)." >&2
            ;;
        *)
            echo "!! The join service returned HTTP ${__code}." >&2
            [[ -s "$__body" ]] && sed 's/^/   /' "$__body" >&2
            ;;
    esac
    rm -f "$__body"
done
echo "!! Authentication failed after 3 attempts." >&2
exit 1
