#!/usr/bin/env bash
# vault-install.sh — o bloco que instala o cofre no servidor, como root.
#
# Não é executado sozinho: é carregado (`. vault-install.sh`) por
# create-ops-user.sh, provision.sh e secrets-install.sh, e cada um deles insere
# `$(vault_install_block <ops> [<app-path>])` no script remoto que roda como root.
#
# O que o bloco faz no servidor, idempotente:
#   1. cria o usuário de serviço `sshepherd-app` (sistema, sem login, HOME própria)
#   2. cria /var/lib/sshepherd/secrets (root:root 700) e app.env (root:root 600)
#   3. instala /usr/local/sbin/sshepherd-secrets a partir de secrets-helper.sh
#   4. grava /etc/sudoers.d/sshp-<ops>-secrets, validado pelo visudo
#   5. se houver <app-path>/shared/.env legado, migra para o cofre (ver regras)
#
# Por que base64 duas vezes: os scripts que chamam isto montam o texto remoto
# num heredoc SEM aspas, que expande `$`. O helper e este bloco são shell, cheios
# de `$`. O alfabeto do base64 não tem `$`, crase nem barra invertida, então o
# texto atravessa o heredoc intacto e só vira shell de novo no servidor.

VAULT_INSTALL_SCRIPT=$(cat <<'VAULT_SCRIPT'
set -uo pipefail
umask 022
PATH=/usr/sbin:/usr/bin:/sbin:/bin; export PATH
OPS="${1:-}"; APP_PATH="${2:-}"
SVC="sshepherd-app"
DIR="/var/lib/sshepherd/secrets"
HELPER="/usr/local/sbin/sshepherd-secrets"

case "$OPS" in
  ""|root|*[!a-z0-9_-]*) echo "cofre=ERRO usuario_de_operacao_invalido"; exit 2 ;;
esac
case "$APP_PATH" in
  ""|/*) ;;
  *) echo "cofre=ERRO app_path_invalido"; exit 2 ;;
esac

# 1. usuário de serviço: a aplicação roda como ele, e ele não entra por SSH.
if ! id -u "$SVC" >/dev/null 2>&1; then
  useradd --system --create-home --home-dir "/var/lib/${SVC}" --shell /usr/sbin/nologin "$SVC" \
    && echo "usuario_servico=criado" || { echo "cofre=ERRO nao_criou_usuario_servico"; exit 4; }
else
  echo "usuario_servico=ja_existia"
fi
install -d -m 750 -o "$SVC" -g "$SVC" "/var/lib/${SVC}"

# 2. o cofre. A pasta de cima é 755 para o usuário de operação conseguir ver
# QUE o cofre existe; a de dentro é 700 para ele não conseguir ver o que há.
install -d -m 755 -o root -g root /var/lib/sshepherd
install -d -m 700 -o root -g root "$DIR"
if [ ! -f "$DIR/app.env" ]; then
  install -m 600 -o root -g root /dev/null "$DIR/app.env"
  echo "cofre=criado_vazio"
else
  chown root:root "$DIR/app.env"; chmod 600 "$DIR/app.env"
  echo "cofre=ja_existia"
fi

# 3. o helper, por temporário: nunca existe um executável de root pela metade.
TMP=$(mktemp /usr/local/sbin/.sshepherd-secrets.XXXXXX)
printf "%s" "__HELPER_B64__" | base64 -d > "$TMP" 2>/dev/null
if [ -s "$TMP" ] && head -n1 "$TMP" | grep -q "^#!/bin/bash" && bash -n "$TMP" 2>/dev/null; then
  chown root:root "$TMP"; chmod 755 "$TMP"
  mv -f "$TMP" "$HELPER"
  echo "helper=instalado"
else
  rm -f "$TMP"
  echo "cofre=ERRO helper_corrompido"
  exit 4
fi

# 4. sudo só para o helper. Sem `bash`, sem curinga de caminho: o argumento
# livre vai para um executável de root que só aceita nome de variável.
SUDOERS="/etc/sudoers.d/sshp-${OPS}-secrets"
{
  echo "# Gerado pelo SSHepherd. O usuario de operacao so eleva para o cofre."
  echo "Defaults!${HELPER} env_reset, !setenv"
  echo "${OPS} ALL=(root) NOPASSWD: ${HELPER}, ${HELPER} *"
} > "${SUDOERS}.tmp"
chmod 440 "${SUDOERS}.tmp"
if visudo -cf "${SUDOERS}.tmp" >/dev/null 2>&1; then
  mv -f "${SUDOERS}.tmp" "$SUDOERS"
  echo "sudoers_cofre=valido"
else
  rm -f "${SUDOERS}.tmp"
  echo "cofre=ERRO sudoers_invalido"
  exit 4
fi

# 5. migração do .env antigo, só quando faz sentido e nunca perdendo valor.
#    - a origem pertence ao usuário de operação: é dado não confiável. Symlink
#      é recusado, e a cópia é lida uma vez só.
#    - cofre VAZIO: importa tudo que for NOME=valor válido. Linha que o helper
#      não aceitaria (nome minúsculo, valor com barra invertida, `export`) é
#      listada por NOME e o arquivo antigo FICA, para a pessoa resolver.
#    - cofre com conteúdo: não mistura. Lista os nomes do arquivo antigo e
#      deixa o arquivo onde está. Misturar dois cofres em silêncio é como se
#      perde o valor certo.
if [ -n "$APP_PATH" ] && [ -e "$APP_PATH/shared/.env" ]; then
  OLD="$APP_PATH/shared/.env"
  if [ -L "$OLD" ]; then
    echo "migracao=recusada motivo=symlink origem=${OLD}"
  elif [ ! -f "$OLD" ]; then
    echo "migracao=recusada motivo=nao_e_arquivo origem=${OLD}"
  elif [ -s "$DIR/app.env" ]; then
    echo "migracao=nao_feita motivo=cofre_ja_tem_conteudo"
    echo "migracao_pendente=$(grep -oE "^[A-Za-z_][A-Za-z0-9_]*" "$OLD" | tr "\n" " ")"
    echo "dica=cadastre no cofre o que faltar e apague ${OLD} você mesmo"
  else
    COPIA=$(mktemp "$DIR/.migracao.XXXXXX"); chmod 600 "$COPIA"
    cat "$OLD" | tr -d "\r" > "$COPIA"
    NOVO=$(mktemp "$DIR/.app.env.XXXXXX"); chmod 600 "$NOVO"
    IGNORADAS=""; IMPORTADAS=0
    while IFS= read -r linha || [ -n "$linha" ]; do
      case "$linha" in ""|"#"*) continue ;; esac
      linha="${linha#export }"
      nome="${linha%%=*}"; valor="${linha#*=}"
      # aspas em volta do valor inteiro, sem aspa dentro: tira, é o que todo leitor de .env faz
      case "$valor" in
        \"*\") v2="${valor#\"}"; v2="${v2%\"}"; case "$v2" in *\"*) ;; *) valor="$v2" ;; esac ;;
        \'*\') v2="${valor#\'}"; v2="${v2%\'}"; case "$v2" in *\'*) ;; *) valor="$v2" ;; esac ;;
      esac
      ok=1
      case "$nome" in *[!A-Z0-9_]*|[!A-Z]*|"") ok=0 ;; esac
      [ "$nome" = "$linha" ] && ok=0
      case "$valor" in *"\\"*|" "*|*" "|\"*|\'*) ok=0 ;; esac
      [ "${#valor}" -le 8192 ] || ok=0
      if [ "$ok" -eq 1 ]; then
        grep -q "^${nome}=" "$NOVO" || { printf "%s=%s\n" "$nome" "$valor" >> "$NOVO"; IMPORTADAS=$((IMPORTADAS+1)); }
      else
        IGNORADAS="${IGNORADAS} ${nome:-linha_sem_nome}"
      fi
    done < "$COPIA"
    rm -f "$COPIA"
    chown root:root "$NOVO"; chmod 600 "$NOVO"
    mv -f "$NOVO" "$DIR/app.env"
    echo "migracao=feita importadas=${IMPORTADAS}"
    if [ -n "$IGNORADAS" ]; then
      echo "migracao_ignoradas=${IGNORADAS}"
      echo "dica=o arquivo antigo ${OLD} FICOU: cadastre essas no cofre e apague-o você mesmo"
    else
      rm -f "$OLD"
      echo "arquivo_antigo=removido ${OLD}"
    fi
  fi
  # Backups antigos de shared/ carregam o .env de antes. Apagar a origem não
  # apaga as cópias; quem decide é a pessoa, com a lista na mão.
  ANTIGOS=$(ls -1 /srv/backups/shared-*.tar.gz 2>/dev/null | tr "\n" " ")
  [ -z "$ANTIGOS" ] || echo "backups_com_env_antigo=${ANTIGOS}"
fi

echo "cofre_pronto=sim"
VAULT_SCRIPT
)

# Imprime a linha que, inserida num script remoto rodando como root, instala
# o cofre. Argumentos: usuário de operação e, opcionalmente, o caminho da app.
vault_install_block() {
  local ops="$1" app_path="${2:-}" dir helper_b64 script script_b64
  dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  helper_b64=$(base64 < "${dir}/secrets-helper.sh" | tr -d '\n')
  script="${VAULT_INSTALL_SCRIPT/__HELPER_B64__/$helper_b64}"
  script_b64=$(printf '%s' "$script" | base64 | tr -d '\n')
  printf "printf '%%s' '%s' | base64 -d | bash -s -- '%s' '%s'\n" "$script_b64" "$ops" "$app_path"
}
