#!/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 [args] web servidor Next (saída standalone) worker processo de fila migrate [--dry-run] seed-staging flag 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 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