-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart_dev
More file actions
executable file
·229 lines (201 loc) · 9.67 KB
/
Copy pathstart_dev
File metadata and controls
executable file
·229 lines (201 loc) · 9.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env bash
#
# start_dev — Sobe tudo que o Oracle Borderless precisa para rodar em desenvolvimento:
#
# 1. Garante um .env (copia de .env.example na primeira vez).
# 2. Sobe o Postgres+pgvector via docker compose, escolhendo automaticamente
# uma porta livre no host (contorna o conflito recorrente na 5432).
# 3. Espera o banco ficar saudável.
# 4. Roda as migrations (alembic upgrade head).
# 5. Roda os seeds de dev — tolerando falha (ex.: sem OPENAI_API_KEY).
# 6. Sobe o backend (uvicorn main:app --reload) em background e espera o /health.
# 7. Sobe o frontend (vite dev server) em primeiro plano (bloqueante).
#
# Ctrl+C derruba os dois — o backend é morto junto com o frontend.
#
# Uso: ./start_dev
#
set -euo pipefail
# --- Sempre a partir da raiz do projeto (onde este script vive) ---
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
COMPOSE_FILE="docker/docker-compose.yml"
CONTAINER="oracle_borderless_db"
ENV_FILE=".env"
CANDIDATE_PORTS=(5432 5434 5435 5436 5437)
FRONTEND_DIR="frontend"
FRONTEND_ENV_FILE="$FRONTEND_DIR/.env"
# Porta do backend: fixa, porque o proxy do vite (frontend/vite.config.ts) aponta
# para http://localhost:8000.
API_PORT=8000
# --- Helpers de log ---
info() { printf '\033[1;34m›\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m✓\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; }
# Porta livre no host? (usa /dev/tcp — não depende de ss/lsof)
port_free() { ! (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null; }
# Lê o valor de uma chave de um arquivo .env (vazio se ausente).
env_get() { grep -E "^$2=" "$1" 2>/dev/null | tail -1 | cut -d= -f2- || true; }
# Grava/atualiza uma chave em um arquivo .env.
env_set() {
local file="$1" key="$2" val="$3"
if grep -qE "^$key=" "$file" 2>/dev/null; then
sed -i "s|^$key=.*|$key=$val|" "$file"
else
printf '%s=%s\n' "$key" "$val" >> "$file"
fi
}
command -v docker >/dev/null || die "docker não encontrado no PATH."
command -v uv >/dev/null || die "uv não encontrado no PATH."
command -v npm >/dev/null || die "npm não encontrado no PATH (necessário para o frontend)."
command -v curl >/dev/null || die "curl não encontrado no PATH (usado no health check do backend)."
command -v setsid >/dev/null || die "setsid não encontrado no PATH (usado para derrubar o backend junto com o script)."
[[ -d "$FRONTEND_DIR" ]] || die "Diretório '$FRONTEND_DIR' não encontrado."
# ---------------------------------------------------------------------------
# 1. .env (backend e frontend)
# ---------------------------------------------------------------------------
if [[ ! -f "$ENV_FILE" ]]; then
cp .env.example "$ENV_FILE"
warn "Criado .env a partir de .env.example — preencha as chaves (ANTHROPIC/OPENAI/NOTION_MCP/TAVILY) quando for usar o oráculo de verdade."
else
ok ".env já existe."
fi
if [[ ! -f "$FRONTEND_ENV_FILE" ]]; then
cp "$FRONTEND_DIR/.env.example" "$FRONTEND_ENV_FILE"
# O .env.example do frontend vem com demo mode ligado; aqui o backend real
# sobe junto, então o default do start_dev é falar com a API.
env_set "$FRONTEND_ENV_FILE" VITE_DEMO_MODE "false"
warn "Criado $FRONTEND_ENV_FILE (VITE_DEMO_MODE=false — usando o backend local)."
else
ok "$FRONTEND_ENV_FILE já existe."
[[ "$(env_get "$FRONTEND_ENV_FILE" VITE_DEMO_MODE)" == "true" ]] \
&& warn "VITE_DEMO_MODE=true em $FRONTEND_ENV_FILE — o frontend vai ignorar o backend e usar dados de demo."
fi
# ---------------------------------------------------------------------------
# 2. Escolhe a porta do Postgres
# ---------------------------------------------------------------------------
DB_PORT=""
# Se nosso container já está de pé, reutiliza a porta publicada dele.
if [[ -n "$(docker ps -q --filter "name=^/${CONTAINER}$" --filter "status=running")" ]]; then
DB_PORT="$(docker port "$CONTAINER" 5432/tcp 2>/dev/null | head -1 | sed 's/.*://')"
[[ -n "$DB_PORT" ]] && info "Container '$CONTAINER' já está rodando na porta $DB_PORT — reutilizando."
fi
# Senão, respeita DB_PORT do .env se ainda estiver livre.
if [[ -z "$DB_PORT" ]]; then
current="$(env_get "$ENV_FILE" DB_PORT)"
if [[ -n "$current" ]] && port_free "$current"; then
DB_PORT="$current"
fi
fi
# Senão, pega a primeira porta candidata livre.
if [[ -z "$DB_PORT" ]]; then
for p in "${CANDIDATE_PORTS[@]}"; do
if port_free "$p"; then DB_PORT="$p"; break; fi
done
[[ -n "$DB_PORT" ]] || die "Nenhuma porta livre entre: ${CANDIDATE_PORTS[*]}."
[[ "$DB_PORT" != "5432" ]] && warn "Porta 5432 ocupada — usando $DB_PORT."
fi
env_set "$ENV_FILE" DB_PORT "$DB_PORT"
env_set "$ENV_FILE" DB_HOST "localhost"
export DB_PORT
ok "Postgres mapeado na porta $DB_PORT do host."
# ---------------------------------------------------------------------------
# 3. Sobe o banco e espera ficar acessível NO HOST
# ---------------------------------------------------------------------------
# pg_isready roda DENTRO do container e por isso não prova que a porta está
# publicada no host — daí checamos a porta do host + pg_isready juntos.
wait_db() { # $1 = tentativas (segundos)
for _ in $(seq 1 "$1"); do
if ! port_free "$DB_PORT" \
&& docker exec "$CONTAINER" pg_isready -U oracle -d oracle_borderless >/dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}
info "Subindo Postgres+pgvector (docker compose)…"
docker compose -f "$COMPOSE_FILE" up -d
info "Aguardando o banco ficar acessível em localhost:$DB_PORT…"
if ! wait_db 20; then
# Container antigo pode ter sido só "iniciado" sem publicar a porta atual.
# Recriar aplica a config do compose (o volume nomeado preserva os dados).
warn "Banco não acessível — recriando o container para aplicar a porta $DB_PORT…"
docker compose -f "$COMPOSE_FILE" up -d --force-recreate
wait_db 30 || die "Banco não respondeu a tempo. Veja: docker logs $CONTAINER"
fi
ok "Banco pronto e acessível em localhost:$DB_PORT."
# Banco de testes (conveniência; não bloqueia o start).
docker exec "$CONTAINER" createdb -U oracle oracle_borderless_test >/dev/null 2>&1 \
&& info "Banco de testes 'oracle_borderless_test' criado." || true
# ---------------------------------------------------------------------------
# 4. Migrations
# ---------------------------------------------------------------------------
info "Rodando migrations (alembic upgrade head)…"
uv run alembic upgrade head
ok "Migrations aplicadas."
# ---------------------------------------------------------------------------
# 5. Seeds (tolerante a falha)
# ---------------------------------------------------------------------------
info "Rodando seeds de dev…"
if uv run python -m database.seeds; then
ok "Seeds aplicados."
else
warn "Seeds falharam — seguindo mesmo assim (causas comuns: tabela 'seeds_executions' sem migration, ou OPENAI_API_KEY vazia p/ embeddings)."
fi
# ---------------------------------------------------------------------------
# 6. Dependências do frontend
# ---------------------------------------------------------------------------
if [[ ! -d "$FRONTEND_DIR/node_modules" ]]; then
info "Instalando dependências do frontend (npm ci)…"
(cd "$FRONTEND_DIR" && npm ci)
ok "Dependências do frontend instaladas."
else
ok "node_modules do frontend já existe."
fi
# ---------------------------------------------------------------------------
# 7. Backend em background
# ---------------------------------------------------------------------------
API_PID=""
api_healthy() { curl -fsS -m 2 "http://localhost:$API_PORT/health" >/dev/null 2>&1; }
# O uvicorn --reload é um supervisor que forka um worker; matar só o PID pai
# deixa o worker órfão segurando a porta 8000. Rodar em sessão própria (setsid)
# permite matar o grupo inteiro de uma vez.
stop_api() {
[[ -n "$API_PID" ]] || return 0
local pid="$API_PID"
API_PID=""
info "Derrubando o backend (pid $pid)…"
kill -- "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
}
trap stop_api EXIT INT TERM
if port_free "$API_PORT"; then
info "Subindo o backend em http://localhost:$API_PORT…"
# --reload-include .env: o uvicorn só recarrega em mudança de *.py por padrão;
# incluir o .env faz o servidor reiniciar (e reler as settings) quando você
# edita chaves como OPENAI_API_KEY — sem isso, a mudança só vale no próximo start.
setsid uv run uvicorn main:app --reload --reload-include .env --host 0.0.0.0 --port "$API_PORT" &
API_PID=$!
info "Aguardando o backend responder em /health…"
for _ in $(seq 1 30); do
kill -0 "$API_PID" 2>/dev/null || die "O backend morreu durante o boot — veja os logs acima."
api_healthy && break
sleep 1
done
api_healthy || die "Backend não respondeu ao /health a tempo."
ok "Backend pronto em http://localhost:$API_PORT (docs em /docs)."
elif api_healthy; then
# Backend já de pé (outro terminal / run anterior). Reutiliza em vez de
# brigar pela porta — e NÃO derruba no fim, porque não é nosso.
warn "Já existe um backend saudável em http://localhost:$API_PORT — reutilizando (não será derrubado no Ctrl+C)."
else
die "Porta $API_PORT está ocupada por outro processo que não responde ao /health. Derrube-o e rode de novo."
fi
# ---------------------------------------------------------------------------
# 8. Frontend em primeiro plano
# ---------------------------------------------------------------------------
ok "Subindo o frontend (vite) em http://localhost:5173 (Ctrl+C para parar tudo)…"
cd "$FRONTEND_DIR"
npm run dev