#!/usr/bin/env bash

# -----------------------------------------------------------------------------
# PUBLIC LOADER DEFAULTS — edit only this block before publishing public/.
#
# Leave DEFAULT_LOGIN_SERVER empty to use Tailscale's hosted control plane.
# Every install must still pass exactly one -b <branch> or -t <tag>; there is
# deliberately no default Git ref.
DEFAULT_REPO_URL='git@shipyard:fcos'
DEFAULT_LOGIN_SERVER=''
DEFAULT_NODE_TAG='tag:fleetcommand-node'
DEFAULT_AUTH_TIMEOUT='15m'

# Install-policy defaults accept only 'true' or 'false'. Enabling
# DEFAULT_YES_SINGLE_DISK skips the typed destruction confirmation only when
# the installer safely auto-selects exactly one installable disk.
DEFAULT_YES_SINGLE_DISK='true'
DEFAULT_ALLOW_POWER_BUTTON='false'
DEFAULT_PERSISTENT_JOURNAL='false'
# END PUBLIC LOADER DEFAULTS
# -----------------------------------------------------------------------------

set -euo pipefail

REPO_URL="$DEFAULT_REPO_URL"
LOGIN_SERVER="$DEFAULT_LOGIN_SERVER"
NODE_TAG="$DEFAULT_NODE_TAG"
AUTH_TIMEOUT="$DEFAULT_AUTH_TIMEOUT"
GIT_REF=""
GIT_REF_KIND=""
HOSTNAME_ARG=""
WORK_ROOT=""
TAILSCALE_PID=""
TAILSCALE_SOCKET=""
TAILSCALE_STATE=""
REVOKE_ON_EXIT=0
FORWARDED_BOOTSTRAP_ARGS=()
POSITIONAL_ARGS=()

msg() { printf '\033[32m%s\033[0m\n' "$*"; }
die() { printf '\033[31m%s\033[0m\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }

normalize_nixos_path() {
  local bin_dir
  # Interactive installer shells normally include these paths. Transient
  # services and sudo policies may not, even though the commands are present
  # in the live NixOS closure.
  for bin_dir in /run/current-system/sw/bin /nix/var/nix/profiles/default/bin; do
    [[ ! -d "$bin_dir" ]] || PATH="$bin_dir:$PATH"
  done
  export PATH
}

usage() {
  cat >&2 <<'EOF'
Usage: install.sh (-b <branch> | -t <tag>) [loader options] [install options] [hostname]

Required (choose exactly one):
  -b <branch>             Install an explicitly selected branch.
  -t <tag>                Install an explicitly selected tag.

Loader options:
  --repo-url <url>        Private SSH Git URL (default is embedded at publish time).
  --login-server <url>    Headscale or other compatible control-server URL.
  --tailscale-saas        Use Tailscale's hosted control plane.
  --node-tag <tag:name>   Tag requested for the new FCOS identity.
  --auth-timeout <time>   Browser-approval window, from 1s through 1h.

Install options passed to the private bootstrap:
  --yes-single-disk
  --require-disk-confirmation
  --admin-password-file <path>
  --admin-password-hash <hash>
  --prompt-admin-password
  --edit-host-nix
  --allow-power-button
  --ignore-power-button
  --persistent-journal
  --volatile-journal

The positive install flags enable their matching published defaults for one
run. The paired inverse flags disable an enabled published default for one run.

The loader never supplies a default branch or tag. It creates a temporary,
root-only Tailscale identity, verifies and clones the selected private ref via
Tailscale SSH, and hands that exact checkout to the private installer.
EOF
}

validate_git_ref() {
  [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._/@+-]{0,127}$ ]]
}

duration_seconds() {
  local value="$1" number multiplier
  [[ "$value" =~ ^([1-9][0-9]*)([smh])$ ]] || return 1
  number="${BASH_REMATCH[1]}"
  case "${BASH_REMATCH[2]}" in
    s) multiplier=1 ;;
    m) multiplier=60 ;;
    h) multiplier=3600 ;;
  esac
  (( number * multiplier <= 3600 )) || return 1
  printf '%s' "$(( number * multiplier ))"
}

validate_node_tag() {
  [[ "$1" =~ ^tag:[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$ ]]
}

validate_boolean() {
  [[ "$1" == true || "$1" == false ]]
}

validate_login_server() {
  local url="$1"
  [[ -z "$url" ]] && return 0
  [[ "$url" != *$'\n'* && "$url" != *$'\r'* && "$url" != *[[:space:]]* ]] || return 1
  [[ "$url" =~ ^https?://[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]{1,5})?/?$ ]]
}

git_remote_host() {
  local url="$1" authority host
  if [[ "$url" =~ ^[A-Za-z_][A-Za-z0-9._-]*@([A-Za-z0-9][A-Za-z0-9.-]*):[^[:space:]]+$ ]]; then
    printf '%s' "${BASH_REMATCH[1]}"
    return 0
  fi
  if [[ "$url" == ssh://* ]]; then
    authority="${url#ssh://}"
    authority="${authority%%/*}"
    [[ "$authority" == *@* ]] || return 1
    host="${authority#*@}"
    host="${host%%:*}"
    [[ "$host" =~ ^[A-Za-z0-9][A-Za-z0-9.-]*$ ]] || return 1
    printf '%s' "$host"
    return 0
  fi
  return 1
}

validate_repo_url() {
  local url="$1" authority userinfo
  [[ "$url" != *$'\n'* && "$url" != *$'\r'* && "$url" != *[[:space:]]* ]] || return 1
  if [[ "$url" == ssh://* ]]; then
    authority="${url#ssh://}"
    authority="${authority%%/*}"
    userinfo="${authority%@*}"
    [[ "$authority" == *@* && "$userinfo" != *:* && "$url" == ssh://*/* ]] || return 1
  elif [[ ! "$url" =~ ^[A-Za-z_][A-Za-z0-9._-]*@[A-Za-z0-9][A-Za-z0-9.-]*:[^[:space:]]+$ ]]; then
    return 1
  fi
  git_remote_host "$url" >/dev/null
}

parse_args() {
  local yes_single_disk="$DEFAULT_YES_SINGLE_DISK"
  local allow_power_button="$DEFAULT_ALLOW_POWER_BUTTON"
  local persistent_journal="$DEFAULT_PERSISTENT_JOURNAL"

  validate_boolean "$yes_single_disk" \
    || die "DEFAULT_YES_SINGLE_DISK must be 'true' or 'false'."
  validate_boolean "$allow_power_button" \
    || die "DEFAULT_ALLOW_POWER_BUTTON must be 'true' or 'false'."
  validate_boolean "$persistent_journal" \
    || die "DEFAULT_PERSISTENT_JOURNAL must be 'true' or 'false'."

  POSITIONAL_ARGS=()
  FORWARDED_BOOTSTRAP_ARGS=()
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --install)
        shift ;;
      --upgrade)
        die "The public loader is only for fresh installs." ;;
      -b|-t)
        [[ -z "$GIT_REF" ]] || die "Use only one Git ref flag: -b <branch> or -t <tag>."
        [[ -n "${2:-}" && "${2:-}" != -* ]] || die "Missing or invalid argument for $1"
        GIT_REF="$2"
        if [[ "$1" == "-b" ]]; then GIT_REF_KIND="branch"; else GIT_REF_KIND="tag"; fi
        shift 2 ;;
      --repo-url)
        [[ -n "${2:-}" && "${2:-}" != -* ]] || die "Missing or invalid argument for $1"
        REPO_URL="$2"; shift 2 ;;
      --login-server)
        [[ -n "${2:-}" && "${2:-}" != -* ]] || die "Missing or invalid argument for $1"
        LOGIN_SERVER="$2"; shift 2 ;;
      --tailscale-saas)
        LOGIN_SERVER=""; shift ;;
      --node-tag)
        [[ -n "${2:-}" && "${2:-}" != -* ]] || die "Missing or invalid argument for $1"
        NODE_TAG="$2"; shift 2 ;;
      --auth-timeout)
        [[ -n "${2:-}" && "${2:-}" != -* ]] || die "Missing or invalid argument for $1"
        AUTH_TIMEOUT="$2"; shift 2 ;;
      --yes-single-disk)
        yes_single_disk=true; shift ;;
      --require-disk-confirmation)
        yes_single_disk=false; shift ;;
      --allow-power-button)
        allow_power_button=true; shift ;;
      --ignore-power-button)
        allow_power_button=false; shift ;;
      --persistent-journal)
        persistent_journal=true; shift ;;
      --volatile-journal)
        persistent_journal=false; shift ;;
      --prompt-admin-password|--edit-host-nix)
        FORWARDED_BOOTSTRAP_ARGS+=("$1"); shift ;;
      --admin-password-file|--admin-password-hash)
        [[ -n "${2:-}" ]] || die "Missing argument for $1"
        FORWARDED_BOOTSTRAP_ARGS+=("$1" "$2"); shift 2 ;;
      -h|--help)
        usage; exit 0 ;;
      --)
        shift
        POSITIONAL_ARGS+=("$@")
        break ;;
      -*)
        die "Unknown flag: $1" ;;
      *)
        POSITIONAL_ARGS+=("$1"); shift ;;
    esac
  done

  [[ -n "$GIT_REF" ]] || die "Fresh install requires an explicit -b <branch> or -t <tag>; no default ref exists."
  validate_git_ref "$GIT_REF" || die "Invalid Git ${GIT_REF_KIND}: $GIT_REF"
  validate_repo_url "$REPO_URL" || die "Repository URL must be a credential-free SSH URL such as git@shipyard:fcos."
  validate_login_server "$LOGIN_SERVER" || die "Control-server URL must be an HTTP(S) origin without credentials, query, fragment, or path."
  validate_node_tag "$NODE_TAG" || die "Invalid node tag: $NODE_TAG"
  duration_seconds "$AUTH_TIMEOUT" >/dev/null || die "Authentication timeout must be from 1s through 1h (for example, 15m)."
  [[ ${#POSITIONAL_ARGS[@]} -le 1 ]] || die "Unexpected extra argument: ${POSITIONAL_ARGS[1]}"
  HOSTNAME_ARG="${POSITIONAL_ARGS[0]:-}"

  [[ "$yes_single_disk" == false ]] || FORWARDED_BOOTSTRAP_ARGS+=(--yes-single-disk)
  [[ "$allow_power_button" == false ]] || FORWARDED_BOOTSTRAP_ARGS+=(--allow-power-button)
  [[ "$persistent_journal" == false ]] || FORWARDED_BOOTSTRAP_ARGS+=(--persistent-journal)
}

check_root_and_runtime() {
  [[ -n "${BASH_VERSION:-}" ]] || die "Run this loader with bash, not sh."
  [[ "$EUID" -eq 0 ]] || die "Run this loader as root (sudo)."
  have findmnt || die "Missing required command: findmnt"
  [[ "$(findmnt -n -o FSTYPE /run 2>/dev/null || true)" == tmpfs ]] \
    || die "/run must be a tmpfs so the temporary Tailnet identity never lands on installer media."
}

create_work_root() {
  local attempt candidate
  umask 077
  if have mktemp; then
    WORK_ROOT="$(mktemp -d /run/fleetcommand-private-install.XXXXXX)"
  else
    # Some minimal NixOS installer environments omit mktemp from the initial
    # system PATH. mkdir is atomic, so a PID/random candidate still gives us a
    # safe root-private directory from which to install the remaining tools.
    for attempt in {1..100}; do
      candidate="/run/fleetcommand-private-install.${BASHPID}.${RANDOM}.${attempt}"
      if mkdir -m 0700 -- "$candidate" 2>/dev/null; then
        WORK_ROOT="$candidate"
        break
      fi
    done
    [[ -n "$WORK_ROOT" ]] || die "Unable to create a private temporary directory under /run."
  fi
  chmod 0700 "$WORK_ROOT"
  mkdir -m 0700 "$WORK_ROOT/home" "$WORK_ROOT/config"
  export HOME="$WORK_ROOT/home"
  export XDG_CONFIG_HOME="$WORK_ROOT/config"
  TAILSCALE_SOCKET="$WORK_ROOT/tailscaled.sock"
  TAILSCALE_STATE="$WORK_ROOT/tailscaled.state"
}

ensure_tools() {
  local missing=() packages=(nixos.git nixos.jq nixos.openssh nixos.tailscale) tool profile
  local needs_coreutils=0
  for tool in chmod head mktemp rm seq sleep tail timeout; do
    if ! have "$tool"; then
      missing+=("$tool")
      needs_coreutils=1
    fi
  done
  for tool in git jq ssh tailscale tailscaled; do
    have "$tool" || missing+=("$tool")
  done
  [[ ${#missing[@]} -gt 0 ]] || return 0
  have nix-env || die "Missing ${missing[*]}, and nix-env is unavailable to install temporary loader tools."

  profile="$WORK_ROOT/tool-profile"
  msg "Installing temporary loader tools into the live environment..."
  (( needs_coreutils == 0 )) || packages+=(nixos.coreutils)
  nix-env --profile "$profile" -iA "${packages[@]}" >/dev/null
  export PATH="$profile/bin:$PATH"
  for tool in chmod git head jq mktemp rm seq sleep ssh tail tailscale tailscaled timeout; do
    have "$tool" || die "Temporary tool installation did not provide: $tool"
  done
}

ts() {
  tailscale --socket="$TAILSCALE_SOCKET" "$@"
}

stop_tailscaled() {
  if [[ -n "$TAILSCALE_PID" ]] && kill -0 "$TAILSCALE_PID" 2>/dev/null; then
    kill "$TAILSCALE_PID" 2>/dev/null || true
    wait "$TAILSCALE_PID" 2>/dev/null || true
  fi
  TAILSCALE_PID=""
}

cleanup() {
  local rc=$?
  set +e
  if [[ "$REVOKE_ON_EXIT" -eq 1 && -S "$TAILSCALE_SOCKET" ]]; then
    timeout 10s tailscale --socket="$TAILSCALE_SOCKET" logout >/dev/null 2>&1 || true
  fi
  stop_tailscaled
  if [[ -n "$WORK_ROOT" && "$WORK_ROOT" == /run/fleetcommand-private-install.* && -d "$WORK_ROOT" ]]; then
    rm -rf -- "$WORK_ROOT"
  fi
  exit "$rc"
}

start_tailscaled() {
  local loader_hostname
  loader_hostname="${HOSTNAME_ARG:-fcos-installer}"
  msg "Starting an isolated Tailscale client in volatile memory..."
  tailscaled \
    --state="$TAILSCALE_STATE" \
    --statedir="$WORK_ROOT/tailscale" \
    --socket="$TAILSCALE_SOCKET" \
    --tun=userspace-networking \
    --port=0 \
    --encrypt-state=false \
    --no-logs-no-support \
    >"$WORK_ROOT/tailscaled.log" 2>&1 &
  TAILSCALE_PID=$!

  for _ in $(seq 1 100); do
    [[ -S "$TAILSCALE_SOCKET" ]] && break
    kill -0 "$TAILSCALE_PID" 2>/dev/null || {
      tail -n 20 "$WORK_ROOT/tailscaled.log" >&2 || true
      die "The temporary Tailscale daemon exited before becoming ready."
    }
    sleep 0.1
  done
  [[ -S "$TAILSCALE_SOCKET" ]] || die "The temporary Tailscale daemon did not become ready."

  local up_args=(up --reset --hostname="$loader_hostname" --advertise-tags="$NODE_TAG"
    --accept-dns=false --accept-routes=false --ssh=false --qr --timeout="$AUTH_TIMEOUT")
  [[ -z "$LOGIN_SERVER" ]] || up_args+=(--login-server="$LOGIN_SERVER")

  msg "Approve the new '${NODE_TAG}' device in the browser or with the QR code below."
  if ! timeout --foreground "$AUTH_TIMEOUT" \
    tailscale --socket="$TAILSCALE_SOCKET" "${up_args[@]}"; then
    die "Tailscale authentication was not completed within $AUTH_TIMEOUT."
  fi
  ts status --json | jq -e '.BackendState == "Running" and (.Self.ID | type == "string" and length > 0)' >/dev/null \
    || die "Tailscale did not reach a connected state."
  REVOKE_ON_EXIT=1
}

verify_and_clone() {
  local repo_host checkout ref_selector commit
  repo_host="$(git_remote_host "$REPO_URL")"
  checkout="$WORK_ROOT/checkout"

  msg "Checking Tailnet reachability for Git host '${repo_host}'..."
  ts ping --timeout=10s --c=3 --until-direct=false "$repo_host" >/dev/null \
    || die "Git host '${repo_host}' is not reachable in the authenticated Tailnet."

  export GIT_TERMINAL_PROMPT=0
  export GIT_SSH_COMMAND="tailscale --socket=$TAILSCALE_SOCKET ssh"
  export GIT_SSH_VARIANT=simple
  if [[ "$GIT_REF_KIND" == branch ]]; then
    ref_selector="refs/heads/${GIT_REF}"
    msg "Verifying private branch '${GIT_REF}'..."
    git ls-remote --exit-code --heads "$REPO_URL" "$ref_selector" >/dev/null \
      || die "Branch not found or inaccessible in ${REPO_URL}: ${GIT_REF}"
  else
    ref_selector="refs/tags/${GIT_REF}"
    msg "Verifying private tag '${GIT_REF}'..."
    git ls-remote --exit-code --tags "$REPO_URL" "$ref_selector" >/dev/null \
      || die "Tag not found or inaccessible in ${REPO_URL}: ${GIT_REF}"
  fi

  msg "Cloning the selected private ref once into volatile memory..."
  (umask 022; git clone --quiet --depth 1 --branch "$GIT_REF" "$REPO_URL" "$checkout") \
    || die "Unable to clone ${REPO_URL} ref ${GIT_REF}."
  commit="$(git -C "$checkout" rev-parse --verify HEAD)"
  [[ "$commit" =~ ^[0-9a-f]{40}$ ]] || die "The staged checkout did not resolve to a full Git commit."
  [[ -f "$checkout/bootstrap.sh" && -f "$checkout/configuration.nix" && -f "$checkout/nixpkgs.nix" ]] \
    || die "The selected ref is not a FleetCommandOS installation repository."
  bash -n "$checkout/bootstrap.sh" || die "The selected private bootstrap has invalid shell syntax."
  git -C "$checkout" config fleetcommand.transport tailscale-ssh
  chmod 0755 "$checkout"
}

write_handoff_and_exec() {
  local checkout manifest commit stable_id ipv4
  checkout="$WORK_ROOT/checkout"
  manifest="$WORK_ROOT/install-handoff.json"
  commit="$(git -C "$checkout" rev-parse --verify HEAD)"
  stable_id="$(ts status --json | jq -er '.Self.ID')"
  ipv4="$(ts status --json | jq -er '.Self.TailscaleIPs[] | select(test("^[0-9]+\\."))' | head -n 1)"
  [[ -n "$ipv4" ]] || die "The authenticated installer identity has no Tailscale IPv4 address."

  stop_tailscaled
  [[ -s "$TAILSCALE_STATE" ]] || die "The temporary Tailscale state file is missing."
  chmod 0600 "$TAILSCALE_STATE"

  jq -n \
    --arg checkout "$checkout" \
    --arg repoUrl "$REPO_URL" \
    --arg refKind "$GIT_REF_KIND" \
    --arg ref "$GIT_REF" \
    --arg commit "$commit" \
    --arg stateFile "$TAILSCALE_STATE" \
    --arg stableId "$stable_id" \
    --arg ipv4 "$ipv4" \
    --arg loginServer "$LOGIN_SERVER" \
    --arg nodeTag "$NODE_TAG" \
    '{schemaVersion:1, checkout:$checkout, repoUrl:$repoUrl, refKind:$refKind, ref:$ref,
      commit:$commit, tailscale:{stateFile:$stateFile, stableId:$stableId, ipv4:$ipv4,
      loginServer:(if $loginServer == "" then null else $loginServer end), nodeTag:$nodeTag}}' \
    > "$manifest"
  chmod 0600 "$manifest"

  local bootstrap_args=(--install --install-handoff "$manifest" --repo-url "$REPO_URL")
  if [[ "$GIT_REF_KIND" == branch ]]; then
    bootstrap_args+=(-b "$GIT_REF")
  else
    bootstrap_args+=(-t "$GIT_REF")
  fi
  bootstrap_args+=("${FORWARDED_BOOTSTRAP_ARGS[@]}")
  [[ -z "$HOSTNAME_ARG" ]] || bootstrap_args+=("$HOSTNAME_ARG")

  REVOKE_ON_EXIT=0
  trap - EXIT INT TERM
  msg "Private source verified at commit ${commit}. Handing off to its installer..."
  exec bash "$checkout/bootstrap.sh" "${bootstrap_args[@]}"
}

main() {
  parse_args "$@"
  normalize_nixos_path
  check_root_and_runtime
  create_work_root
  trap cleanup EXIT
  trap 'exit 130' INT TERM
  ensure_tools
  start_tailscaled
  verify_and_clone
  write_handoff_and_exec
}

if [[ "${FCOS_PUBLIC_LOADER_TESTING:-0}" != 1 ]]; then
  main "$@"
fi
