126 lines
4.8 KiB
Bash
Executable File
126 lines
4.8 KiB
Bash
Executable File
#!/bin/sh
|
|
# Entrada única da imagem. Mantém os comandos curtos e garante que todos os
|
|
# scripts operacionais rodem no MESMO processo do contêiner.
|
|
#
|
|
# Por que `node --import tsx` e não o CLI do tsx: o CLI cria um processo filho,
|
|
# e o SIGTERM do `docker stop` pararia no pai. O worker precisa receber o sinal
|
|
# para esperar o tick corrente e liberar o lock no `finally`.
|
|
set -eu
|
|
|
|
TSX="--import=tsx"
|
|
|
|
# Armazenamento local (development): o volume `app-storage` montado em /app/.data.
|
|
# Homologação e produção usam o bucket do Supabase e não montam este volume.
|
|
DATA_DIR=/app/.data
|
|
STORAGE_DIR="$DATA_DIR/storage"
|
|
|
|
# Mesma regra de src/infra/storage/index.ts: sem as três variáveis do Supabase,
|
|
# o processo grava no disco local.
|
|
local_storage_in_use() {
|
|
[ -z "${NEXT_PUBLIC_SUPABASE_URL:-}" ] || [ -z "${SUPABASE_SERVICE_ROLE_KEY:-}" ] || [ -z "${SUPABASE_STORAGE_BUCKET:-}" ]
|
|
}
|
|
|
|
# Web e worker recusam subir com armazenamento local que não aceita escrita.
|
|
# Sem isto o processo fica no ar e o primeiro upload responde "Erro interno".
|
|
assert_storage_writable() {
|
|
local_storage_in_use || return 0
|
|
probe="$STORAGE_DIR/.write-probe-$$"
|
|
if mkdir -p "$STORAGE_DIR" 2>/dev/null && touch "$probe" 2>/dev/null; then
|
|
rm -f "$probe"
|
|
return 0
|
|
fi
|
|
echo "STORAGE_NOT_WRITABLE: $STORAGE_DIR não aceita escrita pelo uid $(id -u)." >&2
|
|
echo "Rode o serviço storage-init do compose (docs/spreadsheet-adapter/23-storage-permissions.md)." >&2
|
|
exit 1
|
|
}
|
|
|
|
# Roda como root, uma vez, antes de web e worker (serviço storage-init).
|
|
# Ajusta só os dois diretórios do armazenamento, sem recursão: arquivos que já
|
|
# existem não são tocados, a não ser com --fix-existing, que é passo manual.
|
|
prepare_storage() {
|
|
if [ "$(id -u)" != "0" ]; then
|
|
echo "prepare-storage precisa rodar como root (user: \"0:0\" no serviço storage-init)." >&2
|
|
exit 1
|
|
fi
|
|
app_uid=$(id -u adaptador)
|
|
app_gid=$(id -g adaptador)
|
|
|
|
for dir in "$DATA_DIR" "$STORAGE_DIR"; do
|
|
if [ -L "$dir" ]; then
|
|
echo "[storage-init] recusado: $dir é link simbólico." >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p "$dir"
|
|
owner=$(stat -c '%u:%g' "$dir")
|
|
if [ "$owner" != "$app_uid:$app_gid" ]; then
|
|
chown "$app_uid:$app_gid" "$dir"
|
|
echo "[storage-init] $dir: dono $owner -> $app_uid:$app_gid"
|
|
fi
|
|
mode=$(stat -c '%a' "$dir")
|
|
if [ "$mode" != "750" ]; then
|
|
chmod 750 "$dir"
|
|
echo "[storage-init] $dir: modo $mode -> 750"
|
|
fi
|
|
done
|
|
|
|
if [ "${1:-}" = "--fix-existing" ]; then
|
|
# -xdev não atravessa outro sistema de arquivos; -h não segue link simbólico.
|
|
find "$DATA_DIR" -xdev ! -user "$app_uid" -exec chown -h "$app_uid:$app_gid" {} +
|
|
echo "[storage-init] dono de itens existentes ajustado para $app_uid:$app_gid"
|
|
fi
|
|
|
|
foreign=$(find "$DATA_DIR" -xdev ! -user "$app_uid" | wc -l)
|
|
if [ "$foreign" -gt 0 ]; then
|
|
echo "[storage-init] AVISO: $foreign item(ns) em $DATA_DIR com outro dono; nada foi alterado neles." >&2
|
|
echo "[storage-init] Para ajustar, rode com --fix-existing (procedimento de recuperação no doc 23)." >&2
|
|
fi
|
|
|
|
# Prova com o usuário da aplicação, não com root.
|
|
if ! setpriv --reuid="$app_uid" --regid="$app_gid" --clear-groups \
|
|
sh -c 'p="$1/.write-probe-$$"; touch "$p" && rm -f "$p"' _ "$STORAGE_DIR"; then
|
|
echo "STORAGE_NOT_WRITABLE: o uid $app_uid não escreve em $STORAGE_DIR mesmo após a preparação." >&2
|
|
exit 1
|
|
fi
|
|
echo "[storage-init] STORAGE_WRITABLE: $STORAGE_DIR, dono $app_uid:$app_gid, modo 750"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
uso: entrypoint.sh <comando> [args]
|
|
|
|
web servidor Next (saída standalone)
|
|
worker processo de fila
|
|
migrate [--dry-run]
|
|
seed-staging
|
|
flag <workspace_id> on|off
|
|
cleanup
|
|
verify-config
|
|
check-bucket
|
|
smoke
|
|
worker-health confere o heartbeat do worker (healthcheck do contêiner)
|
|
prepare-storage [--fix-existing]
|
|
prepara /app/.data como root (serviço storage-init)
|
|
exec <cmd...> qualquer outro comando
|
|
EOF
|
|
}
|
|
|
|
cmd="${1:-web}"
|
|
[ $# -gt 0 ] && shift || true
|
|
|
|
case "$cmd" in
|
|
web) assert_storage_writable; exec node server.js ;;
|
|
worker) assert_storage_writable; exec node $TSX scripts/worker.ts ;;
|
|
prepare-storage) prepare_storage "$@" ;;
|
|
migrate) exec node $TSX scripts/migrate.ts "$@" ;;
|
|
seed-staging) exec node $TSX scripts/seed-staging.ts "$@" ;;
|
|
flag) exec node $TSX scripts/set-flag.ts "$@" ;;
|
|
cleanup) exec node $TSX scripts/cleanup-temp.ts "$@" ;;
|
|
verify-config) exec node $TSX scripts/verify-config.ts ;;
|
|
check-bucket) exec node $TSX scripts/check-bucket.ts ;;
|
|
smoke) exec node $TSX scripts/smoke-staging.ts ;;
|
|
worker-health) exec node docker/worker-health.mjs ;;
|
|
exec) exec "$@" ;;
|
|
-h|--help|help) usage ;;
|
|
*) echo "comando desconhecido: $cmd" >&2; usage >&2; exit 2 ;;
|
|
esac
|