#!/bin/sh
#
# A build da tela nova passa no Linux do jeito que o `publicar` a faz -- e o
# vhost gerado do modelo serve o que ela produz?
#
#     sh scripts/ensaiar-build-do-publicar.sh               # o HEAD
#     sh scripts/ensaiar-build-do-publicar.sh <commit>      # o commit que vai subir
#     sh scripts/ensaiar-build-do-publicar.sh <diretorio>   # uma COPIA da arvore
#
# Sai com 0 quando a build passa e o nginx serve o /novo/ como o modelo manda;
# 1 quando algo disso falha, e diz o que. No BVIP, com o freio armado, a build
# reprovada, o `nginx -t` reprovado, o /novo/ sem a tela nova (sem a senha: o
# index sem o JS de /novo/assets/, ou o JS sem 200), o noindex ou o HSTS
# faltando no /novo/, ou o 401 faltando com a senha tiram o site do ar; o resto
# (o tipo, o cache, o 301 do /novo, o 404 do asset que falta, a rota interna, a
# location que sumiu do modelo) o `conferir` do deploy nao mede, e a tela nova
# fica no ar servida errada. 2 quando nao consegue medir (sem docker, imagem
# fora do cache, o npm sem rede, o scripts/deploy-teste.sh ou o modelo do vhost
# mudaram de forma e o ensaio nao sabe mais copia-los) -- e so se nada falhou
# antes: um defeito ja achado sai com 1, meca-se o resto ou nao. O diretorio e
# como se falsifica sem commit: uma COPIA da arvore (git archive HEAD | tar -x
# -C <dir>) com UMA mudanca, fora de repositorio git -- ou num clone cujo
# frontend/ nao tenha nada fora do git (nao versionado ou ignorado): o tar leva
# o frontend/ do diretorio como esta, e a arvore de trabalho tem o que o clone
# do BVIP nao tera.
#
# QUANDO RODAR: antes de todo `publicar` e `atualizar` em que o frontend/ do
# commit que vai subir difere do da build instalada no BVIP (o commit dela esta
# em <FRONT_RAIZ>/novo.commit, e o deploy reconstroi contra ele, e nao contra o
# commit anterior: um `atualizar` que salta varios commits reconstroi mesmo que
# o ultimo nao toque o frontend/), antes do primeiro `publicar`, e antes de todo
# `voltar`, que reconstroi sempre (FORCAR_FRONT=sim) -- com o commit alvo como
# argumento. Numa maquina com o repositorio e o docker (docs/ambiente-teste.md,
# "Subir").
#
# FORA DO CORREDOR E DO CI, DE PROPOSITO: pede o docker e a rede -- o `npm ci`
# baixa os pacotes do frontend/package-lock.json, como no servidor. Nao entra
# no scripts/conferir-tudo.sh nem no .github/workflows/ci.yml.
#
# ---------------------------------------------------------------------------
# POR QUE ISTO EXISTE (09/10/2026, pergunta 59 do lote)
#
# O `publicar` constroi a tela nova num conteiner Node com o freio ja armado
# (AO_FALHAR=tirar_do_ar): se a build falhar, o site sai do ar. Ate 09/10 ela
# so tinha rodado no Windows. O CI nao faz build (e nao inicia desde 13/09), e
# o d391e43 (25/09) dava "a build da tela nova como o publicar a faz, o vhost
# gerado num nginx 1.24 de verdade" como em andamento, sem registro do
# resultado. No Windows, `./Index.css` acha `index.css`; no disco do servidor,
# nao -- e o TypeScript nao confere o import de CSS: so a build no Linux o ve.
#
# MEDIDO EM 09/10/2026: verde no 2d3c6a0, no nginx:1.24 e no nginx:1.30
# (IMAGEM_NGINX=nginx:1.30, o outro ramo do http2), e como root
# (USUARIO_DO_DEPLOY=0:0). Os mutantes das duas rodadas, e a guarda do volume
# arrancada -- a pasta do Windows deu verde ao import com a caixa trocada --,
# estao no docs/roadmap.md (BUILD-LINUX-DO-PUBLICAR).
#
# ---------------------------------------------------------------------------
# COMO
#
#   1. LE do scripts/deploy-teste.sh DA ORIGEM o que copia: a IMAGEM_NODE, o
#      comando do `sh -c` do construir_front, os `-e` dele, o padrao que ele
#      confere no index.html, o bloco do `sed` do instalar_vhost, os dois
#      valores do `auth`, a FRONT_RAIZ, o HTPASSWD e o DOMINIO padrao. O
#      construir_front, o trocar_front e o instalar_front vao INTEIROS contra um
#      molde (abaixo), em que so o comando, os `-e`, o padrao e o texto
#      impresso variam: uma flag nova no docker run, um comando a mais, uma
#      troca no trocar_front dao 2 -- e o ensaio nao roda uma copia velha. O
#      que o publicar faz ENTRE a build e a troca (o app, as conferencias) nao
#      entra no molde.
#   2. EXPORTA o frontend/ da origem (git archive; nunca a arvore de trabalho,
#      que tem o node_modules do Windows) para um VOLUME do docker, e nao para
#      uma pasta do Windows: a pasta montada do Windows nao distingue
#      maiusculas, e o ext4 do servidor distingue. O ensaio confere que o
#      volume distingue. So o frontend/: e so ele que o deploy monta em /app.
#   3. A BUILD, como o construir_front: a mesma imagem, a mesma expressao do
#      usuario (`id -u`:`id -g` de quem roda: aqui, o uid do Git Bash; no
#      BVIP, onde o deploy roda como root -- o dono prefere, 02/10 --, 0:0;
#      USUARIO_DO_DEPLOY=0:0 ensaia esse), as mesmas variaveis, o mesmo /app, o
#      mesmo -w e o mesmo comando. Muda so o que esta montado em /app: o
#      volume, no lugar de "$RAIZ/frontend". Mede quanto levou.
#   4. O DIST: o padrao do deploy no index.html; todo `src`/`href` do index
#      dentro de /novo/ e existindo no dist; todo `assets/...` citado nos
#      arquivos do dist existindo; nenhum caminho absoluto do Windows nem o
#      `/src/` do desenvolvimento. A EXISTENCIA e conferida na lista do tar
#      tirado do volume, com a caixa exata, e nao na copia do rascunho: o
#      rascunho e uma pasta do Windows, e `[ -f assets/Fundo.webp ]` acharia o
#      fundo.webp.
#   5. O VHOST, gerado do docker/nginx/teste-host.conf.modelo da origem pelo
#      MESMO `sed` do deploy (o bloco lido no passo 1, e nao uma copia), nas
#      duas escolhas da senha HTTP, com o http2 decidido pela versao do nginx
#      do conteiner, como o deploy decide pela do host. O `nginx -t` de cada um
#      roda num conteiner da IMAGEM_NGINX (padrao nginx:1.24, a do Ubuntu
#      24.04), com um certificado autoassinado no caminho do Let's Encrypt e o
#      dist em $FRONT_RAIZ/novo, copiado como o trocar_front copia (cp -a,
#      chmod -R a+rX).
#   6. AS SONDAS, por curl, no vhost sem senha: /novo/ da o index do dist (200,
#      text/html, o Cache-Control que o modelo da a location, noindex, HSTS);
#      cada arquivo de /novo/ que o index cita da 200 com o tipo certo e o
#      mesmo conteudo do dist; o asset que nao existe da 404, e nao o index;
#      /novo da 301 para /novo/; uma rota interna (/novo/agenda) da o que o
#      modelo manda -- o index se a location do /novo/ tiver o fallback, 404
#      se nao; e, sem fallback, a tela nao pode por rota no caminho (o ensaio
#      procura pushState e roteador no src, e o pushState/replaceState no JS do
#      dist, onde cai o codigo de um roteador de biblioteca). No vhost com
#      senha: 401 sem ela, e com ela o index, o asset e o 404.
#   7. APAGA os conteineres, os volumes e o rascunho em toda saida que o shell
#      ve (o fim, um erro, o Ctrl-C, o TERM). Um SIGKILL -- ou a ferramenta que
#      mata a arvore de processos -- nao passa pelo trap: os conteineres (todos
#      com o rotulo $ROTULO) e os volumes ficam, e a rodada seguinte os avisa,
#      com o comando que os apaga.
#
# O QUE ELE NAO AFIRMA: que a node:22-bookworm-slim que o BVIP baixar e a do
# cache daqui (a etiqueta anda; o ensaio imprime o id da imagem e as versoes
# do node e do npm que mediu); que o registro do npm responde do BVIP; que o
# `nginx -t` do host passa (ele le os vhosts dos outros sites; este so le o
# deste); quanto a build leva la; o certificado, o certbot, o proxy para o ERP
# em / e as rotas negadas, que o `conferir` do deploy mede no servidor. Nem que
# a build passa como root sem USUARIO_DO_DEPLOY=0:0 (o padrao e o uid de quem
# roda o ensaio). Nem as permissoes dos pais da FRONT_RAIZ no BVIP: aqui o
# volume e montado direto nela, e o docker cria os pais como root 0755; la, se
# o www-data nao atravessa um deles, o /novo/ da 403 -- o `conferir` do deploy
# o pega, mas com o site ja fora do ar.

set -u

cd "$(dirname "$0")/.." || exit 2

# UM DEFEITO JA ACHADO VENCE o que nao se mediu depois dele: a saida e 1, e nao
# o 2 de "nao consegui medir" -- o 2 escondia o defeito impresso acima.
medir_nao() {
    if [ "${FALHAS:-0}" -gt 0 ]; then
        echo "  e depois NAO CONSEGUI MEDIR o resto: $*"
        echo
        echo "VERMELHO: $FALHAS defeito(s) acima; o que nao foi medido nao os desfaz."
        exit 1
    fi
    echo "NAO CONSEGUI MEDIR: $*" >&2
    exit 2
}
FALHAS=0
defeito() { echo "  DEFEITO: $*"; FALHAS=$((FALHAS + 1)); }
certo() { echo "  ok: $*"; }

# O DOCKER DO GIT BASH (Windows) recebe "/app" e "HOME=/tmp" convertidos em
# caminho do Windows sem isto. So nele: o curl e o git de la precisam da
# conversao para escrever no rascunho.
dk() { MSYS_NO_PATHCONV=1 docker "$@"; }
# TODO CONTEINER DO ENSAIO leva o rotulo: um SIGKILL no roteiro nao para o
# conteiner de um `docker run` em primeiro plano, e a rodada seguinte so avisa o
# que acha pelo rotulo.
dk_run() { dk run --label "$ROTULO" "$@"; }

IMAGEM_NGINX="${IMAGEM_NGINX:-nginx:1.24}"

ORIGEM="${1:-HEAD}"
DIRETORIO=""
if [ -d "$ORIGEM" ]; then
    DIRETORIO=$ORIGEM
    QUAL="a copia em $DIRETORIO"
    # UMA COPIA, E NAO A ARVORE DE TRABALHO: o tar leva o frontend/ do
    # diretorio como esta, e numa arvore de trabalho isso inclui o que o git
    # nao tem (os nao versionados, os ignorados, um .env.local que o vite le
    # na build) e o clone do BVIP nao tera. Dentro de um repositorio git, o
    # frontend/ do diretorio nao pode ter nada fora do git (o que esta mudado
    # e versionado vale: e assim que se falsifica); fora de repositorio, vale a
    # copia como esta.
    if git -C "$DIRETORIO" rev-parse --is-inside-work-tree > /dev/null 2>&1; then
        sobra=$(git -C "$DIRETORIO" status --porcelain --ignored -- frontend 2>/dev/null | grep -E '^(\?\?|!!) ' | head -n 4 | sed 's/^...//' | tr '\n' ' ')
        [ -z "$sobra" ] || medir_nao "$DIRETORIO e uma arvore de trabalho com o que o git nao tem no frontend/ (${sobra% }): a build levaria isso, e o clone do BVIP nao o tem. Use uma copia (git archive HEAD | tar -x -C <dir>) fora do repositorio, ou um commit"
    fi
else
    git rev-parse --verify -q "$ORIGEM^{commit}" > /dev/null 2>&1 || medir_nao "$ORIGEM nao e commit nem diretorio"
    QUAL="o commit $(git rev-parse --short "$ORIGEM^{commit}")"
fi

# Um arquivo da origem, e nao da arvore de trabalho.
ler() {
    if [ -n "$DIRETORIO" ]; then cat "$DIRETORIO/$1"; else git show "$ORIGEM:$1"; fi
}

for c in docker git awk sed grep tar curl od cmp diff find mktemp; do
    command -v "$c" > /dev/null 2>&1 || medir_nao "sem $c"
done
dk info > /dev/null 2>&1 || medir_nao "o docker nao responde (o daemon esta no ar?)"

BASE=$(mktemp -d 2>/dev/null) || medir_nao "mktemp -d falhou"
NOME="franquiabv-ensaio-build-$$"
ROTULO="franquiabv.ensaio=build-do-publicar"
VOL_APP="$NOME-app"
VOL_FRONT="$NOME-front"
VOL_CONF="$NOME-conf"
NGINX="$NOME-nginx"

limpar() {
    for c in "$NOME-exporta" "$NOME-build" "$NOME-dist" "$NOME-troca" "$NOME-conf" "$NOME-senha" "$NOME-t" "$NGINX"; do
        dk rm -f "$c" > /dev/null 2>&1
    done
    dk volume rm -f "$VOL_APP" "$VOL_FRONT" "$VOL_CONF" > /dev/null 2>&1
    rm -rf "$BASE"
}
trap limpar EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

# AS SOBRAS DE UMA RODADA MORTA sem o trap (SIGKILL): os conteineres e os
# volumes levam o rotulo. Os volumes contam: depois de um `docker rm -f` no
# nginx, eles ficavam sem aviso nenhum.
n_sobras=$(dk ps -aq --filter "label=$ROTULO" 2>/dev/null | grep -c .)
n_sobras_vol=$(dk volume ls -q --filter "label=$ROTULO" 2>/dev/null | grep -c .)
if [ "$n_sobras" -gt 0 ] || [ "$n_sobras_vol" -gt 0 ]; then
    echo "AVISO: ha sobras de um ensaio que morreu sem a limpeza: $n_sobras conteiner(es) e $n_sobras_vol volume(s) com o rotulo $ROTULO; este nao as toca. Para apagar, quando nenhum ensaio estiver rodando:"
    echo "    docker rm -f \$(docker ps -aq --filter label=$ROTULO); docker volume rm \$(docker volume ls -q --filter label=$ROTULO)"
fi

echo "== ensaio da build do publicar: $QUAL"

# ---------------------------------------------------------------------------
# 1. O QUE O DEPLOY FAZ, lido dele.
mkdir -p "$BASE/arvore/docker/nginx" "$BASE/arvore/scripts" "$BASE/fonte" "$BASE/dist" "$BASE/conf" \
    || medir_nao "nao criei o rascunho"
DEPLOY="$BASE/arvore/scripts/deploy-teste.sh"
MODELO="$BASE/arvore/docker/nginx/teste-host.conf.modelo"
ler scripts/deploy-teste.sh > "$DEPLOY" 2>/dev/null || medir_nao "nao li o scripts/deploy-teste.sh de $ORIGEM"
ler docker/nginx/teste-host.conf.modelo > "$MODELO" 2>/dev/null || medir_nao "nao li o modelo do vhost de $ORIGEM"
ler .env.teste.example > "$BASE/env.exemplo" 2>/dev/null || medir_nao "nao li o .env.teste.example de $ORIGEM"

# O corpo de uma funcao do deploy, da linha `nome() {` ate o primeiro `}` na
# coluna 0.
funcao() {
    awk -v f="$1" '$0 ~ ("^" f "[(][)] [{]") { d = 1 } d { print } d && /^[}]/ { exit }' "$DEPLOY"
}

# Uma atribuicao do deploy, rodada aqui: so texto e $VARIAVEL. Um $( ou uma
# crase mudam a forma, e o ensaio para em vez de rodar o que nao leu.
atribuir() {
    case "$1" in *'`'*|*'$('*) medir_nao "atribuicao fora da forma no deploy: $1" ;; esac
    eval "$1"
}

# A IMAGEM_NODE: uma atribuicao so no deploy (fora dos comentarios), e literal.
# Uma segunda (`IMAGEM_NODE="${IMAGEM_NODE_DO_ENV:-...}"` mais abaixo) trocaria
# a imagem da build sem o ensaio ver.
[ "$(grep -vE '^[[:space:]]*#' "$DEPLOY" | grep -cE '(^|[^A-Za-z0-9_])IMAGEM_NODE=')" = 1 ] \
    && [ "$(grep -c '^IMAGEM_NODE="[^"$`]*"$' "$DEPLOY")" = 1 ] \
    || medir_nao "nao achei uma IMAGEM_NODE so, literal, no deploy"
IMAGEM_NODE=$(sed -n 's/^IMAGEM_NODE="\([^"$`]*\)"$/\1/p' "$DEPLOY")

DOMINIO=$(sed -n 's/^DOMINIO="\${DOMINIO:-\${DOMINIO_DO_ENV:-\([a-z0-9.-]*\)}}"$/\1/p' "$DEPLOY")
[ -n "$DOMINIO" ] || medir_nao "nao achei o DOMINIO padrao no deploy"
linha=$(grep '^FRONT_RAIZ="\${FRONT_RAIZ:-' "$DEPLOY" | sed 's/^FRONT_RAIZ="\${FRONT_RAIZ:-\(.*\)}"$/FRONT_RAIZ="\1"/')
[ -n "$linha" ] || medir_nao "nao achei a FRONT_RAIZ padrao no deploy"
atribuir "$linha"
linha=$(grep '^HTPASSWD="' "$DEPLOY")
[ -n "$linha" ] || medir_nao "nao achei o HTPASSWD no deploy"
atribuir "$linha"
grep -qF 'PORTA_APP="${APP_PORT##*:}"' "$DEPLOY" || medir_nao "a PORTA_APP do deploy mudou de forma"
APP_PORT=$(sed -n 's/^APP_PORT=//p' "$BASE/env.exemplo" | tail -n 1 | tr -d '\r')
PORTA_APP="${APP_PORT##*:}"
case "$PORTA_APP" in ''|*[!0-9]*) medir_nao "nao achei a APP_PORT no .env.teste.example" ;; esac

# O CONSTRUIR_FRONT, O TROCAR_FRONT E O INSTALAR_FRONT, INTEIROS, contra um
# molde. Antes o ensaio conferia dois pedacos do docker run, e um
# `--network none --memory=256m` acrescentado nele (ou um comando a mais na
# funcao, ou uma troca no trocar_front) passava calado: o ensaio rodava a build
# sem eles e dava 0, e no BVIP o npm ci falharia com o site saindo do ar.
#
# juntar: o corpo da funcao sem comentarios nem linhas vazias, com as
# continuacoes (`\` no fim) juntas numa linha so.
juntar() {
    funcao "$1" | awk '
        /^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
        junta != "" { sub(/^[[:space:]]+/, ""); $0 = junta " " $0; junta = "" }
        /[\\]$/ { sub(/[[:space:]]*[\\]$/, ""); junta = $0; next }
        { print }
        END { if (junta != "") print junta }'
}
# marcar: as partes que variam de proposito viram marcas -- o texto do `passo`
# e do `falhar` (so impressao, com no maximo $VARIAVEL: um $(, um ${ ou uma
# crase ficam e reprovam), os `-e NOME=valor` e o `sh -c '...'` do docker run,
# e o padrao do grep do index. O resto tem de bater com o molde, caractere por
# caractere.
marcar() {
    sed -E \
        -e 's/^(    passo )"([^"`$\\]|[$][A-Za-z_][A-Za-z0-9_]*)*"$/\1<TEXTO>/' \
        -e 's/ [|][|] falhar "([^"`$\\]|[$][A-Za-z_][A-Za-z0-9_]*)*"$/ || falhar <TEXTO>/' \
        -e "s/^(    docker run --rm -u \"[\$][(]id -u[)]:[\$][(]id -g[)]\" )(-e [A-Za-z_][A-Za-z0-9_]*=[A-Za-z0-9_.\/:-]+ )+(-v \"[\$]RAIZ\/frontend:\/app\" -w \/app \"[\$]IMAGEM_NODE\" sh -c )'[^']*'\$/\1<VARIAVEIS> \3<COMANDO>/" \
        -e "s/^(    grep -q )'[^']*'( frontend\/dist\/index[.]html [|][|] falhar <TEXTO>)\$/\1<PADRAO>\2/"
}
conferir_molde() {  # $1 a funcao, $2 o molde
    [ "$(grep -c "^$1() {" "$DEPLOY")" = 1 ] || medir_nao "o deploy nao tem um $1 so"
    juntar "$1" | marcar > "$BASE/molde-lido"
    printf '%s\n' "$2" > "$BASE/molde-esperado"
    if ! cmp -s "$BASE/molde-esperado" "$BASE/molde-lido"; then
        diff "$BASE/molde-esperado" "$BASE/molde-lido" | sed 's/^/    | /'
        medir_nao "o $1 do deploy saiu do molde (acima: < o molde, > o deploy): o ensaio rodaria outra coisa que nao a do deploy -- ajuste este ensaio antes de confiar nele"
    fi
}
conferir_molde construir_front 'construir_front() {
    passo <TEXTO>
    docker run --rm -u "$(id -u):$(id -g)" <VARIAVEIS> -v "$RAIZ/frontend:/app" -w /app "$IMAGEM_NODE" sh -c <COMANDO>
    grep -q <PADRAO> frontend/dist/index.html || falhar <TEXTO>
}'
# O trocar_front, que o passo 5 copia (cp -a do dist, chmod -R a+rX).
conferir_molde trocar_front 'trocar_front() {
    passo <TEXTO>
    sudo mkdir -p "$FRONT_RAIZ"
    sudo rm -rf "$FRONT_RAIZ/novo.entrando" "$FRONT_RAIZ/novo.anterior"
    sudo cp -a frontend/dist "$FRONT_RAIZ/novo.entrando"
    sudo chmod -R a+rX "$FRONT_RAIZ"
    if sudo test -d "$FRONT_RAIZ/novo"; then
        sudo mv "$FRONT_RAIZ/novo" "$FRONT_RAIZ/novo.anterior"
    fi
    sudo mv "$FRONT_RAIZ/novo.entrando" "$FRONT_RAIZ/novo"
    git rev-parse HEAD | sudo tee "$FRONT_RAIZ/novo.commit" >/dev/null
}'
conferir_molde instalar_front 'instalar_front() {
    construir_front
    trocar_front
}'
# Dentro do molde, o que varia, lido da linha juntada.
CONSTRUIR=$(juntar construir_front)
DOCKER_RUN=$(printf '%s\n' "$CONSTRUIR" | grep '^    docker run ')
VARIAVEIS=$(printf '%s\n' "$DOCKER_RUN" | sed -E 's/^.*"[$][(]id -u[)]:[$][(]id -g[)]" ((-e [^ ]+ )+)-v .*$/\1/')
COMANDO=$(printf '%s\n' "$DOCKER_RUN" | sed -E "s/^.* sh -c '([^']*)'\$/\1/")
PADRAO=$(printf '%s\n' "$CONSTRUIR" | sed -n -E "s/^    grep -q '([^']*)' frontend\/dist\/index[.]html .*/\1/p")
[ -n "$COMANDO" ] && [ -n "$VARIAVEIS" ] && [ -n "$PADRAO" ] || medir_nao "nao li o comando, os -e ou o padrao do construir_front"

INSTALAR=$(funcao instalar_vhost)
[ -n "$INSTALAR" ] || medir_nao "nao achei o instalar_vhost no deploy"
BLOCO_SED=$(printf '%s\n' "$INSTALAR" | awk '/^[[:space:]]*sed -e "s#__DOMINIO__#/ { d = 1 } d { print } d && /teste-host[.]conf[.]modelo > "[$]tmp"$/ { exit }')
printf '%s\n' "$BLOCO_SED" | awk '
    NR == 1 { if ($0 !~ /^[[:space:]]*sed -e "[^"`]*" \\$/) ruim = 1; next }
    /^[[:space:]]*-e "[^"`]*" \\$/ { next }
    /^[[:space:]]*docker\/nginx\/teste-host[.]conf[.]modelo > "[$]tmp"$/ { fim = 1; next }
    { ruim = 1 }
    END { exit (ruim || !fim) }' || medir_nao "o sed do instalar_vhost mudou de forma; ajuste este ensaio"
case "$BLOCO_SED" in *'$('*) medir_nao "o sed do instalar_vhost tem \$(" ;; esac

AUTH_SIM=""
AUTH_NAO=""
printf '%s\n' "$INSTALAR" | grep -E '^[[:space:]]*auth="' > "$BASE/auth"
[ "$(grep -c . "$BASE/auth")" = 2 ] || medir_nao "o instalar_vhost nao tem os dois auth= de antes"
while IFS= read -r linha; do
    auth=""
    atribuir "$linha"
    case "$auth" in
        *auth_basic_user_file*) AUTH_SIM=$auth ;;
        *) AUTH_NAO=$auth ;;
    esac
done < "$BASE/auth"
[ -n "$AUTH_SIM" ] && [ -n "$AUTH_NAO" ] || medir_nao "nao separei o auth com senha do sem senha"

for pedaco in "if printf '1.25.1\\n%s\\n' \"\$versao\" | sort -V -C; then" 'http2_diretiva="    http2 on;"' 'http2_listen=" http2"'; do
    printf '%s\n' "$INSTALAR" | grep -qF -- "$pedaco" || medir_nao "a escolha do http2 do instalar_vhost mudou (nao achei: $pedaco)"
done

# O que o modelo manda na tela nova: o Cache-Control de cada location e o
# fallback da location do /novo/.
#
# A LOCATION QUE SUMIU DO MODELO E DEFEITO, e nao "nao consegui medir": sem
# ela, o pedido cai no `location /` (o proxy para o ERP). O ensaio a registra e
# segue, e as sondas mostram o efeito. So a que mudou de forma -- outro
# modificador, uma regex com o mesmo caminho -- e 2: essa o ensaio nao sabe
# ler.
location_do_modelo() {  # $1 a linha exata, $2 a ERE do mesmo caminho em outra forma, $3 o efeito sem ela
    grep -qxF -- "$1" "$MODELO" && return 0
    if grep -vE '^[[:space:]]*#' "$MODELO" | grep -qE "^[[:space:]]*location[[:space:]](.*[[:space:]^])?$2"; then
        medir_nao "a location '${1#    }' do modelo mudou de forma (o caminho segue, em outra linha); ajuste este ensaio"
    fi
    defeito "o modelo nao tem a location '${1#    }': $3"
}
location_do_modelo '    location = /novo {' '/novo([[:space:]]|[{$)|]|$)' \
    '/novo cai no proxy do ERP, e nao vai para /novo/'
location_do_modelo '    location ^~ /novo/assets/ {' '/novo/assets' \
    'os assets saem pela location do /novo/, sem o cache longo'
location_do_modelo '    location ^~ /novo/ {' '/novo/([[:space:]]|[{($)|]|$)' \
    'o /novo/ cai no proxy do ERP; no BVIP, o conferir do deploy nao acha a tela nova e o publicar tira o site do ar'
na_location() {  # $1 a linha da location, $2 a diretiva (fora dos comentarios)
    awk -v l="$1" -v d="$2" 'index($0, l) == 1 && length($0) == length(l) { dentro = 1; next }
        dentro && /^[[:space:]]*#/ { next }
        dentro && /^    [}]/ { exit }
        dentro && index($0, d) { print; exit }' "$MODELO"
}
CACHE_ASSETS=$(na_location '    location ^~ /novo/assets/ {' 'add_header Cache-Control' | sed 's/^.*Cache-Control "\([^"]*\)".*$/\1/')
CACHE_NOVO=$(na_location '    location ^~ /novo/ {' 'add_header Cache-Control' | sed 's/^.*Cache-Control "\([^"]*\)".*$/\1/')
case "$(na_location '    location ^~ /novo/ {' 'try_files')" in
    *index.html*) FALLBACK=sim ;;
    *) FALLBACK=nao ;;
esac

for img in "$IMAGEM_NODE" "$IMAGEM_NGINX"; do
    dk image inspect "$img" > /dev/null 2>&1 \
        || medir_nao "a imagem $img nao esta no cache do docker, e este ensaio nao baixa imagem (docker pull $img, se puder)"
done
ID_NODE=$(dk image inspect -f '{{.Id}}' "$IMAGEM_NODE" | tr -d '\r' | cut -c8-19)
VERSOES=$(dk_run --rm --pull=never "$IMAGEM_NODE" sh -c 'echo "node $(node -v), npm $(npm -v)"' 2>/dev/null | tr -d '\r')
versao=$(dk_run --rm --pull=never "$IMAGEM_NGINX" nginx -v 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
[ -n "$versao" ] || medir_nao "nao li a versao do nginx de $IMAGEM_NGINX"
# A MESMA ESCOLHA do instalar_vhost (conferida acima), pela versao do conteiner.
http2_listen=""
http2_diretiva=""
if printf '1.25.1\n%s\n' "$versao" | sort -V -C; then
    http2_diretiva="    http2 on;"
else
    http2_listen=" http2"
fi
certo "lido do deploy: $IMAGEM_NODE ($ID_NODE; $VERSOES), sh -c '$COMANDO', ${VARIAVEIS% }, o index com $PADRAO"
certo "lido do deploy: DOMINIO=$DOMINIO, FRONT_RAIZ=$FRONT_RAIZ, PORTA_APP=$PORTA_APP; o modelo: Cache-Control '$CACHE_ASSETS' nos assets e '$CACHE_NOVO' no /novo/, fallback=$FALLBACK"

# ---------------------------------------------------------------------------
# 2. O frontend/ DA ORIGEM, num volume que distingue maiusculas.
echo "== 1/4 exportando o frontend/ para um volume do docker"
if [ -n "$DIRETORIO" ]; then
    tar -C "$DIRETORIO/frontend" --exclude=./node_modules --exclude=./dist -cf "$BASE/frontend.tar" . \
        || medir_nao "nao empacotei $DIRETORIO/frontend"
else
    git -c core.autocrlf=false archive --format=tar -o "$BASE/frontend.tar" "$ORIGEM:frontend" \
        || medir_nao "o git archive de $ORIGEM:frontend falhou"
fi
tar -xf "$BASE/frontend.tar" -C "$BASE/fonte" || medir_nao "nao abri o pacote do frontend/"
# O `-u "$(id -u):$(id -g)"` do deploy da o usuario de quem o roda: aqui, o uid
# do Git Bash (sem entrada no /etc/passwd da imagem); no BVIP, rodando como
# root, 0:0. USUARIO_DO_DEPLOY=0:0 ensaia o do BVIP; o volume fica desse dono,
# como o clone do root la.
USUARIO="${USUARIO_DO_DEPLOY:-$(id -u):$(id -g)}"
case "$USUARIO" in *[!0-9:]*|:*|*:|*:*:*|'') medir_nao "USUARIO_DO_DEPLOY='$USUARIO' nao e uid:gid" ;; esac
case "$USUARIO" in *:*) ;; *) medir_nao "USUARIO_DO_DEPLOY='$USUARIO' nao e uid:gid" ;; esac
dk volume create --label "$ROTULO" "$VOL_APP" > /dev/null || medir_nao "nao criei o volume $VOL_APP"
dk_run --rm -i --pull=never --name "$NOME-exporta" -v "$VOL_APP:/app" "$IMAGEM_NODE" sh -c '
    set -e
    tar -xf - -C /app
    touch /app/.caixa-do-ensaio
    if [ -e /app/.CAIXA-DO-ENSAIO ]; then echo "CAIXA: nao distingue"; else echo "CAIXA: distingue"; fi
    rm -f /app/.caixa-do-ensaio
    chown -R "$1" /app
' sh "$USUARIO" < "$BASE/frontend.tar" > "$BASE/exporta.log" 2>&1 \
    || { sed 's/^/    | /' "$BASE/exporta.log"; medir_nao "nao consegui por o frontend/ no volume"; }
grep -q 'CAIXA: distingue' "$BASE/exporta.log" \
    || medir_nao "o volume do docker nao distingue maiusculas: o ensaio nao reproduz o disco do servidor"
certo "$(tar -tf "$BASE/frontend.tar" | grep -vc '/$') arquivos, num volume que distingue maiusculas, do usuario $USUARIO"

# ---------------------------------------------------------------------------
# 3. A BUILD, como o construir_front.
echo "== 2/4 a build: docker run --rm -u $USUARIO ${VARIAVEIS}-v <volume>:/app -w /app $IMAGEM_NODE sh -c '$COMANDO'"
inicio=$(date +%s)
# shellcheck disable=SC2086
dk_run --rm --pull=never --name "$NOME-build" -u "$USUARIO" $VARIAVEIS \
    -v "$VOL_APP:/app" -w /app "$IMAGEM_NODE" \
    sh -c "$COMANDO" > "$BASE/build.log" 2>&1
saida=$?
segundos=$(( $(date +%s) - inicio ))
sed 's/^/    | /' "$BASE/build.log"
if [ "$saida" -ne 0 ]; then
    if grep -qE 'npm (error|ERR!) (code )?(ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENETUNREACH)|npm (error|ERR!) network' "$BASE/build.log"; then
        medir_nao "o npm nao alcancou o registro (saida $saida, acima): a build nao foi medida"
    fi
    echo "  DEFEITO: a build saiu com $saida em $segundos s (acima). No BVIP, o publicar tira o site do ar neste ponto."
    echo
    echo "VERMELHO: a build da tela nova nao passa no Linux."
    exit 1
fi
certo "a build passou em $segundos s"

# ---------------------------------------------------------------------------
# 4. O DIST.
echo "== 3/4 o dist"
INDEX="$BASE/dist/index.html"
dk_run --rm --pull=never --name "$NOME-dist" -v "$VOL_APP:/app:ro" "$IMAGEM_NODE" tar -C /app/dist -cf - . \
    > "$BASE/dist.tar" 2> "$BASE/dist.err" && tar -xf "$BASE/dist.tar" -C "$BASE/dist" \
    || { cat "$BASE/dist.err"; echo "  DEFEITO: a build passou e nao deixou o /app/dist"; echo; echo "VERMELHO."; exit 1; }
# OS NOMES DO DIST, com a caixa exata do volume: a lista do tar, e nao a copia
# do rascunho. O rascunho e uma pasta do Windows, que nao distingue
# maiusculas: `[ -f assets/Fundo.webp ]` nela acha o fundo.webp, e no
# servidor o /novo/assets/Fundo.webp da 404.
tar -tf "$BASE/dist.tar" | sed 's#^[.]/##' | grep -v '/$' | grep . | sort > "$BASE/dist-nomes" \
    || medir_nao "nao li a lista do dist"
existe() { grep -qxF -- "$1" "$BASE/dist-nomes"; }
existe index.html && [ -f "$INDEX" ] || { echo "  DEFEITO: o dist nao tem index.html"; echo; echo "VERMELHO."; exit 1; }
sed 's/^/    | /' "$BASE/dist-nomes"

if grep -q "$PADRAO" "$INDEX"; then
    certo "o index tem $PADRAO, o que o construir_front confere"
else
    defeito "o index nao tem $PADRAO: o construir_front reprovaria, e o publicar tiraria o site do ar"
fi
antes=$FALHAS
grep -oE '(src|href)="[^"]*"' "$INDEX" | sed -E 's/^(src|href)="//; s/"$//' > "$BASE/citados"
n=0
while IFS= read -r ref; do
    n=$((n + 1))
    case "$ref" in
        /novo/*) existe "${ref#/novo/}" || defeito "o index cita $ref, e o dist nao o tem (com essa caixa)" ;;
        *) defeito "o index cita $ref, fora de /novo/: no servidor esse pedido nao chega ao dist" ;;
    esac
done < "$BASE/citados"
[ "$n" -gt 0 ] || defeito "o index nao cita arquivo nenhum"
find "$BASE/dist" -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' \) \
    -exec grep -ohE 'assets/[A-Za-z0-9._-]+[.](js|css|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|json|wasm)' {} + \
    | sort -u > "$BASE/assets-citados"
m=0
while IFS= read -r a; do
    m=$((m + 1))
    existe "$a" || defeito "o dist cita $a, e ele nao existe (com essa caixa)"
done < "$BASE/assets-citados"
[ "$FALHAS" -gt "$antes" ] || certo "$n arquivos citados pelo index, todos em /novo/ e no dist; $m nomes assets/... citados no dist, todos nele"
if grep -rlE '[A-Za-z]:(\\\\|\\|/)(Users|Dev|Windows|Program Files)[\\/]|sistemabv' "$BASE/dist" > "$BASE/windows"; then
    defeito "caminho do Windows no dist: $(sed "s#^$BASE/dist/##" "$BASE/windows" | tr '\n' ' ')"
else
    certo "nenhum caminho absoluto do Windows no dist"
fi
if grep -q '"/src/' "$INDEX"; then defeito "o index ainda aponta para o /src/ do desenvolvimento"; fi

# A ROTA NO CAMINHO pede o fallback: sem ele, recarregar /novo/<rota> da 404.
# No src, o roteador escrito a mao (e os nomes dos roteadores conhecidos); no
# JS do dist, o pushState/replaceState que um roteador de biblioteca leva para
# o bundle sem o nome dele aparecer no src (o wouter, o TanStack Router): a
# History API e chamada pelo nome da propriedade, que o minificador nao troca.
find "$BASE/fonte/src" -type f \( -name '*.ts' -o -name '*.tsx' \) ! -name '*.teste.*' \
    -exec grep -lE 'pushState|replaceState|createBrowserRouter|BrowserRouter|location\.pathname' {} + \
    > "$BASE/rotas" 2>/dev/null
find "$BASE/dist" -type f -name '*.js' -exec grep -lE 'pushState|replaceState' {} + \
    > "$BASE/rotas-dist" 2>/dev/null
ROTA_NO_CAMINHO=nao
if [ -s "$BASE/rotas" ] || [ -s "$BASE/rotas-dist" ]; then
    ROTA_NO_CAMINHO=sim
    if [ "$FALLBACK" = nao ]; then
        defeito "a tela poe rota no caminho ($(sed -e "s#^$BASE/fonte/##" -e "s#^$BASE/dist/#dist/#" "$BASE/rotas" "$BASE/rotas-dist" | tr '\n' ' ')), e a location do /novo/ do modelo nao tem fallback: recarregar a rota da 404"
    fi
else
    certo "nem o src nem o JS do dist mexem no caminho (pushState, replaceState, roteador)"
fi

# ---------------------------------------------------------------------------
# 5. O VHOST, do modelo, pelo sed do deploy.
echo "== 4/4 o vhost do modelo, num $IMAGEM_NGINX (nginx $versao, http2 ${http2_diretiva:+na diretiva}${http2_listen:+no listen})"
for escolha in nao sim; do
    if [ "$escolha" = sim ]; then auth=$AUTH_SIM; else auth=$AUTH_NAO; fi
    tmp="$BASE/conf/vhost-$escolha.conf"
    ( cd "$BASE/arvore" && eval "$BLOCO_SED" ) || medir_nao "o sed do instalar_vhost falhou (SENHA_HTTP=$escolha)"
    if grep -q '__[A-Z0-9_]*__' "$tmp"; then
        defeito "sobrou marcador no vhost gerado (SENHA_HTTP=$escolha): $(grep -o '__[A-Z0-9_]*__' "$tmp" | sort -u | tr '\n' ' ')"
    fi
done
# Quem pede senha e o `auth_basic` com um nome; o `auth_basic off;` a tira, e
# as sondas medem onde.
pede_senha() { grep -E '^[[:space:]]*auth_basic[[:space:]]' "$1" | grep -vqE 'auth_basic[[:space:]]+off[[:space:]]*;'; }
pede_senha "$BASE/conf/vhost-sim.conf" || defeito "o vhost com SENHA_HTTP=sim nao pede senha"
! pede_senha "$BASE/conf/vhost-nao.conf" || defeito "o vhost com SENHA_HTTP=nao pede senha"

SENHA=$(od -An -tx1 -N16 /dev/urandom | tr -d ' \n')
[ "${#SENHA}" -eq 32 ] || medir_nao "nao gerei a senha do htpasswd do ensaio"
dk volume create --label "$ROTULO" "$VOL_CONF" > /dev/null || medir_nao "nao criei o volume $VOL_CONF"
dk volume create --label "$ROTULO" "$VOL_FRONT" > /dev/null || medir_nao "nao criei o volume $VOL_FRONT"
( cd "$BASE/conf" && tar -cf - vhost-nao.conf vhost-sim.conf ) > "$BASE/conf.tar" || medir_nao "nao empacotei os vhosts"
dk_run --rm -i --pull=never --name "$NOME-conf" -v "$VOL_CONF:/ensaio" "$IMAGEM_NGINX" sh -c '
    set -e
    tar -xf - -C /ensaio
    openssl req -x509 -newkey rsa:2048 -nodes -days 2 -subj "/CN=$1" \
        -keyout /ensaio/privkey.pem -out /ensaio/fullchain.pem > /dev/null 2>&1
' sh "$DOMINIO" < "$BASE/conf.tar" || medir_nao "nao gerei o certificado do ensaio"
# A SENHA vai pela entrada, e nao pela linha de comando; nao e impressa.
printf '%s' "$SENHA" | dk_run --rm -i --pull=never --name "$NOME-senha" -v "$VOL_CONF:/ensaio" "$IMAGEM_NGINX" sh -c '
    set -e
    h=$(openssl passwd -apr1 -stdin)
    printf "ensaio:%s\n" "$h" > /ensaio/htpasswd
    chmod 644 /ensaio/htpasswd
' || medir_nao "nao gerei o htpasswd do ensaio"
# COMO O trocar_front: cp -a do dist para $FRONT_RAIZ/novo, e chmod -R a+rX.
dk_run --rm --pull=never --name "$NOME-troca" -v "$VOL_APP:/app:ro" -v "$VOL_FRONT:/front" "$IMAGEM_NODE" \
    sh -c 'set -e; cp -a /app/dist /front/novo; chmod -R a+rX /front' || medir_nao "nao copiei o dist para a raiz servida"

# O nginx do conteiner com so este vhost, onde o host o poria (o
# sites-enabled do Ubuntu e o conf.d da imagem entram no mesmo http {}).
PREPARO='set -e
rm -f /etc/nginx/conf.d/*.conf
mkdir -p "/etc/letsencrypt/live/$1"
cp /ensaio/fullchain.pem /ensaio/privkey.pem "/etc/letsencrypt/live/$1/"
cp /ensaio/htpasswd "$2"
cp "/ensaio/vhost-$3.conf" "/etc/nginx/conf.d/$1.conf"
'
reprovados=0
for escolha in nao sim; do
    if dk_run --rm --pull=never --name "$NOME-t" -v "$VOL_FRONT:$FRONT_RAIZ:ro" -v "$VOL_CONF:/ensaio:ro" "$IMAGEM_NGINX" \
        sh -c "${PREPARO}nginx -t" sh "$DOMINIO" "$HTPASSWD" "$escolha" > "$BASE/t.log" 2>&1; then
        certo "nginx -t do vhost gerado com SENHA_HTTP=$escolha"
    else
        sed 's/^/    | /' "$BASE/t.log"
        defeito "o nginx -t reprovou o vhost gerado com SENHA_HTTP=$escolha: no BVIP, o ativar_vhost devolve o anterior e o publicar tira o site do ar"
        reprovados=$((reprovados + 1))
    fi
done
if [ "$reprovados" -gt 0 ]; then
    echo
    echo "VERMELHO: $FALHAS defeito(s) acima; sem vhost valido, as sondas nao rodam."
    exit 1
fi

dk_run -d --pull=never --name "$NGINX" -p 127.0.0.1::443 \
    -v "$VOL_FRONT:$FRONT_RAIZ:ro" -v "$VOL_CONF:/ensaio:ro" "$IMAGEM_NGINX" \
    sh -c "${PREPARO}exec nginx -g 'daemon off;'" sh "$DOMINIO" "$HTPASSWD" nao > /dev/null \
    || medir_nao "nao subi o nginx do ensaio"
PORTA=$(dk port "$NGINX" 443/tcp | tr -d '\r' | head -n 1)
PORTA=${PORTA##*:}
case "$PORTA" in ''|*[!0-9]*) medir_nao "nao achei a porta publicada do nginx do ensaio" ;; esac

# O codigo HTTP; os cabecalhos e o corpo ficam no rascunho.
sonda() {  # $1 caminho, $2 com-senha (opcional)
    rm -f "$BASE/cab" "$BASE/corpo"
    if [ "${2:-}" = com-senha ]; then
        printf 'user = "ensaio:%s"\n' "$SENHA" | curl -sk -K - --max-time 20 --resolve "$DOMINIO:$PORTA:127.0.0.1" \
            -D "$BASE/cab" -o "$BASE/corpo" -w '%{http_code}' "https://$DOMINIO:$PORTA$1"
    else
        curl -sk --max-time 20 --resolve "$DOMINIO:$PORTA:127.0.0.1" \
            -D "$BASE/cab" -o "$BASE/corpo" -w '%{http_code}' "https://$DOMINIO:$PORTA$1" < /dev/null
    fi
}
cab() {
    [ -f "$BASE/cab" ] || return 0
    tr -d '\r' < "$BASE/cab" | grep -i "^$1:" | tail -n 1 | sed 's/^[^:]*:[[:space:]]*//'
}
# O caminho da 200 com o arquivo do dist, o tipo, o cache do modelo, o
# noindex e o HSTS.
servido() {  # $1 caminho, $2 arquivo do dist, $3 tipo (ERE), $4 Cache-Control, $5 com-senha
    c=$(sonda "$1" "${5:-}")
    [ "$c" = 200 ] || { defeito "$1 -> $c, e devia ser 200"; return; }
    cmp -s "$BASE/corpo" "$2" || { defeito "$1 -> 200, e o corpo nao e o ${2#"$BASE"/} do dist"; return; }
    t=$(cab content-type)
    printf '%s\n' "$t" | grep -qE "^($3)" || { defeito "$1 -> Content-Type '$t', e devia ser $3"; return; }
    k=$(cab cache-control)
    [ "$k" = "$4" ] || { defeito "$1 -> Cache-Control '$k', e o modelo manda '$4'"; return; }
    cab x-robots-tag | grep -q '^noindex' || { defeito "$1 sem o X-Robots-Tag noindex"; return; }
    [ -n "$(cab strict-transport-security)" ] || { defeito "$1 sem o Strict-Transport-Security"; return; }
    certo "$1 -> 200, o arquivo do dist, $t, Cache-Control '$k'${5:+, com a senha}"
}
tipo_de() {
    case "$1" in
        *.js) echo '(application|text)/javascript' ;;
        *.css) echo 'text/css' ;;
        *.svg) echo 'image/svg\+xml' ;;
        *.png) echo 'image/png' ;;
        *.ico) echo 'image/(x-icon|vnd\.microsoft\.icon)' ;;
        *.woff2) echo 'font/woff2' ;;
        *.json) echo 'application/json' ;;
        *) echo '.' ;;
    esac
}
cache_de() {
    case "$1" in /novo/assets/*) printf '%s' "$CACHE_ASSETS" ;; *) printf '%s' "$CACHE_NOVO" ;; esac
}
esperar() {  # $1 o codigo que o /novo/ tem de dar
    i=0
    while [ "$(sonda /novo/)" != "$1" ]; do
        i=$((i + 1))
        if [ "$i" -ge 20 ]; then
            dk logs "$NGINX" 2>&1 | tail -n 20 | sed 's/^/    | /'
            return 1
        fi
        sleep 1
    done
}

echo "  -- SENHA_HTTP=nao (porta $PORTA do loopback)"
esperar 200 || [ "$(sonda /novo/)" != 000 ] || medir_nao "o nginx do ensaio nao respondeu em 20 s"
servido /novo/ "$INDEX" 'text/html' "$CACHE_NOVO"
JS=""
while IFS= read -r ref; do
    case "$ref" in /novo/*) ;; *) continue ;; esac
    [ -n "$JS" ] || case "$ref" in /novo/assets/*.js) JS=$ref ;; esac
    servido "$ref" "$BASE/dist/${ref#/novo/}" "$(tipo_de "$ref")" "$(cache_de "$ref")"
done < "$BASE/citados"
[ -n "$JS" ] || defeito "o index nao cita JavaScript em /novo/assets/"
FALTA="/novo/assets/nao-existe-$$.js"
c=$(sonda "$FALTA")
if [ "$c" = 404 ] && ! grep -q 'id="root"' "$BASE/corpo" 2>/dev/null; then
    certo "$FALTA -> 404, e nao o index"
else
    defeito "$FALTA -> $c$(grep -q 'id="root"' "$BASE/corpo" 2>/dev/null && echo ', com o index no corpo'): o chunk velho em cache viraria tela branca"
fi
c=$(sonda /novo)
l=$(cab location)
case "$c:$l" in
    301:*/novo/) certo "/novo -> 301, Location $l" ;;
    *) defeito "/novo -> $c, Location '$l'; devia ser 301 para /novo/" ;;
esac
if [ "$FALLBACK" = sim ]; then
    servido /novo/agenda "$INDEX" 'text/html' "$CACHE_NOVO"
else
    c=$(sonda /novo/agenda)
    if [ "$c" = 404 ] && ! grep -q 'id="root"' "$BASE/corpo" 2>/dev/null; then
        if [ "$ROTA_NO_CAMINHO" = nao ]; then
            certo "/novo/agenda -> 404: o modelo nao tem fallback, e a tela nao poe rota no caminho"
        else
            certo "/novo/agenda -> 404, o que o modelo sem fallback manda (e a rota no caminho e o defeito acima)"
        fi
    else
        defeito "/novo/agenda -> $c; sem o fallback no modelo, devia ser 404"
    fi
fi

echo "  -- SENHA_HTTP=sim"
dk exec "$NGINX" sh -c 'cp /ensaio/vhost-sim.conf "/etc/nginx/conf.d/$1.conf" && nginx -t > /dev/null 2>&1 && nginx -s reload 2> /dev/null' sh "$DOMINIO" \
    || medir_nao "nao troquei o vhost do ensaio pelo com senha"
esperar 401 || true
c=$(sonda /novo/)
[ "$c" = 401 ] && certo "/novo/ sem a senha -> 401" || defeito "/novo/ sem a senha -> $c, e devia ser 401"
if [ -n "$JS" ]; then
    c=$(sonda "$JS")
    [ "$c" = 401 ] && certo "$JS sem a senha -> 401" || defeito "$JS sem a senha -> $c, e devia ser 401"
fi
servido /novo/ "$INDEX" 'text/html' "$CACHE_NOVO" com-senha
[ -z "$JS" ] || servido "$JS" "$BASE/dist/${JS#/novo/}" "$(tipo_de "$JS")" "$CACHE_ASSETS" com-senha
c=$(sonda "$FALTA" com-senha)
[ "$c" = 404 ] && certo "$FALTA com a senha -> 404" || defeito "$FALTA com a senha -> $c, e devia ser 404"

echo
if [ "$FALHAS" -eq 0 ]; then
    echo "VERDE: a build do publicar passa no Linux ($IMAGEM_NODE, $VERSOES, $segundos s) e o vhost do modelo serve o /novo/ ($IMAGEM_NGINX, nginx $versao), com e sem a senha HTTP."
    exit 0
fi
echo "VERMELHO: $FALHAS defeito(s) acima."
exit 1
