#!/usr/bin/env bash
# sshking-user: get SSH certificates from SSHKing (sshking.app). Needs bash (3.2+), curl and ssh-keygen.
# For your laptop or workstation. Servers use sshking-server instead.
#
#   sshking-user login [-e ENV] [-k KEY] [-t 8h]   sign in (first time: approve a code in the browser) and get certificates;
#                                             also sets up automatic renewal when you ssh (see setup-ssh)
#   sshking-user setup-ssh [--remove]                add/remove the ~/.ssh/config rule that renews certificates on ssh
#   sshking-user rotate-key [-e ENV] [-y]            make a new ed25519 key, register it, revoke the old one (asks first)
#   sshking-user envs                                list environments you can get certificates for
#   sshking-user status [-k KEY]                     show your certificates
#   sshking-user logout                              forget the CLI session
#
# Config (env vars or KEY=VALUE in ~/.config/sshca/config): SSHCA_URL, SSHCA_ENV (default environment)
# legacy token mode: SSHCA_TOKEN / SSHCA_TOKEN_FILE (signer tokens from /admin)

set -euo pipefail
# systemd timers and cron run without HOME; fall back to the account's home directory (root: /root)
HOME="${HOME:-$(getent passwd "$(id -u)" 2>/dev/null | cut -d: -f6)}"; HOME="${HOME:-/root}"; export HOME

die() { echo "sshking-user: $*" >&2; exit 1; }
CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/sshca"
LOGIN_ENV="${SSHCA_ENV:-}"   # your default environment: from the environment or ~/.config/sshca/config, never a server's /etc/sshca.conf
for f in /etc/sshca.conf "$CONF_DIR/config"; do
  if [[ -r "$f" ]]; then
    while IFS='=' read -r k v; do
      [[ "$k" =~ ^SSHCA_[A-Z_]+$ ]] || continue
      if [[ -z "${!k:-}" ]]; then v="${v%\"}"; v="${v#\"}"; export "$k=$v"; [[ "$k" == SSHCA_ENV && "$f" != /etc/sshca.conf ]] && LOGIN_ENV="$v"; fi
    done < "$f"
  fi
done
SSHCA_URL="${SSHCA_URL:-https://sshking.app}"; SSHCA_URL="${SSHCA_URL%/}"   # filled in by the service when downloaded
[[ "$SSHCA_URL" == __SSHKING_URL* ]] && SSHCA_URL=""
SESSION_FILE="$CONF_DIR/session"


need_url() { [[ -n "$SSHCA_URL" ]] || die "set SSHCA_URL (e.g. export SSHCA_URL=https://ssh-ca.example.workers.dev)"; }

secs() { # 8h / 30d / 90m / 3600 -> seconds
  local v="$1"; [[ "$v" =~ ^([0-9]+)([smhd]?)$ ]] || die "bad duration '$v'"
  local n="${BASH_REMATCH[1]}"
  case "${BASH_REMATCH[2]}" in m) n=$((n*60));; h) n=$((n*3600));; d) n=$((n*86400));; esac
  echo "$n"
}

# kv FILE KEY -> value from "key value" response lines
kv() { awk -v k="$2" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$1"; }

# req METHOD PATH [JSON] [AUTH] -> body to stdout; dies with the server's message on HTTP errors
req() {
  local method="$1" path="$2" data="${3:-}" auth="${4:-}" tmp code
  tmp="$(mktemp)"
  local args=(-sS -o "$tmp" -w '%{http_code}' -X "$method" "$SSHCA_URL$path")
  [[ -n "$auth" ]] && args+=(-H "authorization: Bearer $auth")
  [[ -n "$data" ]] && args+=(-H 'content-type: application/json' --data "$data")
  code=$(curl "${args[@]}") || { rm -f "$tmp"; die "cannot reach $SSHCA_URL"; }
  [[ -n "${REQ_CODE_FILE:-}" ]] && printf '%s' "$code" > "$REQ_CODE_FILE"
  if [[ "$code" != 2* ]]; then
    local msg; msg="$(sed -n 's/.*"error": *"\(.*\)".*/\1/p' "$tmp" | head -1)"
    rm -f "$tmp"; echo "sshking-user: ${msg:-HTTP $code}" >&2; return 22
  fi
  cat "$tmp"; rm -f "$tmp"
}

pubkey_line() { # file -> "type base64" (comment dropped; keeps JSON simple)
  local key; key="$(awk '{print $1" "$2; exit}' "$1")"
  [[ "$key" =~ ^[a-z0-9@.-]+\ [A-Za-z0-9+/=]+$ ]] || die "$1 does not look like an OpenSSH public key"
  echo "$key"
}




session_token() { [[ -r "$SESSION_FILE" ]] && tr -d '[:space:]' < "$SESSION_FILE" || true; }

open_browser() {
  [[ -z "${SSHCA_NO_BROWSER:-}" ]] || return 0
  if command -v open >/dev/null 2>&1; then open "$1" >/dev/null 2>&1 || true
  elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$1" >/dev/null 2>&1 || true; fi
}

# cli_wait START_FILE -> waits for approval in the browser; prints the final poll response
cli_wait() {
  local dc interval out
  dc="$(kv "$1" device_code)"; interval="$(kv "$1" interval)"
  for _ in $(seq 1 200); do
    sleep "${interval:-3}"
    out="$(req POST "/api/cli/poll?format=text" "{\"device_code\":\"$dc\"}")" || return 1
    if [[ "$(printf '%s\n' "$out" | awk '$1=="status"{print $2}')" == approved ]]; then printf '%s\n' "$out"; return 0; fi
  done
  echo "sshking-user: timed out waiting for approval" >&2; return 1
}

# reconfirm: the organisation wants people to confirm it's them every so often; refresh this CLI session
reconfirm() {
  local tmp url uc; tmp="$(mktemp)"
  req POST "/api/cli/reconfirm?format=text" "{}" "$(session_token)" > "$tmp" || { rm -f "$tmp"; return 1; }
  url="$(kv "$tmp" verify_url)"; uc="$(kv "$tmp" user_code)"
  { echo "sshking-user: please confirm it's you. Open:  $url"; echo "sshking-user: and confirm the code: $uc"; } >&2
  open_browser "$url"
  cli_wait "$tmp" >/dev/null || { rm -f "$tmp"; return 1; }
  rm -f "$tmp"; echo "sshking-user: confirmed" >&2
}

cli_login() {
  need_url
  local tmp; tmp="$(mktemp)"
  req POST "/api/cli/start?format=text" "{\"label\":\"sshking-user on $(hostname -s 2>/dev/null || hostname)\"}" > "$tmp" || die "could not start login"
  local url uc; url="$(kv "$tmp" verify_url)"; uc="$(kv "$tmp" user_code)"
  echo "To sign in, open:  $url"
  echo "and confirm the code: $uc"
  open_browser "$url"
  local out tok
  out="$(cli_wait "$tmp")" || { rm -f "$tmp"; exit 1; }
  rm -f "$tmp"
  if true; then
      tok="$(printf '%s\n' "$out" | awk '$1=="token"{print $2}')"
      mkdir -p "$CONF_DIR"; chmod 700 "$CONF_DIR"
      ( umask 077; printf '%s\n' "$tok" > "$SESSION_FILE" )
      grep -qs '^SSHCA_URL=' "$CONF_DIR/config" || echo "SSHCA_URL=$SSHCA_URL" >> "$CONF_DIR/config"
      echo "signed in as $(printf '%s\n' "$out" | awk '$1=="email"{print $2}') (username $(printf '%s\n' "$out" | awk '$1=="username"{print $2}'))"
      return 0
  fi
}

ensure_session() {
  need_url
  local t; t="$(session_token)"
  if [[ -n "$t" ]] && req GET /api/me "" "$t" >/dev/null 2>&1; then return 0; fi
  cli_login
}

default_key() {
  for k in id_ed25519 id_ecdsa id_ed25519_sk id_ecdsa_sk id_rsa; do
    [[ -f "$HOME/.ssh/$k.pub" ]] && { echo "$HOME/.ssh/$k"; return; }
  done
}

user_known_hosts() { # env
  local f="$HOME/.ssh/sshca_known_hosts"
  mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"
  local tmp; tmp="$(mktemp)"
  [[ -f "$f" ]] && grep -v " sshca-env=$1\$" "$f" > "$tmp" || true
  curl -fsS "$SSHCA_URL/v1/envs/$1/known_hosts" | sed "/^@cert-authority/s/\$/ sshca-env=$1/" >> "$tmp" || true
  mv -f "$tmp" "$f"; chmod 644 "$f"
  if ! grep -qs 'sshca_known_hosts' "$HOME/.ssh/config"; then
    { echo; echo "# added by sshca: trust host certificates"; echo "Host *"; echo "    UserKnownHostsFile ~/.ssh/known_hosts ~/.ssh/sshca_known_hosts"; } >> "$HOME/.ssh/config"
    chmod 600 "$HOME/.ssh/config"
    echo "added UserKnownHostsFile ~/.ssh/sshca_known_hosts to ~/.ssh/config"
  fi
}

# sign_user KEY ENV TTL [HOST] [AUTO] [DEST] -> writes the certificate (default KEY-cert.pub); returns 1 on failure
sign_user() {
  local key="$1" env="$2" ttl="$3" host="${4:-}" auto="${5:-}" dest="${6:-$1-cert.pub}" body out cf code try
  body="{\"public_key\":\"$(pubkey_line "$key.pub")\",\"register\":true,\"key_name\":\"$(hostname -s 2>/dev/null || hostname)\""
  [[ -n "$env" ]] && body+=",\"env_id\":\"$env\""
  [[ -n "$ttl" ]] && body+=",\"ttl\":$(secs "$ttl")"
  [[ -n "$host" ]] && body+=",\"host\":\"$host\""
  [[ -n "$auto" ]] && body+=",\"auto\":true"
  body+="}"
  cf="$(mktemp)"
  for try in 1 2; do
    : > "$cf"
    if out="$(REQ_CODE_FILE="$cf" req POST "/api/me/certs?format=text" "$body" "$(session_token)")"; then rm -f "$cf"; break; fi
    code="$(cat "$cf")"
    if [[ "$code" == 428 && $try == 1 ]] && reconfirm; then continue; fi
    rm -f "$cf"; [[ -n "$auto" ]] || echo "  (see your environments with: sshking-user envs)" >&2; return 1
  done
  mkdir -p "$(dirname "$dest")"
  printf '%s\n' "$out" > "$dest.tmp"; chmod 644 "$dest.tmp"; mv -f "$dest.tmp" "$dest"
}

# cert_meta CERT -> "valid_until issued confirmed key" (epochs; key = base64 of the certified public key)
to_epoch() { date -d "$1" +%s 2>/dev/null || date -j -f '%Y-%m-%dT%H:%M:%S' "$1" +%s 2>/dev/null || echo 0; }
cert_meta() {
  local info until kid i=0 v=0
  info="$(ssh-keygen -L -f "$1" 2>/dev/null)" || return 1
  until="$(printf '%s\n' "$info" | awk '/Valid:/{print $NF; exit}')"
  [[ "$until" == forever ]] && until=4102444800 || until="$(to_epoch "$until")"
  kid="$(printf '%s\n' "$info" | awk -F'"' '/Key ID/{print $2; exit}')"
  [[ "$kid" =~ ,i=([0-9]+),v=([0-9]+)/ ]] && { i="${BASH_REMATCH[1]}"; v="${BASH_REMATCH[2]}"; }
  echo "$until $i $v"
}

# ssh runs "sshking-user auto HOST" (from the ~/.ssh/config rule) before connecting. For servers you can reach through
# the service it makes sure ~/.ssh/sshca/certs/HOST-cert.pub is a fresh certificate and exits 0; otherwise exits 1
# quickly so ssh carries on as usual. Messages go to stderr; ssh shows them.
cmd_auto() {
  local host; host="$(printf '%s' "${1:-}" | tr 'A-Z' 'a-z')"
  [[ -n "$host" && -n "$SSHCA_URL" && -n "$(session_token)" ]] || exit 1
  local cache="$CONF_DIR/hosts" line=""
  [[ -f "$cache" ]] && line="$(awk -v h="$host" '$1==h{print; exit}' "$cache")"
  # refresh the list hourly, or after a miss (at most every 5 minutes, so ssh to other hosts stays fast)
  if { [[ -z "$line" ]] && [[ -z "$(find "$cache" -mmin -5 2>/dev/null)" ]]; } || [[ -z "$(find "$cache" -mmin -60 2>/dev/null)" ]]; then
    local tmp; tmp="$(mktemp)"
    if req GET "/api/me/hosts?format=text" "" "$(session_token)" > "$tmp" 2>/dev/null; then
      mkdir -p "$CONF_DIR"; mv -f "$tmp" "$cache"; line="$(awk -v h="$host" '$1==h{print; exit}' "$cache")"
    else rm -f "$tmp"; touch "$cache" 2>/dev/null || true; fi
  fi
  [[ -n "$line" ]] || exit 1
  local env dev mode maxc maxr; read -r _ env dev mode maxc maxr <<< "$line"
  local key; key="$(default_key)"; [[ -n "$key" ]] || exit 1
  local dir="$HOME/.ssh/sshca/certs" cert until i v now deadline
  cert="$dir/$env-cert.pub"; now="$(date +%s)"; deadline=0
  if [[ -f "$cert" ]] && [[ "$(awk '{print $2}' "$key.pub")" == "$(ssh-keygen -L -f "$cert" 2>/dev/null >/dev/null && awk '{print $2}' "$cert.key" 2>/dev/null)" ]]; then
    read -r until i v <<< "$(cert_meta "$cert")"
    deadline="$until"
    [[ "$i" -gt 0 && -n "$maxc" ]] && (( i + maxc < deadline )) && deadline=$((i + maxc))
    [[ "$v" -gt 0 && -n "$maxr" ]] && (( v + maxr < deadline )) && deadline=$((v + maxr))
    [[ "$i" == 0 ]] && deadline=0      # made before renewal limits existed; replace it
  fi
  if (( now > deadline - 600 )); then
    if [[ "$mode" != auto ]]; then
      (( now < deadline )) || { echo "sshking-user: automatic renewal is off for $host; run: sshking-user login -e $env" >&2; exit 1; }
    elif sign_user "$key" "$env" "" "$host" 1 "$cert"; then
      cp "$key.pub" "$cert.key"; echo "sshking-user: renewed certificate for $host" >&2
    elif (( now >= deadline )); then exit 1; fi
  fi
  ln -sf "$env-cert.pub" "$dir/$host-cert.pub"
  exit 0
}

SSH_MARK_BEGIN="# >>> sshking: automatic certificates (remove with: sshking-user setup-ssh --remove) >>>"
SSH_MARK_END="# <<< sshking <<<"
cmd_setup_ssh() {
  local cfg="$HOME/.ssh/config" self key tmp
  mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"; touch "$cfg"; chmod 600 "$cfg"
  tmp="$(mktemp)"
  awk '/^# >>> ssh(ca|king): automatic certificates/{skip=1} !skip{print} /^# <<< ssh(ca|king) <<<$/{skip=0}' "$cfg" > "$tmp"   # also drops the old sshca block
  if [[ "${1:-}" == --remove ]]; then mv -f "$tmp" "$cfg"; chmod 600 "$cfg"; echo "removed the sshking rule from ~/.ssh/config"; return 0; fi
  self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
  key="${2:-$(default_key)}"; [[ -n "$key" ]] || { rm -f "$tmp"; die "no SSH key; run sshking-user login first"; }
  { echo "$SSH_MARK_BEGIN"
    echo "Match exec \"$self auto %h\""
    echo "    IdentityFile \"$key\""
    echo "    CertificateFile \"$HOME/.ssh/sshca/certs/%h-cert.pub\""
    echo "Match all"
    echo "$SSH_MARK_END"
    cat "$tmp"; } > "$cfg.sshca-new"
  rm -f "$tmp"; chmod 600 "$cfg.sshca-new"
  if ! cmp -s "$cfg.sshca-new" "$cfg"; then mv -f "$cfg.sshca-new" "$cfg"; echo "ssh now renews certificates automatically (rule added to ~/.ssh/config)"
  else rm -f "$cfg.sshca-new"; fi
}

cmd_login() {
  local key="" env="$LOGIN_ENV" ttl=""
  while [[ $# -gt 0 ]]; do case "$1" in
    -k) key="$2"; shift 2;; -e) env="$2"; shift 2;; -t) ttl="$2"; shift 2;; *) die "unknown option $1";; esac; done
  [[ -n "${SSHCA_TOKEN:-}${SSHCA_TOKEN_FILE:-}" ]] && { legacy_login "$key" "$ttl"; return; }
  ensure_session
  [[ -n "$key" ]] || key="$(default_key)"
  if [[ -z "$key" ]]; then
    echo "no SSH key found; creating ~/.ssh/id_ed25519"
    mkdir -p "$HOME/.ssh"; chmod 700 "$HOME/.ssh"
    ssh-keygen -q -t ed25519 -f "$HOME/.ssh/id_ed25519" -C "$(whoami)@$(hostname -s 2>/dev/null || hostname)"
    key="$HOME/.ssh/id_ed25519"
  fi
  key="${key%.pub}"
  # several environments and none chosen: get a certificate for each; ssh then picks the right one per server
  if [[ -z "$env" ]]; then
    local list; list="$(req GET "/api/me/envs?format=text" "" "$(session_token)" | cut -f1)" || exit 1
    if [[ $(printf '%s\n' "$list" | grep -c .) -gt 1 ]]; then
      local e first="" n=0; mkdir -p "$HOME/.ssh/sshca/certs"
      for e in $list; do
        if sign_user "$key" "$e" "$ttl" "" "" "$HOME/.ssh/sshca/certs/$e-cert.pub"; then
          cp -f "$key.pub" "$HOME/.ssh/sshca/certs/$e-cert.pub.key"; user_known_hosts "$e" >/dev/null
          echo "  $e: certificate until $(ssh-keygen -L -f "$HOME/.ssh/sshca/certs/$e-cert.pub" | awk '/Valid:/{print $NF}')"
          [[ -n "$first" ]] || first="$e"; n=$((n + 1))
        else echo "  $e: no certificate (no servers you can log in to here, or refused)"; fi
      done
      [[ $n -gt 0 ]] || die "no certificates issued"
      # the plain id_ed25519-cert.pub gets the first one, so tools that don't read ~/.ssh/config still work there
      cp -f "$HOME/.ssh/sshca/certs/$first-cert.pub" "$key-cert.pub"
      echo "certificates for $n environment(s) in ~/.ssh/sshca/certs ($key-cert.pub = $first)"
      [[ -n "${SSHCA_NO_SSH_CONFIG:-}" ]] || cmd_setup_ssh "" "$key"
      rm -f "$CONF_DIR/hosts"
      if [[ -n "${SSH_AUTH_SOCK:-}" ]] && ssh-add -l >/dev/null 2>&1; then ssh-add "$key" >/dev/null 2>&1 || true; fi
      return 0
    fi
  fi
  sign_user "$key" "$env" "$ttl" || exit 1
  local used; used="$(ssh-keygen -L -f "$key-cert.pub" | awk -F'"' '/Key ID/{print $2}')"
  ssh-keygen -L -f "$key-cert.pub" | grep -E 'Valid:' | sed 's/^ */  /'
  ssh-keygen -L -f "$key-cert.pub" | awk '/Principals:/{p=1;next} /Critical/{p=0} p{gsub(/^ +/,"");print "  login name: "$0}'
  echo "wrote $key-cert.pub"
  if [[ -n "${SSH_AUTH_SOCK:-}" ]] && ssh-add -l >/dev/null 2>&1; then
    ssh-add "$key" >/dev/null 2>&1 && echo "loaded into ssh-agent" || true
  fi
  # env id is the first path segment of the key id ("<env>/<email>/<serial>")
  user_known_hosts "${used%%/*}"
  # keep a copy per environment for the automatic ssh rule
  mkdir -p "$HOME/.ssh/sshca/certs"; cp -f "$key-cert.pub" "$HOME/.ssh/sshca/certs/${used%%/*}-cert.pub"; cp -f "$key.pub" "$HOME/.ssh/sshca/certs/${used%%/*}-cert.pub.key"
  [[ -n "${SSHCA_NO_SSH_CONFIG:-}" ]] || cmd_setup_ssh "" "$key"
  rm -f "$CONF_DIR/hosts"   # access may have changed; refresh on the next ssh
}

cmd_rotate_key() {
  local env="$LOGIN_ENV" old="$HOME/.ssh/id_ed25519" yes=0
  while [[ $# -gt 0 ]]; do case "$1" in -e) env="$2"; shift 2;; -k) old="${2%.pub}"; shift 2;; -y|--yes) yes=1; shift;; *) die "unknown option $1";; esac; done
  if [[ $yes != 1 ]]; then
    echo "This will:"
    echo "  - create a new key at $HOME/.ssh/id_ed25519 and get a certificate for it"
    if [[ -f "$old.pub" ]]; then
      echo "  - revoke your current key $(ssh-keygen -lf "$old.pub" | awk '{print $2}') in every environment you use"
      echo "    (it stops working everywhere within 5 minutes; the files are kept as *.retired-<date>)"
    fi
    [[ -t 0 ]] || die "not a terminal; re-run with -y to confirm"
    printf "Continue? [y/N] "; read -r ans
    [[ "$ans" =~ ^[Yy]([Ee][Ss])?$ ]] || { echo "cancelled"; exit 1; }
  fi
  ensure_session
  local new="$HOME/.ssh/id_ed25519" stamp; stamp="$(date +%Y%m%d-%H%M%S)"
  local oldfp=""
  [[ -f "$old.pub" ]] && oldfp="$(ssh-keygen -lf "$old.pub" | awk '{print $2}')"
  local tmpk; tmpk="$(mktemp -u "$HOME/.ssh/sshca-new-XXXXXX")"
  ssh-keygen -q -t ed25519 -N "${SSHCA_KEY_PASSPHRASE:-}" -f "$tmpk" -C "$(whoami)@$(hostname -s 2>/dev/null || hostname) $stamp"
  sign_user "$tmpk" "$env" "" || exit 1
  if [[ -f "$old" ]]; then
    for f in "$old" "$old.pub" "$old-cert.pub"; do [[ -f "$f" ]] && mv -f "$f" "$f.retired-$stamp"; done
  fi
  mv -f "$tmpk" "$new"; mv -f "$tmpk.pub" "$new.pub"; mv -f "$tmpk-cert.pub" "$new-cert.pub"
  echo "new key: $new ($(ssh-keygen -lf "$new.pub" | awk '{print $2}'))"
  if [[ -n "$oldfp" ]]; then
    req POST /api/me/keys/remove "{\"fingerprint\":\"$oldfp\"}" "$(session_token)" >/dev/null \
      && echo "old key $oldfp removed from your account and revoked (files kept as *.retired-$stamp)" \
      || echo "note: old key was not registered, nothing to revoke"
  fi
  if [[ -n "${SSH_AUTH_SOCK:-}" ]] && ssh-add -l >/dev/null 2>&1; then
    [[ -f "$old.pub.retired-$stamp" ]] && ssh-add -d "$old.pub.retired-$stamp" >/dev/null 2>&1 || true
    ssh-add "$new" >/dev/null 2>&1 || true
  fi
}

cmd_envs() { ensure_session; req GET "/api/me/envs?format=text" "" "$(session_token)"; }
cmd_logout() {
  local t; t="$(session_token)"
  [[ -n "$t" && -n "$SSHCA_URL" ]] && req POST /api/auth/logout "{}" "$t" >/dev/null 2>&1 || true
  rm -f "$SESSION_FILE"; echo "signed out"
}
cmd_status() {
  local key; key="$(default_key)"; [[ "${1:-}" == "-k" ]] && key="${2%.pub}"
  [[ -n "$key" ]] || die "no key found"
  ssh-keygen -L -f "$key-cert.pub"
  if grep -qsF "$SSH_MARK_BEGIN" "$HOME/.ssh/config"; then echo "automatic renewal on ssh: on (~/.ssh/config)"; else echo "automatic renewal on ssh: off (turn on with: sshking-user setup-ssh)"; fi
  local c; for c in "$HOME"/.ssh/sshca/certs/*-cert.pub; do [[ -f "$c" && ! -L "$c" ]] || continue
    echo "  $(basename "$c" -cert.pub): $(ssh-keygen -L -f "$c" | awk '/Valid:/{print $2" "$3" "$4" "$5}')"; done
}

# ======================================================================= legacy signer-token mode (/v1 API)

legacy_token() {
  if [[ -n "${SSHCA_TOKEN:-}" ]]; then printf %s "$SSHCA_TOKEN"; else tr -d '[:space:]' < "$SSHCA_TOKEN_FILE"; fi
}
legacy_login() {
  need_url; local key="${1:-$(default_key)}" ttl="${2:-}"; key="${key%.pub}"
  : "${SSHCA_ENV:?set SSHCA_ENV}"
  local body="{\"public_key\":\"$(pubkey_line "$key.pub")\""; [[ -n "$ttl" ]] && body+=",\"ttl\":$(secs "$ttl")"; body+="}"
  req POST "/v1/envs/$SSHCA_ENV/sign/user?format=text" "$body" "$(legacy_token)" > "$key-cert.pub" || exit 1
  echo "wrote $key-cert.pub"; user_known_hosts "$SSHCA_ENV"
}

case "${1:-}" in
  login) shift; cmd_login "$@";;
  rotate-key) shift; cmd_rotate_key "$@";;
  envs) cmd_envs;;
  logout) cmd_logout;;
  status) shift; cmd_status "$@";;
  auto) shift; cmd_auto "$@";;
  setup-ssh) shift; cmd_setup_ssh "$@";;
  enroll|sync|trust|policy|host|install-timer)
    die "server commands are in sshking-server: sudo curl -fsSL ${SSHCA_URL:-https://sshking.app}/sshking-server -o /usr/local/bin/sshking-server && sudo chmod 755 /usr/local/bin/sshking-server";;
  *) sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'; exit 1;;
esac
