#!/usr/bin/env bash
# Instalador da MinhaBia (bootstrap). Curto e auditável: leia antes de rodar.
#   1) confere root e Ubuntu 24.04+;  2) pede o CÓDIGO DE INSTALAÇÃO (oculto) ou lê MINHABIA_CODIGO;
#   3) valida o código no servidor (token de 15 min);  4) baixa o pacote;
#   5) confere SHA-256, a ASSINATURA ed25519 do manifesto com a chave pública FIXADA abaixo e cada arquivo
#      contra o manifesto assinado;  6) só então descompacta e chama `minhabia instalar --executar`.
# Qualquer falha antes do passo 6 aborta SEM alterar o sistema (tudo acontece numa pasta temporária).
#
# RETOMAR uma instalação que parou num passo (sem código novo):  bash <(curl -sSL <servidor>) --retomar
#   usa a credencial desta instalação (/etc/minhabia/instalacao.env ou canal.env, 600) para baixar o pacote mais
#   novo, confere tudo igual e chama `minhabia instalar --executar`, que pula os passos já concluídos.
set -euo pipefail
umask 077

SERVIDOR="${MINHABIA_SERVIDOR:-https://instalador.b1m.online}"
CHAVE_PUBLICA="9NocnpEVwgM1RSjPfzcuhptJIiOoG66E/oPRjv/Zyo4="   # ed25519 (base64 dos 32 bytes). Confira: <servidor>/chave.pub e docs/INSTALACAO.md

falhar() { printf '\n\033[31mERRO:\033[0m %s\nNada foi alterado neste servidor.\n' "$*" >&2; exit 1; }
passo() { printf '\n\033[1m== %s\033[0m\n' "$*"; }

RETOMAR=0
case "${1:-}" in --retomar) RETOMAR=1 ;; "") ;; *) falhar "opção desconhecida: $1 (use --retomar ou nada)" ;; esac
if [ -n "${MINHABIA_RETOMAR:-}" ]; then RETOMAR=1; fi

main() {
  # `curl ... | bash`: o script vem pela entrada padrão; perguntas passam a ler do terminal.
  if [ ! -t 0 ] && (exec </dev/tty) 2>/dev/null; then exec </dev/tty; fi

  passo "1/6 Conferindo o servidor"
  [ "$(id -u)" = 0 ] || falhar "rode como root (entre com 'sudo -i' e repita o comando)"
  [ -r /etc/os-release ] || falhar "sistema sem /etc/os-release"
  . /etc/os-release
  [ "${ID:-}" = ubuntu ] && [ "${VERSION_ID%%.*}" -ge 24 ] 2>/dev/null \
    || falhar "sistema não suportado (${PRETTY_NAME:-?}). Use Ubuntu 24.04 LTS"
  for c in curl openssl python3 tar sha256sum base64; do
    command -v "$c" >/dev/null || falhar "falta o comando '$c' (apt-get install -y curl openssl python3)"
  done
  case "$SERVIDOR" in
    https://*) ;;
    http://127.0.0.1*|http://localhost*) ;;   # só para teste local
    *) falhar "servidor precisa ser https:// ($SERVIDOR)";;
  esac

  T="$(mktemp -d)"
  trap 'rm -rf "$T"' EXIT
  local CURL=(curl -sS --proto '=https,http' --max-time 600)
  local http URL_PACOTE VERSAO SHA CONTRATO DOMINIO

  if [ "$RETOMAR" = 1 ]; then
  passo "2/6 Retomando: credencial desta instalação"
  [ -s /root/minhabia-licenca.json ] || falhar "não há instalação para retomar neste servidor (/root/minhabia-licenca.json)"
  python3 - "$T" <<'PY' || falhar "credencial da instalação ausente ou inválida (/etc/minhabia/instalacao.env ou canal.env, 600)"
import os, re, sys
t = sys.argv[1]
for nome in ("instalacao.env", "canal.env"):
    p = f"/etc/minhabia/{nome}"
    if not os.path.exists(p) or os.stat(p).st_mode & 0o077:
        continue
    env = dict(l.split("=", 1) for l in open(p).read().splitlines() if "=" in l)
    tok = env.get("MINHABIA_INSTALACAO_TOKEN", "").strip()
    if re.fullmatch(r"mbi_[A-Za-z0-9_-]{20,80}", tok):
        open(f"{t}/auth", "w").write("Authorization: Bearer " + tok + "\n")
        sys.exit(0)
sys.exit(1)
PY
  passo "3/6 Pedindo ao servidor a versão para retomar"
  http="$("${CURL[@]}" -o "$T/resp.json" -w '%{http_code}' -H @"$T/auth" "$SERVIDOR/instalacao/retomar")" \
    || falhar "não consegui falar com $SERVIDOR"
  if [ "$http" != 200 ]; then
    falhar "servidor recusou ($http): $(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("erro","?"))' "$T/resp.json" 2>/dev/null || echo '?')"
  fi
  python3 - "$T" <<'PY' || falhar "resposta do servidor em formato inesperado"
import json, re, sys, os
t = sys.argv[1]
r = json.load(open(f"{t}/resp.json"))
lic = r["licenca"]
salva = json.load(open("/root/minhabia-licenca.json"))
ok = re.fullmatch(r"\d+\.\d+\.\d+", r["versao"]) and re.fullmatch(r"[0-9a-f]{64}", r["sha256"]) \
     and all(lic[k] == salva[k] for k in ("contrato", "equipes", "dominio", "instalacao_id"))
sup = lic.get("suporte_pub")
if sup:
    ok = ok and lic["contrato"] == "recorrente" and re.fullmatch(r"(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)) [A-Za-z0-9+/]{40,}={0,3}( [A-Za-z0-9@._-]{1,80})?", sup)
if not ok:
    sys.exit(1)
if sup:
    open(f"{t}/suporte.pub", "w").write(sup + "\n")
open(f"{t}/vars", "w").write(f"{r['versao']} {r['sha256']} {lic['contrato']} {lic['dominio']}\n")
os.remove(f"{t}/resp.json")
PY
  read -r VERSAO SHA CONTRATO DOMINIO < "$T/vars"
  echo "Retomada autorizada. Versão $VERSAO · contrato $CONTRATO · domínio $DOMINIO"
  URL_PACOTE="$SERVIDOR/instalacao/pacote/$VERSAO"
  else
  passo "2/6 Código de instalação"
  local codigo="${MINHABIA_CODIGO:-}"
  unset MINHABIA_CODIGO
  if [ -z "$codigo" ]; then
    read -r -s -p "Digite o código de instalação (não aparece na tela): " codigo || true
    echo
  fi
  codigo="$(printf '%s' "$codigo" | tr -d ' \r\n')"
  [[ "$codigo" =~ ^[A-Za-z0-9-]{18,30}$ ]] || falhar "código em formato inválido (ex.: MB-XXXX-XXXX-XXXX-XXXX)"


  passo "3/6 Validando o código no servidor"
  local maquina
  maquina="$( (cat /etc/hostname 2>/dev/null || true) | tr -cd 'A-Za-z0-9._-' | head -c 60)"
  MB_CODIGO="$codigo" MB_MAQUINA="$maquina" python3 -c 'import json,os;print(json.dumps({"codigo":os.environ["MB_CODIGO"],"maquina":os.environ["MB_MAQUINA"]}))' > "$T/pedido.json"
  codigo=""
  http="$("${CURL[@]}" -o "$T/resp.json" -w '%{http_code}' -H 'Content-Type: application/json' --data-binary @"$T/pedido.json" "$SERVIDOR/validar")" \
    || falhar "não consegui falar com $SERVIDOR"
  rm -f "$T/pedido.json"
  if [ "$http" != 200 ]; then
    falhar "servidor recusou ($http): $(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("erro","?"))' "$T/resp.json" 2>/dev/null || echo '?')"
  fi
  # Lê a resposta com validação estrita de cada campo (nada da resposta vira comando).
  python3 - "$T" <<'PY' || falhar "resposta do servidor em formato inesperado"
import json, re, sys, os
t = sys.argv[1]
r = json.load(open(f"{t}/resp.json"))
ok = (re.fullmatch(r"mbt_[A-Za-z0-9_-]{20,80}", r["token"]) and re.fullmatch(r"\d+\.\d+\.\d+", r["versao"])
      and re.fullmatch(r"[0-9a-f]{64}", r["sha256"]))
lic = r["licenca"]
ok = ok and lic["contrato"] in ("recorrente", "venda-unica") and re.fullmatch(r"inst_[0-9a-f]{16}", lic["instalacao_id"]) \
     and re.fullmatch(r"(?=.{4,253}$)([a-z0-9-]+\.)+[a-z]{2,}", lic["dominio"]) \
     and all(e in ("marketing", "estudios", "comercial", "financeiro") for e in lic["equipes"])
sup = lic.get("suporte_pub")
if sup:
    ok = ok and lic["contrato"] == "recorrente" and re.fullmatch(r"(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)) [A-Za-z0-9+/]{40,}={0,3}( [A-Za-z0-9@._-]{1,80})?", sup)
ins = r.get("instalacao")
if ins:
    ok = ok and re.fullmatch(r"mbi_[A-Za-z0-9_-]{20,80}", ins["token"]) and re.fullmatch(r"https?://[A-Za-z0-9.:-]+", ins["url"])
can = r.get("canal")
if can:
    ok = ok and re.fullmatch(r"mbi_[A-Za-z0-9_-]{20,80}", can["token"]) and re.fullmatch(r"https?://[A-Za-z0-9.:-]+", can["url"])
if not ok:
    sys.exit(1)
open(f"{t}/auth", "w").write("Authorization: Bearer " + r["token"] + "\n")
open(f"{t}/vars", "w").write(f"{r['versao']} {r['sha256']} {lic['contrato']} {lic['dominio']}\n")
json.dump({"contrato": lic["contrato"], "equipes": lic["equipes"], "dominio": lic["dominio"],
           "instalacao_id": lic["instalacao_id"]}, open(f"{t}/licenca.json", "w"), ensure_ascii=False, indent=1)
if sup:
    open(f"{t}/suporte.pub", "w").write(sup + "\n")
if ins:
    open(f"{t}/instalacao.env", "w").write(f"MINHABIA_SERVIDOR_URL={ins['url']}\nMINHABIA_INSTALACAO_ID={lic['instalacao_id']}\n"
                                           f"MINHABIA_INSTALACAO_TOKEN={ins['token']}\n")
if can:
    open(f"{t}/canal.env", "w").write(f"MINHABIA_CANAL_URL={can['url']}\nMINHABIA_INSTALACAO_ID={lic['instalacao_id']}\n"
                                      f"MINHABIA_INSTALACAO_TOKEN={can['token']}\n")
os.remove(f"{t}/resp.json")
PY
  read -r VERSAO SHA CONTRATO DOMINIO < "$T/vars"
  echo "Código aceito. Versão $VERSAO · contrato $CONTRATO · domínio $DOMINIO"
  URL_PACOTE="$SERVIDOR/pacote/$VERSAO"
  fi

  passo "4/6 Baixando o pacote $VERSAO"
  http="$("${CURL[@]}" -o "$T/pacote.tar" -w '%{http_code}' -H @"$T/auth" "$URL_PACOTE")" || falhar "download falhou"
  rm -f "$T/auth"
  [ "$http" = 200 ] || falhar "download recusado ($http)"

  passo "5/6 Conferindo integridade e assinatura"
  echo "$SHA  $T/pacote.tar" | sha256sum -c --quiet - >/dev/null 2>&1 || falhar "SHA-256 do pacote não confere"
  mkdir "$T/m"
  tar -xf "$T/pacote.tar" -C "$T/m" manifesto.json manifesto.sig 2>/dev/null || falhar "pacote sem manifesto"
  { echo "-----BEGIN PUBLIC KEY-----"
    { printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00'; printf '%s' "$CHAVE_PUBLICA" | base64 -d; } | base64 -w0
    echo; echo "-----END PUBLIC KEY-----"; } > "$T/pub.pem"
  openssl pkeyutl -verify -pubin -inkey "$T/pub.pem" -rawin -in "$T/m/manifesto.json" -sigfile "$T/m/manifesto.sig" >/dev/null 2>&1 \
    || falhar "ASSINATURA INVÁLIDA: o pacote não veio do fornecedor ou foi alterado"
  echo "assinatura ed25519 OK"
  python3 - "$T" "$VERSAO" <<'PY' || falhar "conteúdo do pacote não confere com o manifesto assinado"
import hashlib, json, sys, tarfile
t, versao = sys.argv[1], sys.argv[2]
man = json.load(open(f"{t}/m/manifesto.json"))
assert man["versao"] == versao, "versão do manifesto diferente"
lista = {"conteudo/" + a["caminho"]: a["sha256"] for a in man["arquivos"]}
with tarfile.open(f"{t}/pacote.tar", "r:") as tar:
    ms = tar.getmembers()
    for m in ms:
        n = m.name
        assert not n.startswith("/") and ".." not in n.split("/") and (m.isfile() or m.isdir()), f"entrada insegura {n}"
        assert n in ("manifesto.json", "manifesto.sig", "conteudo") or n.startswith("conteudo/"), f"entrada fora do formato {n}"
    reais = {m.name for m in ms if m.isfile() and m.name.startswith("conteudo/")}
    assert reais == set(lista), "arquivos a mais ou a menos"
    for n, h in lista.items():
        assert hashlib.sha256(tar.extractfile(n).read()).hexdigest() == h, f"hash diferente: {n}"
    tar.extractall(f"{t}/x", members=[m for m in ms if m.name.startswith("conteudo")], filter="data")
print(f"{len(lista)} arquivos conferidos")
PY

  passo "6/6 Iniciando a instalação"
  local DEST="/root/minhabia-$VERSAO"
  [ -e "$DEST" ] && mv "$DEST" "$DEST.anterior-$(date +%Y%m%d%H%M%S)"
  mv "$T/x/conteudo" "$DEST"
  chmod -R u=rwX,go=rX "$DEST"   # código não é segredo; o umask 077 deixaria as pastas fechadas para o serviço
  # chave pública de suporte do fornecedor (contrato recorrente): o passo `suporte` cria o acesso com ela
  if [ -f "$T/suporte.pub" ] && [ ! -s /etc/minhabia/suporte.pub ]; then
    install -d -m 750 /etc/minhabia; install -m 644 "$T/suporte.pub" /etc/minhabia/suporte.pub
  fi
  if [ "$RETOMAR" = 1 ]; then
    echo "Pacote em $DEST. Retomando: o instalador pula os passos já concluídos e não pede código nem respostas."
    cd "$DEST"
    bin/minhabia instalar --executar
    return
  fi
  install -m 600 "$T/licenca.json" /root/minhabia-licenca.json
  install -d -m 750 /etc/minhabia
  for f in instalacao.env canal.env; do
    if [ -f "$T/$f" ]; then install -m 600 "$T/$f" "/etc/minhabia/$f"; fi
  done
  echo "Pacote em $DEST. Equipes, contrato e domínio vêm do seu código; o instalador pergunta o resto."
  cd "$DEST"
  bin/minhabia instalar --licenca /root/minhabia-licenca.json --interativo --executar
}

main "$@"; exit $?
