Migra banco de dados de SQLite para PostgreSQL

- Muda provider Prisma de sqlite para postgresql
- Remove dependencias SQLite (better-sqlite3, adapter)
- Atualiza Better Auth para provider postgresql
- Simplifica prisma.ts removendo adapter SQLite
- Atualiza stack.yml para usar PostgreSQL existente com 2 replicas
- Remove logica de rebuild better-sqlite3 do start-web.sh
- Adiciona script de migracao de dados SQLite -> PostgreSQL
- Atualiza healthcheck para testar PostgreSQL via Prisma
- Habilita start-first deploy para zero-downtime

Melhoria: permite multiplas replicas e deploys sem downtime.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-11 00:35:27 -03:00
parent fb97d9bec8
commit 33a59634e7
10 changed files with 362 additions and 223 deletions

View file

@ -9,28 +9,11 @@ cd /app
export BUN_INSTALL_CACHE_DIR="${BUN_INSTALL_CACHE_DIR:-/tmp/bun-cache}"
mkdir -p "$BUN_INSTALL_CACHE_DIR"
DB_PATH="/app/data/db.sqlite"
echo "[start-web] Using bun cache dir: $BUN_INSTALL_CACHE_DIR"
echo "[start-web] Using APP_DIR=$(pwd)"
echo "[start-web] NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}"
echo "[start-web] NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}"
ensure_db_writable() {
mkdir -p "$(dirname "$DB_PATH")"
if [ ! -e "$DB_PATH" ]; then
touch "$DB_PATH" || true
chmod 660 "$DB_PATH" 2>/dev/null || true
fi
if ! touch "$DB_PATH" >/dev/null 2>&1; then
echo "[start-web] ERRO: não foi possível escrever em $DB_PATH (verifique permissões do volume /app/data)" >&2
ls -ld /app/data "$DB_PATH" >/dev/null 2>&1 || true
exit 1
fi
}
ensure_db_writable
echo "[start-web] DATABASE_URL=${DATABASE_URL:+set}"
# Ensure system deps for native modules (best-effort, idempotent)
if command -v apt-get >/dev/null 2>&1; then
@ -76,113 +59,59 @@ else
echo "[start-web] apt-get unavailable; skipping system deps install" >&2
fi
# Rebuild native better-sqlite3 bindings for the current Node version
if command -v npm >/dev/null 2>&1; then
check_better_sqlite3() {
node - <<'EOF'
const path = require("node:path")
try {
const pkgPath = require.resolve("better-sqlite3/package.json")
const pkg = require(pkgPath)
const binding = path.join(path.dirname(pkgPath), "build", "Release", "better_sqlite3.node")
require("better-sqlite3")
console.log(`[start-web] better-sqlite3 ok (v${pkg.version}) binding=${binding}`)
process.exit(0)
} catch (error) {
console.error("[start-web] better-sqlite3 load failed:", error?.message || error)
process.exit(1)
}
EOF
}
# Aguardar PostgreSQL estar pronto
wait_for_postgres() {
local max_attempts=30
local attempt=1
copy_fallback_binding() {
local temp_dir="/tmp/bsql-fallback"
rm -rf "$temp_dir"
mkdir -p "$temp_dir"
(
cd "$temp_dir" &&
npm install better-sqlite3@11.10.0 --build-from-source --no-save >/dev/null 2>&1
) || {
echo "[start-web] fallback npm install falhou"
return 1
}
local src_pkg="$temp_dir/node_modules/better-sqlite3"
local dest_pkg="/app/node_modules/.bun/better-sqlite3@11.10.0/node_modules/better-sqlite3"
mkdir -p "$dest_pkg"
cp -R "$src_pkg/"* "$dest_pkg/" || return 1
echo "[start-web] fallback: pacote better-sqlite3 copiado para .bun store"
return 0
}
echo "[start-web] Aguardando PostgreSQL..."
rebuild_and_repin_sqlite() {
echo "[start-web] rebuilding better-sqlite3 para a runtime atual"
npm rebuild better-sqlite3 --build-from-source >/dev/null 2>&1 || {
echo "[start-web] rebuild falhou; tentando fallback" >&2
copy_fallback_binding || echo "[start-web] fallback também falhou" >&2
}
node - <<'EOF'
const fs = require("node:fs")
const path = require("node:path")
try {
const pkgPath = require.resolve("better-sqlite3/package.json")
const pkgDir = path.dirname(pkgPath)
const pkg = require(pkgPath)
const built = path.join(pkgDir, "build", "Release", "better_sqlite3.node")
// Copy to bun store
const store = path.join(process.cwd(), "node_modules", ".bun", `better-sqlite3@${pkg.version}`, "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node")
fs.mkdirSync(path.dirname(store), { recursive: true })
fs.copyFileSync(built, store)
console.log(`[start-web] better-sqlite3 (v${pkg.version}) copiado para store bun: ${store}`)
// Copy to @prisma/adapter-better-sqlite3 and @prisma/client locations
const nodeVersion = process.version.slice(1).split('.')[0] + '.' + process.version.slice(1).split('.')[1] + '.' + process.version.slice(1).split('.')[2]
const adapterPaths = [
// Adapter locations
path.join(process.cwd(), "node_modules", "@prisma", "adapter-better-sqlite3", "build", "Release", "better_sqlite3.node"),
path.join(process.cwd(), "node_modules", "@prisma", "adapter-better-sqlite3", "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node"),
// @prisma/client locations that the adapter might be looking at
path.join(process.cwd(), "node_modules", "@prisma", "client", "build", "Release", "better_sqlite3.node"),
path.join(process.cwd(), "node_modules", "@prisma", "client", "Release", "better_sqlite3.node"),
path.join(process.cwd(), "node_modules", "@prisma", "client", "compiled", nodeVersion, "linux", "x64", "better_sqlite3.node"),
// Bun store locations
path.join(process.cwd(), "node_modules", ".bun", "@prisma-adapter-better-sqlite3@7.0.0", "node_modules", "@prisma", "adapter-better-sqlite3", "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node"),
]
for (const dest of adapterPaths) {
try {
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.copyFileSync(built, dest)
console.log(`[start-web] binding copiado para: ${dest}`)
} catch (e) {
// Ignore path copy errors
}
}
} catch (error) {
console.error("[start-web] não foi possível copiar binding para .bun store:", error?.message || error)
}
EOF
}
if [ "${SKIP_SQLITE_REBUILD:-false}" = "true" ]; then
echo "[start-web] SKIP_SQLITE_REBUILD=true; tentando usar bindings existentes"
if ! check_better_sqlite3; then
echo "[start-web] bindings inválidos; forçando rebuild mesmo com SKIP_SQLITE_REBUILD=true"
rebuild_and_repin_sqlite
check_better_sqlite3 || {
echo "[start-web] ERRO: better-sqlite3 continua inválido após rebuild" >&2
exit 1
}
while [ $attempt -le $max_attempts ]; do
if node -e "
const url = process.env.DATABASE_URL;
if (!url) { console.error('DATABASE_URL not set'); process.exit(1); }
fetch(url.replace(/^postgresql:/, 'http:').replace(/\/[^/]+$/, '/'), { method: 'HEAD', signal: AbortSignal.timeout(2000) })
.then(() => process.exit(0))
.catch(() => process.exit(1));
" 2>/dev/null; then
echo "[start-web] PostgreSQL pronto!"
return 0
fi
else
rebuild_and_repin_sqlite
check_better_sqlite3 || {
echo "[start-web] ERRO: better-sqlite3 inválido após rebuild; tentando fallback"
copy_fallback_binding && check_better_sqlite3 || {
echo "[start-web] ERRO: better-sqlite3 continua inválido após fallback" >&2
exit 1
}
}
fi
# Fallback: tenta via psql se disponivel
if command -v psql >/dev/null 2>&1; then
if psql "$DATABASE_URL" -c "SELECT 1" >/dev/null 2>&1; then
echo "[start-web] PostgreSQL pronto (via psql)!"
return 0
fi
fi
# Fallback simples: verifica se o host responde
local pg_host=$(echo "$DATABASE_URL" | sed -E 's/.*@([^:\/]+).*/\1/')
local pg_port=$(echo "$DATABASE_URL" | sed -E 's/.*:([0-9]+)\/.*/\1/')
if timeout 2 bash -c "echo >/dev/tcp/$pg_host/$pg_port" 2>/dev/null; then
echo "[start-web] PostgreSQL acessivel na porta $pg_port!"
return 0
fi
echo "[start-web] Tentativa $attempt/$max_attempts - PostgreSQL nao disponivel"
sleep 2
attempt=$((attempt + 1))
done
echo "[start-web] AVISO: PostgreSQL nao confirmado apos $max_attempts tentativas, continuando mesmo assim..."
return 0
}
# Verificar se DATABASE_URL esta definida
if [ -z "${DATABASE_URL:-}" ]; then
echo "[start-web] ERRO: DATABASE_URL nao definida"
exit 1
fi
# Aguardar PostgreSQL em producao
if [ "${NODE_ENV:-}" = "production" ]; then
wait_for_postgres
fi
# Bun keeps its store in node_modules/.bun by default; ensure it exists and is writable