#!/usr/bin/env bash
# db.sh — instala e configura o banco de dados da aplicação.
#
# Cria o banco, cria um usuário dedicado (nunca root da aplicação) e grava a
# string de conexão no COFRE do servidor. A SENHA É GERADA NO PRÓPRIO SERVIDOR,
# nunca aparece em linha de comando (ps a veria) e nunca passa pela conversa
# do agente.
#
# Rodar de novo não troca a senha de um usuário que já existe e já tem a
# string no cofre: trocar a senha no banco e depois voltar o cofre deixaria a
# aplicação com uma senha que não autentica mais. Para trocar de propósito,
# use --rotate.
#
# Uso:
#   db.sh --host <ip> --user <deploy> --engine <postgres|mysql> --db <nome> [--dbuser <nome>] [--rotate] --confirmed
#   db.sh --host <ip> --user <deploy> --action status
#
# Códigos: 0 ok | 2 argumento | 10 sem confirmação | 13 conexão | 14 sem sudo

set -uo pipefail

. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/redact.sh"

HOST=""; USER_NAME="deploy"; PORT=22; KEY_PATH=""
ENGINE=""; DBNAME=""; DBUSER=""; ACTION="setup"; APP_PATH="/srv/app"; CONFIRMED=0; ROTATE=0
HEALTH_URL="http://127.0.0.1/"

while [ $# -gt 0 ]; do
  case "$1" in
    --host) HOST="$2"; shift 2 ;;
    --user) USER_NAME="$2"; shift 2 ;;
    --port) PORT="$2"; shift 2 ;;
    --key) KEY_PATH="$2"; shift 2 ;;
    --engine) ENGINE="$2"; shift 2 ;;
    --db) DBNAME="$2"; shift 2 ;;
    --dbuser) DBUSER="$2"; shift 2 ;;
    --app-path) APP_PATH="$2"; shift 2 ;;
    --action) ACTION="$2"; shift 2 ;;
    --health) HEALTH_URL="$2"; shift 2 ;;
    --rotate) ROTATE=1; shift ;;
    --confirmed) CONFIRMED=1; shift ;;
    *) echo "argumento desconhecido: $1" >&2; exit 2 ;;
  esac
done

[ -n "$HOST" ] || { echo "ERRO: --host é obrigatório" >&2; exit 2; }

# Tudo que entra no script remoto (root) é validado pela forma.
erro_arg() { echo "ERRO: $1" >&2; exit 2; }
case "$APP_PATH" in /*) ;; *) erro_arg "--app-path precisa ser caminho absoluto" ;; esac
case "$APP_PATH" in *[!A-Za-z0-9._/-]*|*..*) erro_arg "--app-path com caractere inválido" ;; esac
case "$USER_NAME" in ""|root|*[!a-z0-9_-]*) erro_arg "--user inválido: '${USER_NAME}'" ;; esac
case "$HEALTH_URL" in
  http://127.0.0.1*|http://localhost*|https://127.0.0.1*|https://localhost*) ;;
  *) erro_arg "--health aceita só endereço local" ;;
esac
case "$HEALTH_URL" in *[!A-Za-z0-9:/._~%-]*) erro_arg "--health com caractere inválido" ;; esac

SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ForwardAgent=no -o ConnectTimeout=10 -p "$PORT")
[ -n "$KEY_PATH" ] && SSH_OPTS+=(-i "${KEY_PATH/#\~/$HOME}" -o IdentitiesOnly=yes)
TARGET="${USER_NAME}@${HOST}"

# ── status: somente leitura ──────────────────────────────────────────────────
if [ "$ACTION" = "status" ]; then
  ssh "${SSH_OPTS[@]}" "$TARGET" '
    for svc in postgresql mysql mariadb; do
      if systemctl is-active "$svc" >/dev/null 2>&1; then
        echo "engine=$svc"
        echo "estado=ativo"
        case "$svc" in
          postgresql) sudo -n -u postgres psql -tAc "SELECT datname FROM pg_database WHERE datistemplate=false" 2>/dev/null | sed "s/^/banco=/" ;;
          mysql|mariadb) sudo -n mysql -N -e "SHOW DATABASES" 2>/dev/null | grep -vE "^(information_schema|performance_schema|mysql|sys)$" | sed "s/^/banco=/" ;;
        esac
        du -sh /var/lib/postgresql /var/lib/mysql 2>/dev/null | sed "s/^/tamanho=/"
        exit 0
      fi
    done
    echo "engine=nenhum"
    echo "estado=nao_instalado"
  ' 2>&1 | redact
  exit "${PIPESTATUS[0]}"
fi

# ── setup: altera o servidor ─────────────────────────────────────────────────
[ -n "$ENGINE" ] || erro_arg "--engine é obrigatório (postgres ou mysql)"
case "$ENGINE" in postgres|mysql) ;; *) erro_arg "--engine aceita postgres ou mysql" ;; esac
[ -n "$DBNAME" ] || erro_arg "--db é obrigatório"
DBUSER="${DBUSER:-${DBNAME}_app}"

case "$DBNAME$DBUSER" in
  *[!a-zA-Z0-9_]*) erro_arg "nome de banco e de usuário aceitam so letras, números e sublinhado" ;;
esac

echo "--------------------------------------------"
echo " BANCO DE DADOS em ${HOST}"
echo "  engine  : ${ENGINE}"
echo "  banco   : ${DBNAME}"
echo "  usuário : ${DBUSER}"
echo "  senha   : gerada no servidor, gravada no cofre (DATABASE_URL)"
echo "  acesso  : apenas local (127.0.0.1), não exposto na internet"
[ "$ROTATE" -eq 1 ] && echo "  rotação : SIM, a senha atual deixa de valer"
echo "--------------------------------------------"

if [ "$CONFIRMED" -ne 1 ]; then
  echo "RECUSADO: instalar banco altera o servidor e exige --confirmed." >&2
  exit 10
fi

REMOTE=$(cat <<REMOTE_SCRIPT
set -uo pipefail
export DEBIAN_FRONTEND=noninteractive
ENGINE="${ENGINE}"; DBNAME="${DBNAME}"; DBUSER="${DBUSER}"; APP_PATH="${APP_PATH}"
ROTATE="${ROTATE}"; HEALTH_URL="${HEALTH_URL}"; OPS="${USER_NAME}"
HELPER="/usr/local/sbin/sshepherd-secrets"

# Senha forte gerada aqui. Nunca sai para o agente, só para o cofre do servidor.
# Só letras e dígitos: entra em URL sem escape e em SQL sem aspa dentro.
PASS=\$(head -c 48 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 32)

# A string já está no cofre? Então o usuário existe com uma senha que funciona,
# e trocar sem pedir é quebrar a aplicação na próxima reversão do cofre.
TEM_URL=0
if [ -x "\$HELPER" ] && "\$HELPER" status 2>/dev/null | grep -q '^var=DATABASE_URL estado=definida'; then TEM_URL=1; fi

TROCAR_SENHA=1
case "\$ENGINE" in
  postgres)
    if ! systemctl is-active postgresql >/dev/null 2>&1; then
      apt-get update -qq >/dev/null 2>&1
      apt-get install -y -qq postgresql postgresql-contrib >/dev/null 2>&1
      systemctl enable --now postgresql >/dev/null 2>&1
    fi
    # Idempotente: não recria o que já existe. SQL vai pelo stdin: a senha
    # nunca fica em argumento de processo.
    if sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='\$DBUSER'" | grep -q 1; then
      if [ "\$TEM_URL" -eq 1 ] && [ "\$ROTATE" -ne 1 ]; then
        TROCAR_SENHA=0; echo "usuario=mantido senha=mantida"
      else
        printf "ALTER ROLE %s WITH PASSWORD '%s';\n" "\$DBUSER" "\$PASS" | sudo -u postgres psql -q >/dev/null
        echo "usuario=atualizado"
      fi
    else
      printf "CREATE ROLE %s LOGIN PASSWORD '%s';\n" "\$DBUSER" "\$PASS" | sudo -u postgres psql -q >/dev/null
      echo "usuario=criado"
    fi
    if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='\$DBNAME'" | grep -q 1; then
      sudo -u postgres createdb -O "\$DBUSER" "\$DBNAME"
      echo "banco=criado"
    else
      echo "banco=ja_existia"
    fi
    URL="postgresql://\$DBUSER:\$PASS@127.0.0.1:5432/\$DBNAME"
    PORTA=5432
    ;;
  mysql)
    if ! systemctl is-active mysql >/dev/null 2>&1 && ! systemctl is-active mariadb >/dev/null 2>&1; then
      apt-get update -qq >/dev/null 2>&1
      apt-get install -y -qq mariadb-server >/dev/null 2>&1
      systemctl enable --now mariadb >/dev/null 2>&1
    fi
    mysql -e "CREATE DATABASE IF NOT EXISTS \\\`\$DBNAME\\\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
    EXISTE=\$(mysql -N -e "SELECT COUNT(*) FROM mysql.user WHERE user='\$DBUSER' AND host='localhost'" 2>/dev/null || echo 0)
    if [ "\${EXISTE:-0}" -gt 0 ] && [ "\$TEM_URL" -eq 1 ] && [ "\$ROTATE" -ne 1 ]; then
      TROCAR_SENHA=0; echo "usuario=mantido senha=mantida"
    else
      {
        printf "CREATE USER IF NOT EXISTS '%s'@'localhost' IDENTIFIED BY '%s';\n" "\$DBUSER" "\$PASS"
        printf "ALTER USER '%s'@'localhost' IDENTIFIED BY '%s';\n" "\$DBUSER" "\$PASS"
      } | mysql
      echo "usuario=pronto"
    fi
    mysql -e "GRANT ALL PRIVILEGES ON \\\`\$DBNAME\\\`.* TO '\$DBUSER'@'localhost'; FLUSH PRIVILEGES"
    echo "banco=pronto"
    URL="mysql://\$DBUSER:\$PASS@127.0.0.1:3306/\$DBNAME"
    PORTA=3306
    ;;
esac

# Confirma que o banco so escuta local. Banco aberto na internet e invasão certa.
if ss -ltn 2>/dev/null | awk '{print \$4}' | grep -qE "^0\.0\.0\.0:\${PORTA}\$"; then
  echo "AVISO=o banco está escutando em todas as interfaces; deveria ser so 127.0.0.1"
else
  echo "exposicao=apenas_local"
fi

# Metadados SEM segredo, para backup e restore saberem qual banco é: eles rodam
# como o usuário de operação e não leem o cofre.
META="\$APP_PATH/shared/.sshepherd-db"
if [ -d "\$APP_PATH/shared" ]; then
  printf 'engine=%s\ndbname=%s\ndbuser=%s\n' "\$ENGINE" "\$DBNAME" "\$DBUSER" > "\$META.tmp"
  chown "\$OPS:\$OPS" "\$META.tmp" 2>/dev/null || true; chmod 644 "\$META.tmp"
  mv -f "\$META.tmp" "\$META"
  echo "metadados=\$META"
fi

# A string de conexão vai para o cofre. O valor nunca volta para o agente.
if [ "\$TROCAR_SENHA" -eq 1 ]; then
  if [ -x "\$HELPER" ]; then
    printf '%s' "\$URL" | "\$HELPER" set DATABASE_URL --stdin >/dev/null && echo "cofre=DATABASE_URL gravada" || echo "cofre=FALHOU ao gravar DATABASE_URL"
  else
    # Servidor sem cofre (compose, ou provisionado antes dele): shared/.env.
    ENVFILE="\$APP_PATH/shared/.env"
    touch "\$ENVFILE"; chmod 600 "\$ENVFILE"
    if grep -q '^DATABASE_URL=' "\$ENVFILE" 2>/dev/null; then
      sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\$URL|" "\$ENVFILE"
    else
      echo "DATABASE_URL=\$URL" >> "\$ENVFILE"
    fi
    chown "\$OPS:\$OPS" "\$ENVFILE" 2>/dev/null || true
    echo "cofre=ausente; DATABASE_URL gravada em shared/.env (legível por \$OPS)"
  fi
fi

echo "conexao_ok=\$(
  case "\$ENGINE" in
    postgres) if [ "\$TROCAR_SENHA" -eq 1 ]; then PGPASSWORD="\$PASS" psql -h 127.0.0.1 -U "\$DBUSER" -d "\$DBNAME" -tAc 'SELECT 1' 2>/dev/null || echo falhou; else echo "nao_testada (senha mantida)"; fi ;;
    mysql) if [ "\$TROCAR_SENHA" -eq 1 ]; then MYSQL_PWD="\$PASS" mysql -h 127.0.0.1 -u "\$DBUSER" -N -e 'SELECT 1' "\$DBNAME" 2>/dev/null || echo falhou; else echo "nao_testada (senha mantida)"; fi ;;
  esac
)"

# A aplicação só enxerga a string nova ao reiniciar. Se já está no ar, aplica.
if [ "\$TROCAR_SENHA" -eq 1 ] && [ -x "\$HELPER" ] && systemctl is-active --quiet sshepherd-app 2>/dev/null; then
  "\$HELPER" apply --health "\$HEALTH_URL" 2>&1 | sed 's/^/aplicacao: /'
fi
echo "banco_pronto=\$DBNAME"
REMOTE_SCRIPT
)

OUT=$(ssh "${SSH_OPTS[@]}" "$TARGET" "sudo -n bash -s" <<< "$REMOTE" 2>&1)
CODE=$?
# Rede de segurança: se algum caminho novo vazar a senha, ela não chega na tela.
printf '%s\n' "$OUT" | redact

if printf '%s' "$OUT" | grep -q "password is required"; then
  echo "" >&2
  echo "'${USER_NAME}' não tem sudo sem senha para bash, e o SSHepherd não concede isso." >&2
  echo "Instalar banco precisa de root: rode conectando como o usuário administrativo." >&2
  exit 14
fi
if [ $CODE -ne 0 ]; then
  echo "FALHOU ao configurar o banco (código ${CODE})." >&2
  exit $CODE
fi

echo ""
echo "A string de conexão está no cofre do servidor, na chave DATABASE_URL."
echo "Ela sobrevive a todos os deploys e nunca passou por esta conversa."
echo ""
echo "PRÓXIMO PASSO: configure o backup. Banco sem backup é questão de tempo."
echo "  /sshp-backup"
exit 0
