Deploy inicial: API FastAPI + infra Podman/Caddy + docs
- App FastAPI (main.py, app/): rotas /health e /dados_retorno, middleware anti-scanner, logging configurado - Config via configs.json (Pydantic); segredos fora do codigo e do git (.gitignore) - Dockerfile (oracledb thin, HEALTHCHECK) + requirements.txt + .dockerignore - Deploy Podman/Quadlet + Caddy em deploy/ - Docs: CLAUDE.md + docs/ (arquitetura, deploy, known-issues) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
15c89c9038
22 changed files with 1397 additions and 0 deletions
64
docs/arquitetura.md
Normal file
64
docs/arquitetura.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Arquitetura e Visão — API-Scardua
|
||||
|
||||
> Documento da **ideia** do projeto: o quê, o porquê e o roadmap. Salvo pra não
|
||||
> reexplicar toda sessão. Atualizar quando a visão evoluir.
|
||||
|
||||
## Em uma frase
|
||||
|
||||
Uma API que **recebe** dados do Holmes do cliente, **trata**, e **envia** de volta
|
||||
pro banco do cliente — hospedada na infra do dev (FLS), não na do cliente.
|
||||
|
||||
## Os atores
|
||||
|
||||
- **FLS Tecnologia (dev / nós)** — constrói e hospeda a API na **própria VPS**
|
||||
(`api-scardua.flstecnologia.tech`). A infra e o código são nossos.
|
||||
- **Comercial Scardua (cliente)** — tem a conta no **Holmes** e um banco **Oracle**
|
||||
(IP privado, `10.16.x` — não acessível pela internet).
|
||||
- **Holmes** — SaaS externo de workflow de documentos; dispara os dados (webhook)
|
||||
pra nossa API.
|
||||
|
||||
## O fluxo
|
||||
|
||||
```
|
||||
Holmes --webhook--> [API na VPS da FLS] --trata--> [Conector no server do cliente] --> [Oracle do cliente]
|
||||
```
|
||||
|
||||
1. O Holmes envia os dados pra nossa API (URL pública, TLS).
|
||||
2. A API recebe e **trata** os dados.
|
||||
3. A API fala com o **banco do cliente** através de um **conector que roda no
|
||||
servidor do cliente** (que tem acesso à rede privada do Oracle).
|
||||
|
||||
## As decisões e o PORQUÊ (o mais importante)
|
||||
|
||||
A API fica na **nossa VPS**, e não no servidor do cliente, por 2 motivos:
|
||||
|
||||
1. **Não depender de o cliente ter domínio / rota pública.** O Holmes precisa de
|
||||
um endpoint público e estável pra mandar os webhooks — damos isso nós, com
|
||||
domínio + TLS próprios.
|
||||
2. **Não deixar nosso código Python no servidor do cliente.** A lógica fica sob
|
||||
nosso controle, na nossa infra.
|
||||
|
||||
**Consequência técnica:** como o Oracle do cliente está numa rede privada
|
||||
(`10.16.x`), a VPS **não alcança o banco direto**. Por isso existe o **conector no
|
||||
server do cliente** — ele é a ponte entre a API (pública) e o Oracle (privado).
|
||||
|
||||
## Roadmap
|
||||
|
||||
| # | Fase | Estado |
|
||||
|---|---|---|
|
||||
| 1 | **Receber dados** — esqueleto da API, rota de teste, deploy na VPS | ✅ feito |
|
||||
| 2 | **Lógica do Holmes** — ligar os endpoints `/v1/holmes/...` ao serviço `app/services/holmes.py` (hoje `obter_dados_compras` é stub) | ⏳ em andamento (dev) |
|
||||
| 3 | **Conector no cliente** — a ponte API (VPS) ↔ Oracle do cliente (rede privada) | ⬜ a fazer |
|
||||
| 4 | **CI/CD** — Forgejo Actions (valida no PR/main) + registry + auto-update pull-based (ver [deploy.md](deploy.md)) | ⬜ desenhado |
|
||||
|
||||
## Pontos em aberto
|
||||
|
||||
- **Como** o conector no cliente vai funcionar — duas abordagens possíveis:
|
||||
- **Túnel/VPN reverso**: o server do cliente abre um túnel e a VPS passa a
|
||||
alcançar o `10.16.x` → a API conecta no Oracle direto (é o que o código de
|
||||
hoje assume: `oracledb` com o DSN privado em `configs.json`).
|
||||
- **Agente HTTP no cliente**: um serviço no server do cliente que a API chama;
|
||||
ele consulta o Oracle **localmente** e devolve o resultado (aí a API nunca
|
||||
toca no Oracle direto).
|
||||
- Essa escolha decide o destino do `app/infra/database.py` (hoje faltando):
|
||||
conexão direta via túnel, ou um cliente HTTP pro agente.
|
||||
54
docs/deploy.md
Normal file
54
docs/deploy.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Deploy — API-Scardua
|
||||
|
||||
Ambiente: **VPS Hostinger** (Debian 13), **Podman rootless + Quadlet**, **Caddy** (proxy + TLS), user `admin`.
|
||||
No ar: **https://api-scardua.flstecnologia.tech**
|
||||
|
||||
## Acesso à VPS
|
||||
- SSH: `ssh vps-fls` (alias já configurado → 179.197.230.154, porta **3115**, user `admin`).
|
||||
- ⚠️ No Windows use o **OpenSSH nativo (PowerShell/cmd)** — o Git Bash não enxerga o `known_hosts`/config e falha.
|
||||
- Porta 22 é **fechada** no firewall. Sempre `-p 3115` (ou o alias).
|
||||
- `systemctl --user` sempre (rootless) — **nunca** `sudo systemctl` pros containers.
|
||||
|
||||
## Arquitetura
|
||||
- Imagem: hoje buildada **local na VPS** = `localhost/api-scardua:latest` (sem registry ainda).
|
||||
- Container `api-scardua` na rede **`fls`** — só o Caddy alcança; a porta 8000 **não** é publicada no host.
|
||||
- Config: `configs.json` montado read-only de `~/.config/api-scardua/configs.json`.
|
||||
- Quadlet: `~/.config/containers/systemd/api-scardua.container`.
|
||||
- Caddy: `api-scardua.flstecnologia.tech` → `api-scardua:8000`, TLS automático (Let's Encrypt).
|
||||
Caddyfile em `/srv/containers/stacks/caddy/Caddyfile`.
|
||||
|
||||
## Atualizar a app (hoje é manual)
|
||||
```bash
|
||||
# 1. copiar o código atualizado pro build dir da VPS (scp de main.py / app/ / etc.)
|
||||
# 2. na VPS:
|
||||
cd ~/build/api-scardua
|
||||
podman build --format docker -t localhost/api-scardua:latest . # --format docker é OBRIGATÓRIO
|
||||
systemctl --user restart api-scardua
|
||||
```
|
||||
> ⚠️ **`--format docker`**: em formato OCI (padrão do Podman) o `HEALTHCHECK` do Dockerfile é **ignorado**.
|
||||
|
||||
## Mexer no Caddy (com segurança)
|
||||
```bash
|
||||
cd /srv/containers/stacks/caddy
|
||||
cp Caddyfile Caddyfile.bak # backup
|
||||
# editar Caddyfile...
|
||||
podman exec caddy caddy validate --config /etc/caddy/Caddyfile # valida ANTES
|
||||
podman exec caddy caddy reload --config /etc/caddy/Caddyfile # recarrega sem downtime
|
||||
```
|
||||
|
||||
## Comandos úteis
|
||||
```bash
|
||||
systemctl --user status api-scardua # estado do serviço
|
||||
podman logs -f api-scardua # logs ao vivo
|
||||
podman ps --filter name=api-scardua # estado + health
|
||||
podman healthcheck run api-scardua # roda o healthcheck na hora
|
||||
```
|
||||
|
||||
## Healthcheck
|
||||
Embutido na imagem (`HEALTHCHECK` no Dockerfile, exec-form). Bate em `/health` a cada 30s.
|
||||
É o sinal que o `podman auto-update` vai usar pra **rollback** quando o registry/CI estiver pronto.
|
||||
|
||||
## Evolução planejada
|
||||
- **Registry + CI** (Forgejo Actions): buildar e publicar em `git.flstecnologia.tech/<owner>/api-scardua`,
|
||||
trocar o Quadlet pro alvo em `deploy/api-scardua.container` (imagem do registry + `AutoUpdate=registry`) → deploy pull-based.
|
||||
- Backup do Postgres da VPS (pendência geral do servidor).
|
||||
17
docs/known-issues.md
Normal file
17
docs/known-issues.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Problemas conhecidos / TODO — API-Scardua
|
||||
|
||||
## Segurança
|
||||
- ✅ **Senha do Holmes movida pro config** (2026-07-23): agora é `settings.holmes.usuario` / `settings.holmes.senha`, lidos do `configs.json` (gitignored). Saiu do código. **Recomendado ainda trocar a senha**, já que ficou em texto claro antes.
|
||||
- `/dados_retorno` está **público e sem autenticação** — qualquer um pode postar (enche o log). OK pra teste; proteger com token na API real.
|
||||
- `/docs`, `/redoc`, `/openapi.json` ficam expostos quando `ambiente != prod` (atrás de basic auth). Fechar de vez com `ambiente = prod` no config, se não precisar deles.
|
||||
- Log mostra o IP do **Caddy** (10.89.0.x), não o real. Pra ver o IP de origem: rodar o uvicorn com `--proxy-headers --forwarded-allow-ips=*` (seguro aqui, porque a porta 8000 não é publicada no host).
|
||||
|
||||
## Código incompleto
|
||||
- **`app/infra/database.py` não existe** — `app/security.py` importa `db_instance` dele e quebraria. Não há camada de conexão Oracle funcional ainda (nada chama `create_pool` no caminho que roda).
|
||||
- `app/security.py` usa `settings.api.jwt_secret`, que **não existe** em `app/config.py` (`ApiConfig` só tem `port`/`ambiente`/`workers`).
|
||||
- `PyJWT` é importado em `security.py` mas **não está** nas dependências.
|
||||
- `app/v1/holmes/holmes.py` → `obter_dados_compras` é um **stub** (`pass`), sem tipo no parâmetro.
|
||||
- `pyproject.toml` aponta `readme = "README.md"`, mas o arquivo não existe (quebra `pip install .`; por isso o Docker usa `requirements.txt`).
|
||||
|
||||
## Middleware
|
||||
- `block_scanners` é **denylist + heurística**, não allowlist estrita: qualquer path fora da blocklist, com User-Agent normal, **passa** e vira 404 na app (ex.: `/robots.txt`). Não vaza nada, mas é mais permissivo do que parece. Dá pra inverter pra allowlist estrita (403 em tudo fora da lista permitida) — lembrar de incluir `/docs`,`/redoc`,`/openapi.json` na lista.
|
||||
Loading…
Add table
Add a link
Reference in a new issue