#!/usr/bin/env bash
#
# A trava do deploy do teste e UMA por servidor -- o root e outro usuario, cada
# um no seu clone, nao rodam juntos? E quem nao faz deploy nao a segura? (D444)
#
#     bash scripts/ensaiar-trava-do-deploy-teste.sh
#     bash scripts/ensaiar-trava-do-deploy-teste.sh <copia do scripts/deploy-teste.sh>
#     IMAGEM_DO_ENSAIO=<imagem Linux com bash, flock, git e useradd> bash scripts/ensaiar-trava-do-deploy-teste.sh
#
# Sai com 0 quando todo caso abaixo da a saida esperada; 1 quando algum nao da, e
# diz qual; 2 quando nao consegue medir (sem docker, a imagem fora do cache --
# o ensaio nao baixa nada --, ou o conteiner nao respondeu). O argumento aponta
# o ensaio para uma COPIA do roteiro -- e como se falsifica sem tocar no de
# verdade.
#
# ---------------------------------------------------------------------------
# POR QUE ISTO EXISTE (08/11/2026, a fatia 129; o D444)
#
# Ate a v9 o `deploy-teste.sh` travava em "$HOME/.franquiabv-teste.trava": o
# root travava o /root/..., o outro usuario o $HOME dele, e os dois rodavam ao
# mesmo tempo -- com a recusa dizendo "esta rodando neste servidor". A trava
# existe porque "um publicar rodando junto com um base religava o site no meio
# da carga". Agora ela e um caminho fixo, /var/lock/franquiabv-teste.trava,
# aberto SO PARA LER (o flock trava igual): o `exec 9>` de antes, sobre o
# arquivo que o outro usuario criou, falhava pela permissao 644 e, num
# diretorio 1777, pelo fs.protected_regular do kernel.
#
# ---------------------------------------------------------------------------
# COMO
#
# Num conteiner DESCARTAVEL (docker run --rm, sem rede, da IMAGEM_DO_ENSAIO; a
# padrao e a do app daqui, que ja esta no cache e tem o flock, o git, o
# useradd e o runuser), com o roteiro montado so para leitura: cria o usuario
# `outro`, dois clones de mentira -- o do root e o do outro, cada um com o seu
# dono --, e roda o `conferir` de cada um. Um `git` de mentira no comeco do
# PATH e o primeiro comando depois da trava (o `guardas`): ele anota que foi
# chamado e, para quem segura, dorme; depois sai 1 (o roteiro para ali, com
# "rode de dentro do clone"). Os casos:
#
#   o root segura, o outro tenta      o outro sai 1 com "esta rodando neste
#                                     servidor", sem chegar ao git
#   o outro segura, o root tenta      o root sai 1 do mesmo jeito
#   a trava criada pelo root, livre   o outro passa da trava (chega ao git):
#                                     a permissao do arquivo nao o barra; ela e
#                                     do grupo docker, 640
#   a trava criada pelo outro, livre  o root passa da trava
#   o root com umask 077 a cria       ela sai 640 do grupo docker, e o outro
#                                     passa (o umask de quem cria primeiro nao
#                                     a fecha)
#   a trava em /var/lock              o arquivo existe la, e nao no $HOME de
#                                     ninguem
#   o `estranho` (fora do grupo       nao abre a trava, e o `tirar` do root
#   docker) tenta segura-la           passa
#   a trava plantada pelo estranho,   o outro sai 1 dizendo o `sudo rm -f`; o
#   644 e segura por ele, e depois    `tirar` do outro segue sem ela, com o
#   000                               aviso; o root a apaga e refaz
#   um link pendurado no lugar dela   o root nao cria o alvo, e a troca por uma
#                                     trava de verdade
#
# O QUE ELE NAO AFIRMA: o BVIP. O fs.protected_regular e o protected_symlinks
# deste kernel (o da maquina virtual do Docker) sao o que forem; o roteiro nao
# depende deles -- abre a trava sem O_CREAT, e a cria por um temporario ligado
# com `ln`, que nao segue link --, mas o ensaio nao os liga nem desliga para
# provar. E o conteiner roda como root de verdade so dentro dele.

set -u

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

DEPLOY="${1:-scripts/deploy-teste.sh}"
case "$DEPLOY" in /*) ;; *) DEPLOY="$RAIZ/$DEPLOY" ;; esac
[ -r "$DEPLOY" ] || { echo "NAO CONSEGUI MEDIR: nao li $DEPLOY" >&2; exit 2; }
IMAGEM="${IMAGEM_DO_ENSAIO:-franquiabv-app:latest}"

command -v docker >/dev/null 2>&1 || { echo "NAO CONSEGUI MEDIR: sem o docker" >&2; exit 2; }
docker image inspect "$IMAGEM" >/dev/null 2>&1 \
    || { echo "NAO CONSEGUI MEDIR: a imagem $IMAGEM nao esta no cache (o ensaio nao baixa nada)" >&2; exit 2; }

DENTRO=$(cat <<'FIM'
set -u
falhas=0; casos=0
falhou() { echo "  - $1"; falhas=$((falhas + 1)); }
for c in flock git useradd groupadd runuser stat getent; do command -v "$c" >/dev/null 2>&1 || { echo "NAO CONSEGUI MEDIR: a imagem nao tem o $c" >&2; exit 2; }; done
[ -d /var/lock ] || { echo "NAO CONSEGUI MEDIR: a imagem nao tem /var/lock" >&2; exit 2; }
# O grupo de quem faz deploy (quem roda o docker compose), o outro usuario de
# deploy nele, e um terceiro, de fora (o www-data de outro vhost, por exemplo).
getent group docker >/dev/null 2>&1 || groupadd docker >/dev/null 2>&1 || { echo "NAO CONSEGUI MEDIR: nao criei o grupo docker" >&2; exit 2; }
useradd -m -G docker outro >/dev/null 2>&1 && useradd -m estranho >/dev/null 2>&1 \
    || { echo "NAO CONSEGUI MEDIR: nao criei os usuarios outro e estranho" >&2; exit 2; }

# O git de mentira: o primeiro comando depois da trava.
mkdir -p /ensaio/bin
cat > /ensaio/bin/git <<'GIT'
#!/usr/bin/env bash
: > "/ensaio/marcas/chegou-$(id -un)"
[ "${ENSAIO_DORMIR:-0}" = 0 ] || sleep "$ENSAIO_DORMIR"
exit 1
GIT
chmod 755 /ensaio/bin/git
mkdir -p /ensaio/marcas && chmod 1777 /ensaio/marcas

for dono in root outro; do
    c="/srv/clone-$dono"
    mkdir -p "$c/scripts"
    cp /ensaio/deploy-teste.sh "$c/scripts/deploy-teste.sh"
    : > "$c/docker-compose.teste.yml"
    chown -R "$dono:" "$c"
done

# rodar <usuario> <segundos dormindo no git> [subcomando] [umask] -- em primeiro plano
rodar() {
    local u="$1" d="$2" sub="${3:-conferir}" um="${4:-022}"
    rm -f "/ensaio/marcas/chegou-$u"
    if [ "$u" = root ]; then
        ( umask "$um"; env -i HOME=/root PATH="/ensaio/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" ENSAIO_DORMIR="$d" \
            bash /srv/clone-root/scripts/deploy-teste.sh "$sub" )
    else
        runuser -u outro -- sh -c 'umask "$1"; shift; exec "$@"' sh "$um" env -i HOME=/home/outro PATH="/ensaio/bin:/usr/local/bin:/usr/bin:/bin" ENSAIO_DORMIR="$d" \
            bash /srv/clone-outro/scripts/deploy-teste.sh "$sub"
    fi
}

# segura <quem> <tenta>: quem segura dorme 6 s no git; o outro tenta enquanto isso.
segura() {
    local quem="$1" tenta="$2" i codigo
    casos=$((casos + 1))
    rodar "$quem" 6 > "/ensaio/segura-$quem.txt" 2>&1 &
    local pid=$!
    for i in $(seq 1 100); do [ -f "/ensaio/marcas/chegou-$quem" ] && break; sleep 0.1; done
    [ -f "/ensaio/marcas/chegou-$quem" ] || { falhou "$quem segurando: nao chegou ao git em 10 s ($(tail -n 1 "/ensaio/segura-$quem.txt"))"; wait "$pid"; return; }
    rodar "$tenta" 0 > "/ensaio/tenta-$tenta.txt" 2>&1
    codigo=$?
    wait "$pid"
    [ "$codigo" = 1 ] || falhou "$tenta tentando com o $quem segurando: saiu $codigo, e devia sair 1"
    grep -q 'está rodando neste servidor' "/ensaio/tenta-$tenta.txt" \
        || falhou "$tenta tentando com o $quem segurando: nao foi recusado pela trava ocupada ($(grep -m1 ABORTADO "/ensaio/tenta-$tenta.txt"))"
    [ ! -f "/ensaio/marcas/chegou-$tenta" ] || falhou "$tenta tentando com o $quem segurando: chegou ao git"
}

# livre <dono do arquivo> <quem roda> [subcomando]: a trava existe, de outro dono, e ninguem a segura.
livre() {
    local dono="$1" quem="$2" sub="${3:-conferir}"
    casos=$((casos + 1))
    rodar "$quem" 0 "$sub" > "/ensaio/livre-$quem.txt" 2>&1
    [ -f "/ensaio/marcas/chegou-$quem" ] \
        || falhou "$quem ($sub) com a trava livre, criada pelo $dono: nao passou dela ($(grep -m1 'ABORTADO\|!!' "/ensaio/livre-$quem.txt"))"
}

# a trava como deve ser: arquivo comum, do grupo docker, 640.
TRAVA=/var/lock/franquiabv-teste.trava
certa() { [ ! -L "$TRAVA" ] && [ -f "$TRAVA" ] && [ "$(stat -c '%G %a' "$TRAVA" 2>/dev/null)" = "docker 640" ]; }

segura root outro
segura outro root

casos=$((casos + 1))
[ -e "$TRAVA" ] || falhou "a trava nao esta em $TRAVA"
ls -A /root /home/outro 2>/dev/null | grep -q 'franquiabv-teste.trava' && falhou "ha trava no \$HOME de alguem"

rm -f "$TRAVA"; rodar root 0 >/dev/null 2>&1
[ "$(stat -c %U "$TRAVA" 2>/dev/null)" = root ] && certa || falhou "o root nao criou a trava do grupo docker, 640 ($(stat -c '%U %G %a' "$TRAVA" 2>/dev/null))"
livre root outro
rm -f "$TRAVA"; rodar outro 0 >/dev/null 2>&1
[ "$(stat -c %U "$TRAVA" 2>/dev/null)" = outro ] && certa || falhou "o outro nao criou a trava do grupo docker, 640 ($(stat -c '%U %G %a' "$TRAVA" 2>/dev/null))"
livre outro root

# O UMASK DE QUEM CRIA PRIMEIRO nao fecha a trava para o outro.
rm -f "$TRAVA"; rodar root 0 conferir 077 >/dev/null 2>&1
certa || falhou "o root com umask 077 criou a trava fora do grupo docker 640 ($(stat -c '%G %a' "$TRAVA" 2>/dev/null))"
livre root outro

# QUEM NAO FAZ DEPLOY nao abre a trava, e nao a segura.
casos=$((casos + 1))
runuser -u estranho -- flock -n "$TRAVA" true >/dev/null 2>&1 && falhou "o estranho (fora do grupo docker) abriu e travou a trava"
casos=$((casos + 1))
runuser -u estranho -- sh -c "flock $TRAVA sleep 8" >/dev/null 2>&1 &
pe=$!
sleep 1
rodar root 0 tirar > /ensaio/estranho-tirar.txt 2>&1
[ -f /ensaio/marcas/chegou-root ] || falhou "o tirar do root com o estranho tentando segurar a trava: nao passou dela ($(grep -m1 'ABORTADO' /ensaio/estranho-tirar.txt))"
kill "$pe" 2>/dev/null; wait "$pe" 2>/dev/null

# A TRAVA PLANTADA por quem nao faz deploy, legivel por todos, e segura por ele.
plantar() { rm -f "$TRAVA"; runuser -u estranho -- sh -c "umask 022; : > $TRAVA; chmod ${1:-644} $TRAVA"; }
casos=$((casos + 1))
plantar 644
runuser -u estranho -- sh -c "flock $TRAVA sleep 8" >/dev/null 2>&1 &
pe=$!
sleep 1
rodar outro 0 > /ensaio/plantada-outro.txt 2>&1
[ ! -f /ensaio/marcas/chegou-outro ] || falhou "o outro passou da trava plantada pelo estranho"
grep -q "sudo rm -f $TRAVA" /ensaio/plantada-outro.txt || falhou "o outro, com a trava plantada: a recusa nao diz como tira-la ($(grep -m1 ABORTADO /ensaio/plantada-outro.txt))"
livre estranho outro tirar
grep -q 'tiro sem ela' /ensaio/livre-outro.txt || falhou "o tirar do outro com a trava plantada: nao avisou que tira sem ela"
livre estranho root
certa && [ "$(stat -c %U "$TRAVA")" = root ] || falhou "o root nao refez a trava plantada ($(stat -c '%U %G %a' "$TRAVA" 2>/dev/null))"
kill "$pe" 2>/dev/null; wait "$pe" 2>/dev/null
casos=$((casos + 1))
plantar 000
rodar outro 0 > /ensaio/plantada-000.txt 2>&1
grep -q "sudo rm -f $TRAVA" /ensaio/plantada-000.txt || falhou "o outro, com a trava 000 plantada: a recusa nao diz como tira-la ($(grep -m1 ABORTADO /ensaio/plantada-000.txt))"
livre estranho root

# O LINK PENDURADO no lugar da trava: o root nao cria o alvo.
casos=$((casos + 1))
rm -f "$TRAVA" /etc/ensaio-alvo-da-trava
runuser -u estranho -- ln -s /etc/ensaio-alvo-da-trava "$TRAVA"
rodar root 0 >/dev/null 2>&1
[ ! -e /etc/ensaio-alvo-da-trava ] || falhou "o root criou o alvo do link plantado no lugar da trava"
certa || falhou "o root nao trocou o link plantado por uma trava do grupo docker ($(stat -c '%F %G %a' "$TRAVA" 2>/dev/null))"

echo "protected_regular deste kernel: $(cat /proc/sys/fs/protected_regular 2>/dev/null || echo '?'); protected_symlinks: $(cat /proc/sys/fs/protected_symlinks 2>/dev/null || echo '?')"
if [ "$falhas" -gt 0 ]; then echo "FALHAS=$falhas CASOS=$casos"; exit 1; fi
echo "CASOS=$casos"
exit 0
FIM
)

SAIDA=$(printf '%s\n' "$DENTRO" | docker run --rm -i --pull=never --network none --entrypoint bash \
    -v "$DEPLOY:/ensaio/deploy-teste.sh:ro" "$IMAGEM" -s 2>&1)
CODIGO=$?
printf '%s\n' "$SAIDA" | grep -v '^CASOS=\|^FALHAS='
case "$CODIGO" in
    0) echo "ok: os $(printf '%s\n' "$SAIDA" | sed -n 's/^CASOS=//p') casos -- o root e o outro usuario, cada um no seu clone, travam o mesmo $IMAGEM:/var/lock/franquiabv-teste.trava; a trava de um dono nao barra o outro quando livre; quem esta fora do grupo docker nao a abre, e a plantada nao barra o root nem o tirar" ;;
    1) echo "A TRAVA DO DEPLOY DO TESTE NAO E UMA POR SERVIDOR ($DEPLOY): $(printf '%s\n' "$SAIDA" | sed -n 's/^FALHAS=//p' | sed 's/ CASOS=/ falha(s) em /') caso(s)" ;;
    *) echo "NAO CONSEGUI MEDIR: o conteiner saiu $CODIGO" >&2; exit 2 ;;
esac
exit "$CODIGO"
