#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat >&2 <<'EOF'
Usage:
  ./install.sh [--repo <git-url>] check <host>
  ./install.sh [--repo <git-url>] key-check <host>
  ./install.sh [--repo <git-url>] preflight <host> [target-host]
  ./install.sh remote <host> <target-host> [nixos-anywhere args...]
  ./install.sh local  <host> <mountpoint>

Examples:
  curl -L https://raw.githubusercontent.com/bresilla/nixos/refs/heads/main/install.sh | bash -s -- check core
  curl -L https://raw.githubusercontent.com/bresilla/nixos/refs/heads/main/install.sh | bash -s -- key-check core
  curl -L https://raw.githubusercontent.com/bresilla/nixos/refs/heads/main/install.sh | bash -s -- preflight core nixos@192.168.100.163
  curl -L https://raw.githubusercontent.com/bresilla/nixos/refs/heads/main/install.sh | bash -s -- remote core nixos@192.168.100.163
  ./install.sh check core
  ./install.sh key-check core
  ./install.sh preflight core nixos@192.168.100.163
  ./install.sh remote core nixos@192.168.100.163
  ./install.sh local core /mnt
EOF
}

die() {
  echo "error: $*" >&2
  exit 1
}

default_repo="https://github.com/bresilla/nixos.git"
repo_url="${NIXOS_INSTALL_REPO:-$default_repo}"

if [[ "${1:-}" == "--repo" ]]; then
  repo_url="${2:-}"
  [[ -n "$repo_url" ]] || die "--repo requires a git URL"
  shift 2
fi

mode="${1:-}"
host="${2:-}"

if [[ -z "$mode" || -z "$host" ]]; then
  usage
  exit 2
fi

script_source="${BASH_SOURCE[0]-}"
if [[ -n "$script_source" && "$script_source" != "bash" && "$script_source" != "-" ]]; then
  repo_dir="$(cd -- "$(dirname -- "$script_source")" && pwd)"
else
  repo_dir="$(pwd)"
fi

if [[ ! -f "$repo_dir/flake.nix" || ! -f "$repo_dir/.sops.yaml" || ! -d "$repo_dir/secrets/host-keys" ]]; then
  command -v git >/dev/null || die "git is not in PATH and the script is not running from a repo checkout"

  tmp_repo="$(mktemp -d)"
  trap 'rm -rf "$tmp_repo"' EXIT

  git clone --depth 1 "$repo_url" "$tmp_repo/repo" >/dev/null
  exec "$tmp_repo/repo/install.sh" "$@"
fi

encrypted_key="$repo_dir/secrets/host-keys/$host.txt"
expected_recipient="$(
  awk -v host="$host" '
    $1 == "-" && $2 == "&" host { print $3; exit }
    $1 == "-" && $2 == "\\&" host { print $3; exit }
    $1 == "-" && $2 == ("&" host) { print $3; exit }
  ' "$repo_dir/.sops.yaml"
)"

[[ -f "$encrypted_key" ]] || die "missing encrypted host key: $encrypted_key"
[[ -n "$expected_recipient" ]] || die "missing public recipient for host '$host' in $repo_dir/.sops.yaml"

decrypt_host_key() (
  [[ -r /dev/tty ]] || die "no /dev/tty available for YubiKey PIN prompt"

  local age_bin identity_file
  age_bin="$(find_age)" || die "working age is not in PATH"

  identity_file="$(mktemp)"
  trap 'rm -f "$identity_file"' EXIT

  age-plugin-yubikey --identity | awk '/^AGE-PLUGIN-YUBIKEY-/ { print; found = 1 } END { exit found ? 0 : 1 }' > "$identity_file" \
    || die "failed to read YubiKey age identity"

  "$age_bin" --decrypt --identity "$identity_file" "$encrypted_key" < /dev/tty
)

find_age() {
  local candidate
  for candidate in /usr/bin/age /bin/age "$(command -v age 2>/dev/null || true)"; do
    [[ -n "$candidate" ]] || continue
    "$candidate" --version >/dev/null 2>&1 || continue
    printf '%s\n' "$candidate"
    return 0
  done
  return 1
}

find_age_keygen() {
  local candidate
  for candidate in /usr/bin/age-keygen /bin/age-keygen "$(command -v age-keygen 2>/dev/null || true)"; do
    [[ -n "$candidate" ]] || continue
    "$candidate" -version >/dev/null 2>&1 || continue
    printf '%s\n' "$candidate"
    return 0
  done
  return 1
}

check_host_key() (
  command -v age-plugin-yubikey >/dev/null || die "age-plugin-yubikey is not in PATH"

  local age_keygen tmp actual_recipient
  age_keygen="$(find_age_keygen)" || die "working age-keygen is not in PATH"

  tmp="$(mktemp -d)"
  trap 'rm -rf "$tmp"' EXIT

  decrypt_host_key > "$tmp/key.txt"
  chmod 0600 "$tmp/key.txt"

  actual_recipient="$("$age_keygen" -y "$tmp/key.txt")"
  [[ "$actual_recipient" == "$expected_recipient" ]] || {
    echo "expected: $expected_recipient" >&2
    echo "actual:   $actual_recipient" >&2
    die "decrypted host key does not match .sops.yaml recipient for $host"
  }

  echo "$actual_recipient"
)

case "$mode" in
  check)
    echo "repo: $repo_dir"
    echo "host: $host"
    echo "encrypted host key: $encrypted_key"
    echo "expected recipient: $expected_recipient"
    ;;

  key-check)
    actual_recipient="$(check_host_key)"

    echo "repo: $repo_dir"
    echo "host: $host"
    echo "recipient: $actual_recipient"
    echo "key-check: ok"
    ;;

  preflight)
    target="${3:-}"

    echo "repo: $repo_dir"
    echo "host: $host"

    [[ -d "$repo_dir/hosts/$host" ]] || die "missing host directory: $repo_dir/hosts/$host"
    [[ -f "$repo_dir/hosts/$host/disko.nix" ]] || die "missing disko file: $repo_dir/hosts/$host/disko.nix"
    [[ -f "$repo_dir/secrets/hosts/$host.yaml" ]] || die "missing host secrets file: $repo_dir/secrets/hosts/$host.yaml"
    echo "repo files: ok"

    actual_recipient="$(check_host_key)"
    echo "host key: ok ($actual_recipient)"

    command -v nix >/dev/null || die "nix is not in PATH"
    nix --extra-experimental-features 'nix-command flakes' eval \
      "$repo_dir#nixosConfigurations.$host.config.sops.age.keyFile" >/dev/null
    nix --extra-experimental-features 'nix-command flakes' eval \
      "$repo_dir#nixosConfigurations.$host.config.sops.secrets.\"netbird/setup_key\".path" >/dev/null
    echo "nix eval: ok"

    if [[ -n "$target" ]]; then
      command -v ssh >/dev/null || die "ssh is not in PATH"
      ssh -F /dev/null -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new "$target" true
      echo "ssh: ok ($target)"
    fi

    echo "preflight: ok"
    ;;

  remote)
    target="${3:-}"
    [[ -n "$target" ]] || {
      usage
      exit 2
    }
    shift 3

    command -v nixos-anywhere >/dev/null || die "nixos-anywhere is not in PATH"
    command -v sops >/dev/null || die "sops is not in PATH"
    command -v age-plugin-yubikey >/dev/null || die "age-plugin-yubikey is not in PATH"

    tmp="$(mktemp -d)"
    trap 'rm -rf "$tmp"' EXIT

    install -d -m 0755 "$tmp/var/lib/sops-nix"
    decrypt_host_key > "$tmp/var/lib/sops-nix/key.txt"
    chmod 0600 "$tmp/var/lib/sops-nix/key.txt"

    nixos-anywhere \
      --extra-files "$tmp" \
      --flake "$repo_dir#$host" \
      "$@" \
      "$target"
    ;;

  local)
    mountpoint="${3:-}"
    [[ -n "$mountpoint" ]] || {
      usage
      exit 2
    }

    command -v sops >/dev/null || die "sops is not in PATH"
    command -v age-plugin-yubikey >/dev/null || die "age-plugin-yubikey is not in PATH"
    [[ -d "$mountpoint" ]] || die "mountpoint does not exist: $mountpoint"

    install -d -m 0755 "$mountpoint/var/lib/sops-nix"
    decrypt_host_key > "$mountpoint/var/lib/sops-nix/key.txt"
    chmod 0600 "$mountpoint/var/lib/sops-nix/key.txt"
    ;;

  *)
    usage
    exit 2
    ;;
esac
