#!/usr/bin/env bash
# Provision Vaultwarden for Officer.
#
# Shape 2 from ../README.md: nothing is asked for, because the one credential that matters is GENERATED
# here. There is no sensible way for a user to invent an admin token, and asking for one produces a
# weaker secret than `openssl rand` does.
#
#   OFFICER_SERVICE_DIR   where to write
#   OFFICER_UID/GID       who the container runs as
#   VAULTWARDEN_PORT      loopback port                     (default 8222)
#
# Idempotent, including the token: an existing one is REUSED rather than rotated, because rotating on a
# re-run would lock the owner out of the admin page during a routine resumed install.

set -euo pipefail

SERVICE_DIR="${OFFICER_SERVICE_DIR:?OFFICER_SERVICE_DIR is required}"
TEMPLATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

OFFICER_UID="${OFFICER_UID:-$(id -u)}"
OFFICER_GID="${OFFICER_GID:-$(id -g)}"
OFFICER_TZ="${OFFICER_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
VAULTWARDEN_PORT="${VAULTWARDEN_PORT:-8222}"

command -v docker >/dev/null || { echo "error: docker is not installed" >&2; exit 2; }
command -v openssl >/dev/null || { echo "error: openssl is required to generate the admin token" >&2; exit 2; }

mkdir -p "$SERVICE_DIR/data"

# ── The admin token ───────────────────────────────────────────────────────────────────────────────────
# Kept in a 0600 file inside the service directory rather than only in the database, so the owner can
# still reach the admin page if Officer is down — which is exactly when they might need to.
TOKEN_FILE="$SERVICE_DIR/.admin-token"

if [ -f "$TOKEN_FILE" ]; then
  echo "==> Reusing the existing admin token"
  VAULTWARDEN_ADMIN_TOKEN="$(cat "$TOKEN_FILE")"
else
  echo "==> Generating an admin token"
  VAULTWARDEN_ADMIN_TOKEN="$(openssl rand -base64 48 | tr -d '\n')"
  ( umask 077; printf '%s' "$VAULTWARDEN_ADMIN_TOKEN" > "$TOKEN_FILE" )
fi

# Vaultwarden accepts a plaintext token but warns loudly and recommends an Argon2 hash; `vaultwarden
# hash` does not exist as a standalone binary, so the hash is produced by the image itself. Falling back
# to plaintext rather than failing: a working install with a warning beats no install at all, and the
# token is only reachable over loopback.
echo "==> Hashing it"
if VAULTWARDEN_ADMIN_TOKEN_HASH="$(printf '%s' "$VAULTWARDEN_ADMIN_TOKEN" \
      | docker run --rm -i vaultwarden/server:latest /vaultwarden hash --preset owasp 2>/dev/null \
      | grep -oE '\$argon2[^ ]*' | head -1)" && [ -n "$VAULTWARDEN_ADMIN_TOKEN_HASH" ]; then
  # Compose reads `$` as interpolation, so a literal Argon2 hash must have every `$` doubled or the
  # container receives a mangled token and rejects every admin login with no useful error.
  VAULTWARDEN_ADMIN_TOKEN_HASH="${VAULTWARDEN_ADMIN_TOKEN_HASH//\$/\$\$}"
else
  echo "    (could not hash — falling back to a plaintext token, which Vaultwarden will warn about)"
  VAULTWARDEN_ADMIN_TOKEN_HASH="$VAULTWARDEN_ADMIN_TOKEN"
fi

# ── Render and start ──────────────────────────────────────────────────────────────────────────────────
echo "==> Preparing $SERVICE_DIR"
export OFFICER_UID OFFICER_GID OFFICER_TZ VAULTWARDEN_PORT VAULTWARDEN_ADMIN_TOKEN_HASH
envsubst '${OFFICER_UID} ${OFFICER_GID} ${OFFICER_TZ} ${VAULTWARDEN_PORT} ${VAULTWARDEN_ADMIN_TOKEN_HASH}' \
  < "$TEMPLATE_DIR/docker-compose.yaml" > "$SERVICE_DIR/docker-compose.yaml"

echo "==> Starting the container"
docker compose --project-directory "$SERVICE_DIR" up -d

echo "==> Waiting for Vaultwarden to answer"
for i in $(seq 1 60); do
  code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${VAULTWARDEN_PORT}/alive" || true)"
  if [ "$code" = "200" ]; then echo "    up after ${i}s"; break; fi
  if [ "$i" = "60" ]; then
    echo "error: Vaultwarden did not answer on 127.0.0.1:${VAULTWARDEN_PORT} within 60s" >&2
    echo "       check: docker compose --project-directory '$SERVICE_DIR' logs" >&2
    exit 1
  fi
  sleep 1
done

# The URL is all the platform stores. The admin token is deliberately NOT returned: the vault sidecar
# proxies the Bitwarden protocol and never needs it, and a secret the platform does not hold is a secret
# it cannot leak. It is in $TOKEN_FILE for the owner.
echo "OFFICER_RESULT_URL=http://127.0.0.1:${VAULTWARDEN_PORT}"
echo "==> Done. Admin token: $TOKEN_FILE (0600, not stored by Officer)"
