#!/bin/bash
# qxvault-setup — Bootstrap OpenBao AppRole credentials for proxmox-kms-bridge
# Part of proxmox-kms-bridge (45Drives SecureVM for Proxmox)
#
# This script configures the OpenBao backend and writes local AppRole
# credentials so that vault-dmkey can authenticate automatically.
#
# Must be run as root. Requires a Vault root/admin token for initial setup.
set -euo pipefail

CONF_FILE="/etc/qxvault/qxvault.conf"
CONF_EXAMPLE="/usr/share/proxmox-kms-bridge/qxvault.conf.example"

VAULT_DMKEY_CONF_FILE="/etc/vault-dmkey/vault-dmkey.toml"

# ── Defaults ───────────────────────────────────────────────────────
OPENBAO_ADDR="https://127.0.0.1:8200"
OPENBAO_CACERT=""
OPENBAO_SKIP_VERIFY="false"
OPENBAO_TIMEOUT="10"
OPENBAO_ROLE_ID_FILE="/etc/qxvault/role-id"
OPENBAO_SECRET_ID_FILE="/etc/qxvault/secret-id"

# Load config if it exists
if [ -f "$CONF_FILE" ]; then
  # shellcheck source=/dev/null
  . "$CONF_FILE"
fi

# ── Helpers ────────────────────────────────────────────────────────
log() { echo "[qxvault-setup] $*"; }
die() { echo "[qxvault-setup] ERROR: $*" >&2; exit 1; }

vault_curl() {
  local -a curl_args=( -s --connect-timeout "$OPENBAO_TIMEOUT" --max-time "$((OPENBAO_TIMEOUT * 2))" )
  if [ -n "$OPENBAO_CACERT" ]; then
    curl_args+=( --cacert "$OPENBAO_CACERT" )
  fi
  if [ "$OPENBAO_SKIP_VERIFY" = "true" ]; then
    curl_args+=( -k )
  fi
  curl "${curl_args[@]}" "$@"
}

check_response() {
  local resp="$1" context="$2"
  local errors
  errors=$(echo "$resp" | jq -r '.errors // empty' 2>/dev/null)
  if [ -n "$errors" ] && [ "$errors" != "null" ]; then
    die "$context: $errors"
  fi
}

# ── Preflight ──────────────────────────────────────────────────────
[ "$(id -u)" -eq 0 ] || die "Must run as root"

if [ -z "${VAULT_TOKEN:-}" ]; then
  echo -n "Enter Vault root/admin token: "
  read -rs VAULT_TOKEN
  echo
fi
[ -n "$VAULT_TOKEN" ] || die "No token provided"

# ── Test connectivity ──────────────────────────────────────────────
log "Testing connection to $OPENBAO_ADDR..."
HEALTH=$(vault_curl "${OPENBAO_ADDR}/v1/sys/health") \
  || die "Cannot reach Vault at $OPENBAO_ADDR"
SEALED=$(echo "$HEALTH" | jq -r '.sealed')
[ "$SEALED" = "false" ] || die "Vault is sealed"
log "Vault is reachable and unsealed."

# ── Enable transit engine at transit/ (idempotent) ──────────────────────────
log "Ensuring KV v2 transit engine at transit/..."
MOUNTS=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" "${OPENBAO_ADDR}/v1/sys/mounts")
if echo "$MOUNTS" | jq -e '.["transit/"]' >/dev/null 2>&1; then
  log "transit engine already mounted at transit/"
else
  RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" -X POST \
    "${OPENBAO_ADDR}/v1/sys/mounts/transit" \
    -d '{"type":"transit"}')
  check_response "${RESP:-}" "Enable transit"
  log "Enabled transit engine at transit/"
fi

# ── Enable AppRole auth (idempotent) ──────────────────────────────
log "Ensuring AppRole auth method..."
AUTH_METHODS=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" "${OPENBAO_ADDR}/v1/sys/auth")
if echo "$AUTH_METHODS" | jq -e '.["approle/"]' >/dev/null 2>&1; then
  log "AppRole auth already enabled"
else
  RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" -X POST \
    "${OPENBAO_ADDR}/v1/sys/auth/approle" \
    -d '{"type":"approle"}')
  check_response "${RESP:-}" "Enable AppRole"
  log "Enabled AppRole auth"
fi

# ── Create qxvault policy ─────────────────────────────────────────
log "Writing qxvault policy..."

POLICY='
# Transit engine — used by vault-dmkey for key management
path "transit/keys/*" {
	capabilities = ["create", "read", "update", "delete"]
}

path "transit/export/encryption-key/*" {
	capabilities = ["read"]
}

path "transit/keys/" {
	capabilities = ["list"]
}

path "transit/keys/+/rotate" {
	capabilities = ["update"]
}

path "transit/*" {
  capabilities = ["update"]
}

# KV v2 engine — legacy, kept for backward compatibility with existing data
path "secret/data/qxvault/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/metadata/qxvault/*" {
  capabilities = ["list", "read", "delete"]
}
'

RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" -X PUT \
  "${OPENBAO_ADDR}/v1/sys/policies/acl/qxvault" \
  -d "{\"policy\":$(echo "$POLICY" | jq -Rs .)}")
check_response "${RESP:-}" "Write policy"
log "Policy 'qxvault' written"

# ── Create AppRole role ───────────────────────────────────────────
log "Creating AppRole role 'qxvault'..."
RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" -X POST \
  "${OPENBAO_ADDR}/v1/auth/approle/role/qxvault" \
  -d '{"token_policies":"qxvault","token_ttl":"1h","token_max_ttl":"4h","secret_id_ttl":"0"}')
check_response "${RESP:-}" "Create AppRole"
log "AppRole role 'qxvault' created"

# ── Fetch Role ID ─────────────────────────────────────────────────
log "Fetching role-id..."
RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" \
  "${OPENBAO_ADDR}/v1/auth/approle/role/qxvault/role-id")
ROLE_ID=$(echo "$RESP" | jq -r '.data.role_id')
if [ -z "$ROLE_ID" ] || [ "$ROLE_ID" = "null" ]; then
  die "Failed to get role-id"
fi

# ── Generate Secret ID ────────────────────────────────────────────
log "Generating secret-id..."
RESP=$(vault_curl -H "X-Vault-Token: $VAULT_TOKEN" -X POST \
  "${OPENBAO_ADDR}/v1/auth/approle/role/qxvault/secret-id")
SECRET_ID=$(echo "$RESP" | jq -r '.data.secret_id')
if [ -z "$SECRET_ID" ] || [ "$SECRET_ID" = "null" ]; then
  die "Failed to generate secret-id"
fi

# ── Write credentials to disk ─────────────────────────────────────
install -d -m 0700 /etc/qxvault

install -m 0600 /dev/null "$OPENBAO_ROLE_ID_FILE"
echo "$ROLE_ID" > "$OPENBAO_ROLE_ID_FILE"
log "Wrote role-id to $OPENBAO_ROLE_ID_FILE"

install -m 0600 /dev/null "$OPENBAO_SECRET_ID_FILE"
echo "$SECRET_ID" > "$OPENBAO_SECRET_ID_FILE"
log "Wrote secret-id to $OPENBAO_SECRET_ID_FILE"

# ── Create config if missing ──────────────────────────────────────
if [ ! -f "$CONF_FILE" ]; then
  if [ -f "$CONF_EXAMPLE" ]; then
    install -m 0600 "$CONF_EXAMPLE" "$CONF_FILE"
  else
    install -m 0600 /dev/null "$CONF_FILE"
    cat > "$CONF_FILE" <<CONFEOF
OPENBAO_ADDR="$OPENBAO_ADDR"
OPENBAO_AUTH_METHOD="approle"
OPENBAO_ROLE_ID_FILE="$OPENBAO_ROLE_ID_FILE"
OPENBAO_SECRET_ID_FILE="$OPENBAO_SECRET_ID_FILE"
OPENBAO_SKIP_VERIFY="$OPENBAO_SKIP_VERIFY"
LOG_LEVEL="info"
# Failure policy is always fail-closed (only safe behavior)
FAILURE_POLICY="fail-closed"
CONFEOF
  fi
  log "Created config at $CONF_FILE"

  # Update OPENBAO_ADDR in config if it differs from default
  if [ "$OPENBAO_ADDR" != "https://127.0.0.1:8200" ]; then
    sed -i "s|OPENBAO_ADDR=\"https://127.0.0.1:8200\"|OPENBAO_ADDR=\"$OPENBAO_ADDR\"|" "$CONF_FILE"
  fi
fi

# TODO: Generate this elsewhere? To update the config from the Proxmox Config UI?
# Provision vault-dmkey config directory and file
if [ ! -d "$(dirname $VAULT_DMKEY_CONF_FILE)" ]; then
  mkdir -p "$(dirname $VAULT_DMKEY_CONF_FILE)"

  if [ ! -f "$VAULT_DMKEY_CONF_FILE" ]; then
    install -m 0600 /dev/null "$VAULT_DMKEY_CONF_FILE"
  fi
fi

# Derive the vault-dmkey config
{
  echo "vault_addr = \"$OPENBAO_ADDR\""
  [ -n "$OPENBAO_CACERT" ] && echo "vault_ca_cert = \"$OPENBAO_CACERT\""
  [ "$OPENBAO_SKIP_VERIFY" = "true" ] && echo "skip_tls_verify = true"
  echo "app_role_id = \"$ROLE_ID\""
  echo "app_role_secret_id = \"$SECRET_ID\""
  echo 'namespace = "root"'
} > "$VAULT_DMKEY_CONF_FILE"

# ── Verify ─────────────────────────────────────────────────────────
log "Verifying AppRole login..."
RESP=$(vault_curl -X POST "${OPENBAO_ADDR}/v1/auth/approle/login" \
  -d "{\"role_id\":\"$ROLE_ID\",\"secret_id\":\"$SECRET_ID\"}")
VERIFY_TOKEN=$(echo "$RESP" | jq -r '.auth.client_token // empty')
[ -n "$VERIFY_TOKEN" ] || die "AppRole login verification failed"
log "AppRole login verified successfully!"

# ── Clear token cache ─────────────────────────────────────────────
rm -f /run/qxvault/token

echo
log "=========================================="
log "  Setup complete!"
log "=========================================="
log "  Vault:       $OPENBAO_ADDR"
log "  Config:      $CONF_FILE"
log "  Role ID:     $OPENBAO_ROLE_ID_FILE"
log "  Secret ID:   $OPENBAO_SECRET_ID_FILE"
log ""
log "  Next steps:"
log "    1. Edit $CONF_FILE if needed"
log "    2. Encrypt a VM:  migrate-vm-luks <VMID>"
log "=========================================="
