Compare commits
1 Commits
main
...
claude/wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
380440f8e7 |
21
.env.example
21
.env.example
@ -29,6 +29,16 @@ GITEA_DB_USER=gitea
|
||||
GITEA_DB_PASSWORD=troque-essa-senha-forte
|
||||
GITEA_DB_NAME=gitea
|
||||
|
||||
# lronetto-finance (repo à parte, consome esta mesma instância de Postgres)
|
||||
FINANCE_DB_USER=finance
|
||||
FINANCE_DB_PASSWORD=troque-essa-senha-forte
|
||||
FINANCE_DB_NAME=finance
|
||||
|
||||
# lronetto-claudeweb (repo à parte, consome esta mesma instância de Postgres)
|
||||
CLAUDEWEB_DB_USER=claudeweb
|
||||
CLAUDEWEB_DB_PASSWORD=troque-essa-senha-forte
|
||||
CLAUDEWEB_DB_NAME=claudeweb
|
||||
|
||||
# ----- MinIO (root creds) -----
|
||||
MINIO_ROOT_USER=minioadmin
|
||||
MINIO_ROOT_PASSWORD=troque-essa-senha-forte
|
||||
@ -36,6 +46,17 @@ MINIO_ROOT_PASSWORD=troque-essa-senha-forte
|
||||
# Bucket criado já no boot pra ser consumido pela stack wedding_photo
|
||||
WEDDING_BUCKET=wedding-media
|
||||
|
||||
# Bucket opcional pra artefatos do lronetto-claudeweb (diff + log de sessão,
|
||||
# M3/RF-14). Só é usado se o claudeweb tiver S3_ENDPOINT preenchido no seu
|
||||
# .env; sem isso o app funciona normal e só pula o upload. Ver minio/init.sh.
|
||||
CLAUDEWEB_BUCKET=claudeweb-artifacts
|
||||
|
||||
# ----- pgAdmin (login web) -----
|
||||
PGADMIN_EMAIL=admin@localhost
|
||||
PGADMIN_PASSWORD=troque-essa-senha-forte
|
||||
|
||||
# ----- scripts/vps-backup.sh (backup completo do host, opcional) -----
|
||||
# Só precisa preencher se for usar `make backup-vps`. Todas com default sensato.
|
||||
# BACKUP_DIR=./backups/vps
|
||||
# BACKUP_RETENTION_DAYS=14
|
||||
# BACKUP_REMOTE= # ex.: r2:meu-bucket/vps-backup (requer rclone configurado)
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,3 +15,6 @@ minio/data/
|
||||
pgadmin/data/
|
||||
caddy/data/
|
||||
caddy/config/
|
||||
|
||||
# Destino do script de backup do VPS (contém dumps + segredos)
|
||||
backups/
|
||||
|
||||
81
CLAUDE.md
81
CLAUDE.md
@ -30,6 +30,33 @@ No disco os 3 ficam como **diretórios irmãos** (ex.: `C:\Users\lrone\code\lron
|
||||
|
||||
> Histórico: nasceu como monorepo `wedding-app` (branch `main`) com tudo em `infra/{main,gitea,wedding_photo}/`. Foi dividido em 3 repos (fresh start, sem histórico herdado — o monorepo original fica arquivado como backup).
|
||||
|
||||
### `lronetto-main` é infra base compartilhada (não só do casamento)
|
||||
|
||||
`lronetto-main` não é infra dedicada ao casamento — é a **infra base** (Postgres + Redis + MinIO + pgAdmin + Caddy + `infra-net`) que **outros serviços independentes** também consomem, além de `gitea` e `wedding`. Todos rodam no mesmo VPS, todos hospedados no mesmo Gitea self-hosted sob o prefixo `lronetto-`:
|
||||
|
||||
| Repo | URL | Escopo | Orquestrado por este `Makefile`? |
|
||||
|---|---|---|---|
|
||||
| `lronetto-main` | `https://gitea.lronetto.com/lronetto/lronetto-main.git` | Este repo — infra base (platform layer) + orquestrador | — |
|
||||
| `lronetto-gitea` | `https://gitea.lronetto.com/lronetto/lronetto-gitea.git` | Git hosting + Actions, hospeda todos os repos `lronetto-*` (dogfooding) | Sim (`up-gitea`) |
|
||||
| `lronetto-wedding` | `https://gitea.lronetto.com/lronetto/lronetto-wedding.git` | App do casamento (é o que este `CLAUDE.md` documenta em detalhe) | Sim (`up-wedding`) |
|
||||
| `lronetto-finance` | `https://gitea.lronetto.com/lronetto/lronetto-finance.git` | Open Finance (Pluggy) — outro projeto, não relacionado ao casamento | Sim (`up-finance`) |
|
||||
| `lronetto-claudeweb` | `https://gitea.lronetto.com/lronetto/lronetto-claudeweb.git` | Interface web pro Claude Code (Agent SDK), single-user — outro projeto, não relacionado ao casamento | Sim (`up-claudeweb`) |
|
||||
|
||||
`lronetto-finance` e `lronetto-claudeweb` **já estão provisionados e rodando em produção**, consumindo Postgres/Redis/MinIO/Caddy deste repo pela `infra-net` — assim como wedding e gitea. Cada um segue o mesmo padrão de "app self-contained, sem Postgres/Redis/Caddy próprios" já usado pela wedding: repo com `docker-compose.yml` (ou `deploy/compose.yml`) próprio, `infra-net` declarada como `external`, containers conectados por nome (`postgres`, `redis`, `minio`), sem porta pública (exposição só via Caddy do main).
|
||||
|
||||
**O que existe hoje neste repo pra suportar os dois:**
|
||||
- `postgres/init/01-create-databases.sh`: cria roles/databases `finance` e `claudeweb` (além de `wedding`/`gitea`), a partir de `FINANCE_DB_*`/`CLAUDEWEB_DB_*` no `.env`
|
||||
- `docker-compose.yml` (serviço `postgres`): repassa `FINANCE_DB_*`/`CLAUDEWEB_DB_*` pro init script
|
||||
- `minio/init.sh`: cria também o bucket `claudeweb-artifacts` (privado, sem download anônimo — usado só se `lronetto-claudeweb` tiver `S3_ENDPOINT` preenchido, pra artefatos de sessão M3/RF-14); `lronetto-finance` não usa MinIO
|
||||
- `caddy/Caddyfile`: roteia `finance.{DOMAIN_BASE}` → `finance_app:8000` e **`code.{DOMAIN_BASE}`** (não `claudeweb.{DOMAIN_BASE}`!) → `claudeweb-api:8300` (rotas `/api/*`, `/healthz`, `/ws/*`) ou `claudeweb-web:80` (resto, SPA estática)
|
||||
- `Makefile`: targets `up-finance`/`down-finance`/`logs-finance`/`pull-finance`/`rebuild-finance` e os equivalentes `-claudeweb`, seguindo o padrão de `WEDDING_DIR=`/`GITEA_DIR=` (agora também `FINANCE_DIR=` / `CLAUDEWEB_DIR=`, default `../lronetto-finance` / `../lronetto-claudeweb`)
|
||||
|
||||
**Diferença importante**: `make up`/`down`/`restart`/`status` continuam cobrindo só o **core** (main + gitea + wedding — os 3 repos que este documento detalha). `finance` e `claudeweb` têm targets avulsos (`make up-finance`, `make up-claudeweb`, etc.) porque têm ciclo de vida e deploy CI próprios, independentes deste projeto — não entram no `up`/`down` agregado de propósito. `make status` já lista os dois (com `|| true`, então não quebra se não estiverem clonados/de pé).
|
||||
|
||||
**Gotcha extra do `lronetto-claudeweb`**: ele cria e gerencia por fora uma segunda network Docker, `claudeweb-workers` (não é a `infra-net`), e **conecta manualmente os containers `redis`, `gitea` e (opcionalmente) `minio` deste repo a ela** — é assim que os containers efêmeros de worker (Agent SDK) do claudeweb falam com Redis/Gitea/MinIO sem entrar na `infra-net` (isolamento de egress). Isso significa que `redis`/`gitea`/`minio` ficam com **duas networks anexadas** por fora do `docker-compose.yml` deste repo — um `docker network disconnect` acidental, um recreate de container, ou uma limpeza de networks (`docker network prune`) pode quebrar essa conexão sem avisar aqui. Ver `README.md` do `lronetto-claudeweb` pra recriar (`docker network connect claudeweb-workers <container>`).
|
||||
|
||||
> Nota sobre backup: o `scripts/vps-backup.sh` (seção 8) já cobre `finance`/`claudeweb` de graça — o `pg_dumpall` do passo 1 dumpa **todos** os bancos da instância, e o script agora também copia o `.env` de `lronetto-finance`/`lronetto-claudeweb` (via `FINANCE_DIR=`/`CLAUDEWEB_DIR=`) se estiverem clonados como irmãos.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stack técnica
|
||||
@ -71,7 +98,9 @@ lronetto-main/ # PLATFORM LAYER + ORQUESTRADOR (este repo)
|
||||
├── caddy/Caddyfile # hostname-based routing
|
||||
├── postgres/init/01-create-databases.sh # cria DBs wedding + gitea
|
||||
├── minio/{init.sh, cors.json} # cria bucket wedding-media
|
||||
└── pgadmin/servers.json # postgres pré-conectado
|
||||
├── pgadmin/servers.json # postgres pré-conectado
|
||||
├── scripts/vps-backup.sh # backup completo do host (make backup-vps)
|
||||
└── backups/vps/ # destino do vps-backup.sh (gitignored)
|
||||
|
||||
lronetto-gitea/ # GIT + CI
|
||||
├── .gitignore
|
||||
@ -124,6 +153,8 @@ lronetto-wedding/ # APLICAÇÃO
|
||||
- `postgres`, `redis`, `minio`, `pgadmin`, `caddy` (stack main)
|
||||
- `gitea`, `gitea_runner` (stack gitea)
|
||||
- `wedding_app`, `wedding_pg_backup`, `wedding_media_backup` (stack wedding_photo)
|
||||
- `finance_app` (stack `lronetto-finance`, repo à parte)
|
||||
- `claudeweb-api`, `claudeweb-web` (stack `lronetto-claudeweb`, repo à parte)
|
||||
|
||||
### URLs (com `DOMAIN_BASE`)
|
||||
|
||||
@ -135,6 +166,8 @@ lronetto-wedding/ # APLICAÇÃO
|
||||
| `https://minio.{DOMAIN_BASE}` | Console admin do MinIO |
|
||||
| `https://media.{DOMAIN_BASE}` | S3 API pública do MinIO (uploads/downloads) |
|
||||
| `ssh://git@gitea.{DOMAIN_BASE}:2222` | Git via SSH |
|
||||
| `https://finance.{DOMAIN_BASE}` | `lronetto-finance` (Open Finance/Pluggy) — repo à parte, não relacionado ao casamento |
|
||||
| `https://code.{DOMAIN_BASE}` | `lronetto-claudeweb` (interface web pro Claude Code) — repo à parte, subdomínio é `code`, não `claudeweb` |
|
||||
|
||||
### `DOMAIN_BASE`
|
||||
- **Dev local**: `localhost` → Caddy emite cert interno automático pra `*.localhost`
|
||||
@ -149,24 +182,43 @@ lronetto-wedding/ # APLICAÇÃO
|
||||
## 6. Comandos
|
||||
|
||||
### Orquestrador (`lronetto-main/Makefile`)
|
||||
Sobe/derruba as 3 stacks de uma vez. Precisa que `lronetto-gitea` e
|
||||
`lronetto-wedding` estejam como diretórios irmãos (ou ajuste `GITEA_DIR=` /
|
||||
`WEDDING_DIR=`).
|
||||
`make up`/`down`/`restart`/`status` cobrem só o **core do casamento** (main +
|
||||
gitea + wedding — os 3 repos que este documento detalha). Precisa que
|
||||
`lronetto-gitea` e `lronetto-wedding` estejam como diretórios irmãos (ou
|
||||
ajuste `GITEA_DIR=` / `WEDDING_DIR=`).
|
||||
|
||||
`finance` e `claudeweb` (seção 2) têm targets avulsos (`up-finance`,
|
||||
`up-claudeweb`, etc.) — de propósito **não** entram no `up`/`down` agregado,
|
||||
porque têm ciclo de vida e deploy CI próprios. Ajuste via `FINANCE_DIR=` /
|
||||
`CLAUDEWEB_DIR=` se não estiverem como diretórios irmãos.
|
||||
|
||||
```bash
|
||||
make help # lista tudo
|
||||
|
||||
# ----- core (main + gitea + wedding) -----
|
||||
make up # network + main + gitea + wedding (na ordem)
|
||||
make down # inverso
|
||||
make restart # down + up
|
||||
make status # ps das 3 stacks
|
||||
make status # ps do core + finance/claudeweb (se de pé)
|
||||
|
||||
make up-main # só infra base (este repo)
|
||||
make up-gitea
|
||||
make up-wedding # rebuilda imagem do app
|
||||
|
||||
make rebuild-wedding # build --no-cache + up
|
||||
make down-{main,gitea,wedding}
|
||||
make logs-{main,gitea,wedding}
|
||||
make pull-{main,gitea,wedding}
|
||||
|
||||
# ----- outros consumidores da infra base (targets avulsos) -----
|
||||
make up-finance # build + up do app finance
|
||||
make up-claudeweb # build da imagem do worker + up do claudeweb (api+web)
|
||||
make rebuild-{finance,claudeweb}
|
||||
make down-{finance,claudeweb}
|
||||
make logs-{finance,claudeweb}
|
||||
make pull-{finance,claudeweb}
|
||||
|
||||
make backup-vps # roda scripts/vps-backup.sh (backup completo do host,
|
||||
# inclui finance/claudeweb se clonados como irmãos)
|
||||
```
|
||||
|
||||
### App wedding (`lronetto-wedding/Makefile`)
|
||||
@ -262,10 +314,23 @@ make deploy # git reset --hard + up (chamado pelo CI)
|
||||
- `DELETE /api/admin/event/cover` — remove a foto de capa
|
||||
|
||||
### Backup
|
||||
|
||||
**Nível stack (`lronetto-wedding`, sidecars sempre ativos)**
|
||||
- **Postgres**: `prodrigestivill/postgres-backup-local` daily, retenção dias/semanas/meses, escreve em `./backups/postgres/` (bind mount do host)
|
||||
- **MinIO**: alpine + mc cron-driven, `mc mirror` (incremental) pra `./backups/media/`
|
||||
- **Backup remoto opcional**: configurar `BACKUP_REMOTE_*` no `.env` da wedding → espelha pra outro endpoint S3 (R2/B2/etc.)
|
||||
|
||||
**Nível host (`lronetto-main/scripts/vps-backup.sh`, `make backup-vps`)**
|
||||
- Script único que cobre o que os sidecars da wedding **não** cobrem: dump lógico completo do Postgres (`pg_dumpall`, todos os bancos + roles, não só o da wedding), snapshot RDB do Redis, tar do diretório de dados do Gitea (repos + config), `.env` das 3 stacks e os certs do Caddy (evita reemissão no Let's Encrypt após um restore)
|
||||
- Empacota tudo num único `.tar.gz` com permissão `600` (tem segredo dentro — os `.env`) em `backups/vps/`, com rotação por `BACKUP_RETENTION_DAYS` (default 14 dias)
|
||||
- Envio offsite opcional via `rclone` se `BACKUP_REMOTE` estiver setado (ex.: `r2:meu-bucket/vps-backup`)
|
||||
- Roda **no host**, fora de containers (usa `docker exec`/`docker cp` pra falar com `postgres`/`redis`, e lê os diretórios dos repos irmãos via `GITEA_DIR=`/`WEDDING_DIR=`, mesma convenção do Makefile orquestrador)
|
||||
- Pensado pra cron diário (fora de horário de pico):
|
||||
```cron
|
||||
0 3 * * * cd /opt/lronetto-main && ./scripts/vps-backup.sh >> backups/vps/cron.log 2>&1
|
||||
```
|
||||
- Restore é manual (não tem script de restore automático — cada peça volta do seu jeito): `gunzip -c postgres-all.sql.gz | docker exec -i postgres psql -U postgres`, `tar -xzf gitea-data.tar.gz -C ../lronetto-gitea/`, etc.
|
||||
|
||||
### Gitea bootstrap (1ª vez)
|
||||
1. `make up-main` (precisa estar de pé pro postgres + redis)
|
||||
2. `make up-gitea` (runner falha porque ainda não tem token, ok)
|
||||
@ -389,7 +454,7 @@ O `extra_hosts: host-gateway` pra `media.{DOMAIN_BASE}` faz o container resolver
|
||||
7. **Split em 3 repos**: `lronetto-main` + `lronetto-gitea` + `lronetto-wedding` (fresh start). Orquestrador no main, deploy CI no wedding. Monorepo `wedding-app` arquivado.
|
||||
|
||||
Repos / branches:
|
||||
- `lronetto-main`, `lronetto-gitea`, `lronetto-wedding` (atuais; default branch `main`)
|
||||
- `lronetto-main`, `lronetto-gitea`, `lronetto-wedding` (atuais; default branch `main`; hospedados no Gitea self-hosted da própria stack, prefixo `lronetto-*`)
|
||||
- Arquivados no monorepo `wedding-app`: `claude/wedding-qrcode-photos-C0PQt` (Cloudflare original), `claude/docker-vps-migration` (1ª migração Docker Node)
|
||||
|
||||
---
|
||||
@ -404,7 +469,7 @@ Repos / branches:
|
||||
| Novo bucket no MinIO | `lronetto-main`: `minio/init.sh` + variável no `.env.example` |
|
||||
| Outro DB no postgres | `lronetto-main`: `postgres/init/01-create-databases.sh` + role nova |
|
||||
| Novo subdomínio Caddy | `lronetto-main`: `caddy/Caddyfile` + container_name correspondente |
|
||||
| Nova app na rede | novo repo com `docker-compose.yml`, declarar `infra-net` como external, conectar a `postgres`/`redis`/`minio` por nome, e referenciar no Makefile orquestrador |
|
||||
| Nova app na rede | novo repo com `docker-compose.yml`, declarar `infra-net` como external, conectar a `postgres`/`redis`/`minio` por nome, e referenciar no Makefile orquestrador. Exemplos reais já rodando assim: `lronetto-finance` e `lronetto-claudeweb` (seção 2) |
|
||||
| Nova tela no front | `apps/web/src/routes/` + rota em `App.tsx` |
|
||||
| Schema compartilhado front-back | duplica: Zod em `packages/shared/src/schemas.ts` (TS) + Pydantic em `apps/api/app/schemas/api.py` (Python). **Camelo nos dois** |
|
||||
| Workflow CI no Gitea | `.gitea/workflows/*.yml` no repo alvo (sintaxe GitHub Actions) — runner já registrado. Ex.: deploy do app em `lronetto-wedding` |
|
||||
|
||||
92
Makefile
92
Makefile
@ -1,39 +1,60 @@
|
||||
.PHONY: help network up down restart status \
|
||||
up-main up-gitea up-wedding \
|
||||
down-main down-gitea down-wedding \
|
||||
logs-main logs-gitea logs-wedding \
|
||||
pull-main pull-gitea pull-wedding \
|
||||
rebuild-wedding
|
||||
up-main up-gitea up-wedding up-finance up-claudeweb \
|
||||
down-main down-gitea down-wedding down-finance down-claudeweb \
|
||||
logs-main logs-gitea logs-wedding logs-finance logs-claudeweb \
|
||||
pull-main pull-gitea pull-wedding pull-finance pull-claudeweb \
|
||||
rebuild-wedding rebuild-finance rebuild-claudeweb backup-vps
|
||||
|
||||
# Orquestrador das 3 stacks. `lronetto-main` é o "platform layer"
|
||||
# (postgres + redis + minio + pgadmin + caddy). Os repos lronetto-gitea e
|
||||
# lronetto-wedding são clonados como diretórios IRMÃOS e referenciados aqui.
|
||||
# Orquestrador do "core" das stacks do casamento (main + gitea + wedding, os 3
|
||||
# repos que este CLAUDE.md documenta em detalhe). `lronetto-main` é o
|
||||
# "platform layer" (postgres + redis + minio + pgadmin + caddy) e também serve
|
||||
# de infra base pra OUTROS consumidores não relacionados ao casamento
|
||||
# (lronetto-finance, lronetto-claudeweb) — ver seção 2 do CLAUDE.md.
|
||||
#
|
||||
# `make up`/`down`/`restart`/`status` cobrem só o core (main+gitea+wedding),
|
||||
# de propósito: finance/claudeweb têm ciclo de vida e deploy CI próprios,
|
||||
# independentes deste projeto. Os targets up-finance/up-claudeweb existem só
|
||||
# de conveniência operacional (ex.: subir tudo depois de um reboot do VPS).
|
||||
#
|
||||
# Todos os repos são clonados como diretórios IRMÃOS e referenciados aqui.
|
||||
# Ajuste os caminhos abaixo se usar outro layout no disco.
|
||||
|
||||
NET := infra-net
|
||||
WEDDING_DIR ?= ../lronetto-wedding
|
||||
GITEA_DIR ?= ../lronetto-gitea
|
||||
FINANCE_DIR ?= ../lronetto-finance
|
||||
CLAUDEWEB_DIR ?= ../lronetto-claudeweb
|
||||
|
||||
DC_MAIN := docker compose -f docker-compose.yml --env-file .env
|
||||
DC_GITEA := docker compose -f $(GITEA_DIR)/docker-compose.yml --env-file $(GITEA_DIR)/.env
|
||||
DC_WEDDING := docker compose -f $(WEDDING_DIR)/docker-compose.yml --env-file $(WEDDING_DIR)/.env
|
||||
DC_FINANCE := docker compose -f $(FINANCE_DIR)/docker-compose.yml --env-file $(FINANCE_DIR)/.env
|
||||
DC_CLAUDEWEB := docker compose -f $(CLAUDEWEB_DIR)/deploy/compose.yml --env-file $(CLAUDEWEB_DIR)/.env
|
||||
CLAUDEWEB_WORKER_IMAGE ?= claudeweb-worker
|
||||
|
||||
help:
|
||||
@echo "Stacks: main (este repo) | gitea ($(GITEA_DIR)) | wedding ($(WEDDING_DIR))"
|
||||
@echo "Core (orquestrado por make up/down/restart/status):"
|
||||
@echo " main (este repo) | gitea ($(GITEA_DIR)) | wedding ($(WEDDING_DIR))"
|
||||
@echo ""
|
||||
@echo " make up Sobe tudo na ordem (main -> gitea -> wedding)"
|
||||
@echo " make down Desce tudo na ordem inversa"
|
||||
@echo "Outros consumidores da infra base (targets avulsos, ciclo de vida próprio):"
|
||||
@echo " finance ($(FINANCE_DIR)) | claudeweb ($(CLAUDEWEB_DIR))"
|
||||
@echo ""
|
||||
@echo " make up Sobe o core na ordem (main -> gitea -> wedding)"
|
||||
@echo " make down Desce o core na ordem inversa"
|
||||
@echo " make restart down + up"
|
||||
@echo " make status ps de todas as stacks"
|
||||
@echo " make status ps do core + finance/claudeweb (se de pé)"
|
||||
@echo ""
|
||||
@echo " make up-main Sobe só a stack main"
|
||||
@echo " make up-gitea Sobe só o gitea"
|
||||
@echo " make up-wedding Build + up do app wedding"
|
||||
@echo " make rebuild-wedding Force rebuild do app wedding"
|
||||
@echo " make down-{main,gitea,wedding}"
|
||||
@echo " make logs-{main,gitea,wedding}"
|
||||
@echo " make pull-{main,gitea,wedding}"
|
||||
@echo " make up-finance Build + up do app finance"
|
||||
@echo " make up-claudeweb Build da imagem do worker + up do claudeweb (api+web)"
|
||||
@echo " make rebuild-{wedding,finance,claudeweb} Force rebuild"
|
||||
@echo " make down-{main,gitea,wedding,finance,claudeweb}"
|
||||
@echo " make logs-{main,gitea,wedding,finance,claudeweb}"
|
||||
@echo " make pull-{main,gitea,wedding,finance,claudeweb}"
|
||||
@echo " make network Cria a network external '$(NET)' (idempotente)"
|
||||
@echo " make backup-vps Backup completo do host (postgres+redis+gitea+envs+caddy certs)"
|
||||
|
||||
network:
|
||||
@./network.sh
|
||||
@ -48,6 +69,8 @@ status:
|
||||
@$(DC_MAIN) ps || true
|
||||
@$(DC_GITEA) ps || true
|
||||
@$(DC_WEDDING) ps || true
|
||||
@$(DC_FINANCE) ps || true
|
||||
@$(DC_CLAUDEWEB) ps || true
|
||||
|
||||
up-main: network
|
||||
$(DC_MAIN) up -d
|
||||
@ -58,10 +81,26 @@ up-gitea: network
|
||||
up-wedding: network
|
||||
$(DC_WEDDING) up -d --build
|
||||
|
||||
up-finance: network
|
||||
$(DC_FINANCE) up -d --build
|
||||
|
||||
up-claudeweb: network
|
||||
docker build -t $(CLAUDEWEB_WORKER_IMAGE) -f $(CLAUDEWEB_DIR)/worker/Dockerfile $(CLAUDEWEB_DIR)
|
||||
$(DC_CLAUDEWEB) up -d --build
|
||||
|
||||
rebuild-wedding: network
|
||||
$(DC_WEDDING) build --no-cache
|
||||
$(DC_WEDDING) up -d
|
||||
|
||||
rebuild-finance: network
|
||||
$(DC_FINANCE) build --no-cache
|
||||
$(DC_FINANCE) up -d
|
||||
|
||||
rebuild-claudeweb: network
|
||||
docker build -t $(CLAUDEWEB_WORKER_IMAGE) -f $(CLAUDEWEB_DIR)/worker/Dockerfile $(CLAUDEWEB_DIR)
|
||||
$(DC_CLAUDEWEB) build --no-cache
|
||||
$(DC_CLAUDEWEB) up -d
|
||||
|
||||
down-main:
|
||||
$(DC_MAIN) down
|
||||
|
||||
@ -71,6 +110,12 @@ down-gitea:
|
||||
down-wedding:
|
||||
$(DC_WEDDING) down
|
||||
|
||||
down-finance:
|
||||
$(DC_FINANCE) down
|
||||
|
||||
down-claudeweb:
|
||||
$(DC_CLAUDEWEB) down
|
||||
|
||||
logs-main:
|
||||
$(DC_MAIN) logs -f
|
||||
|
||||
@ -80,6 +125,12 @@ logs-gitea:
|
||||
logs-wedding:
|
||||
$(DC_WEDDING) logs -f
|
||||
|
||||
logs-finance:
|
||||
$(DC_FINANCE) logs -f
|
||||
|
||||
logs-claudeweb:
|
||||
$(DC_CLAUDEWEB) logs -f
|
||||
|
||||
pull-main:
|
||||
$(DC_MAIN) pull
|
||||
|
||||
@ -88,3 +139,12 @@ pull-gitea:
|
||||
|
||||
pull-wedding:
|
||||
$(DC_WEDDING) pull
|
||||
|
||||
pull-finance:
|
||||
$(DC_FINANCE) pull
|
||||
|
||||
pull-claudeweb:
|
||||
$(DC_CLAUDEWEB) pull
|
||||
|
||||
backup-vps:
|
||||
@GITEA_DIR=$(GITEA_DIR) WEDDING_DIR=$(WEDDING_DIR) FINANCE_DIR=$(FINANCE_DIR) CLAUDEWEB_DIR=$(CLAUDEWEB_DIR) ./scripts/vps-backup.sh
|
||||
|
||||
@ -29,6 +29,34 @@ wedding.{$DOMAIN_BASE} {
|
||||
}
|
||||
}
|
||||
|
||||
# ----- Finance (lronetto-finance, repo à parte) -----
|
||||
finance.{$DOMAIN_BASE} {
|
||||
encode gzip zstd
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.pluggy.ai; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.pluggy.ai; frame-src https://connect.pluggy.ai; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
|
||||
-Server
|
||||
}
|
||||
reverse_proxy finance_app:8000
|
||||
}
|
||||
|
||||
# ----- Claudeweb (lronetto-claudeweb, repo à parte) — subdomínio é "code", não "claudeweb" -----
|
||||
code.{$DOMAIN_BASE} {
|
||||
encode zstd gzip
|
||||
|
||||
@backend path /api/* /healthz /ws/*
|
||||
handle @backend {
|
||||
reverse_proxy claudeweb-api:8300
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy claudeweb-web:80
|
||||
}
|
||||
}
|
||||
|
||||
# ----- pgAdmin -----
|
||||
pgadmin.{$DOMAIN_BASE} {
|
||||
encode gzip
|
||||
|
||||
@ -14,6 +14,12 @@ services:
|
||||
GITEA_DB_USER: ${GITEA_DB_USER:-gitea}
|
||||
GITEA_DB_PASSWORD: ${GITEA_DB_PASSWORD}
|
||||
GITEA_DB_NAME: ${GITEA_DB_NAME:-gitea}
|
||||
FINANCE_DB_USER: ${FINANCE_DB_USER:-finance}
|
||||
FINANCE_DB_PASSWORD: ${FINANCE_DB_PASSWORD}
|
||||
FINANCE_DB_NAME: ${FINANCE_DB_NAME:-finance}
|
||||
CLAUDEWEB_DB_USER: ${CLAUDEWEB_DB_USER:-claudeweb}
|
||||
CLAUDEWEB_DB_PASSWORD: ${CLAUDEWEB_DB_PASSWORD}
|
||||
CLAUDEWEB_DB_NAME: ${CLAUDEWEB_DB_NAME:-claudeweb}
|
||||
volumes:
|
||||
- ./postgres/data:/var/lib/postgresql/data
|
||||
- ./postgres/init:/docker-entrypoint-initdb.d:ro
|
||||
@ -69,6 +75,7 @@ services:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
WEDDING_BUCKET: ${WEDDING_BUCKET:-wedding-media}
|
||||
CLAUDEWEB_BUCKET: ${CLAUDEWEB_BUCKET:-claudeweb-artifacts}
|
||||
volumes:
|
||||
- ./minio/init.sh:/init.sh:ro
|
||||
- ./minio/cors.json:/cors.json:ro
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Inicializa o bucket usado pela stack wedding_photo. Roda como container
|
||||
# one-shot depois que o MinIO ficou healthy.
|
||||
# Inicializa os buckets consumidos pelas stacks. Roda como container one-shot
|
||||
# depois que o MinIO ficou healthy.
|
||||
set -e
|
||||
|
||||
mc alias set --quiet local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
|
||||
|
||||
# ----- wedding: bucket público (download anônimo) pra fotos/vídeos dos convidados -----
|
||||
mc mb --ignore-existing "local/$WEDDING_BUCKET"
|
||||
mc anonymous set download "local/$WEDDING_BUCKET"
|
||||
mc cors set "local/$WEDDING_BUCKET" /cors.json 2>/dev/null || \
|
||||
echo "[minio-init] cors set ignorado (versão antiga do mc?)"
|
||||
|
||||
echo "[minio-init] bucket '$WEDDING_BUCKET' pronto (anonymous download + CORS)"
|
||||
|
||||
# ----- claudeweb: bucket privado pra artefatos de sessão (diff + log, M3/RF-14).
|
||||
# Opcional: o app só sobe artefatos se S3_ENDPOINT estiver preenchido no seu
|
||||
# .env; sem isso este bucket fica criado mas sem uso. Sem download anônimo
|
||||
# (diferente do wedding-media) porque o conteúdo não é público.
|
||||
mc mb --ignore-existing "local/${CLAUDEWEB_BUCKET:-claudeweb-artifacts}"
|
||||
echo "[minio-init] bucket '${CLAUDEWEB_BUCKET:-claudeweb-artifacts}' pronto (privado)"
|
||||
|
||||
@ -12,6 +12,14 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
|
||||
CREATE ROLE "${GITEA_DB_USER}" WITH LOGIN PASSWORD '${GITEA_DB_PASSWORD}';
|
||||
CREATE DATABASE "${GITEA_DB_NAME}" OWNER "${GITEA_DB_USER}";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "${GITEA_DB_NAME}" TO "${GITEA_DB_USER}";
|
||||
|
||||
CREATE ROLE "${FINANCE_DB_USER}" WITH LOGIN PASSWORD '${FINANCE_DB_PASSWORD}';
|
||||
CREATE DATABASE "${FINANCE_DB_NAME}" OWNER "${FINANCE_DB_USER}";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "${FINANCE_DB_NAME}" TO "${FINANCE_DB_USER}";
|
||||
|
||||
CREATE ROLE "${CLAUDEWEB_DB_USER}" WITH LOGIN PASSWORD '${CLAUDEWEB_DB_PASSWORD}';
|
||||
CREATE DATABASE "${CLAUDEWEB_DB_NAME}" OWNER "${CLAUDEWEB_DB_USER}";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "${CLAUDEWEB_DB_NAME}" TO "${CLAUDEWEB_DB_USER}";
|
||||
EOSQL
|
||||
|
||||
echo "[postgres-init] roles + databases criados para wedding (${WEDDING_DB_NAME}) e gitea (${GITEA_DB_NAME})"
|
||||
echo "[postgres-init] roles + databases criados para wedding (${WEDDING_DB_NAME}), gitea (${GITEA_DB_NAME}), finance (${FINANCE_DB_NAME}) e claudeweb (${CLAUDEWEB_DB_NAME})"
|
||||
|
||||
146
scripts/vps-backup.sh
Executable file
146
scripts/vps-backup.sh
Executable file
@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
# Backup completo do VPS (nível host, fora dos sidecars de cada stack).
|
||||
#
|
||||
# Cobre o que os sidecars de `lronetto-wedding` (postgres-backup-local +
|
||||
# media-backup) NÃO cobrem sozinhos: Redis, dados do Gitea (repos + config),
|
||||
# .env de todos os consumidores da infra base (wedding, gitea, finance,
|
||||
# claudeweb) e certs do Caddy. Pensado pra rodar via cron no host, fora de
|
||||
# qualquer container.
|
||||
#
|
||||
# Nota: o dump do Postgres é `pg_dumpall` (passo 1) — cobre TODOS os bancos da
|
||||
# instância de uma vez (wedding, gitea, finance, claudeweb), não só o da
|
||||
# wedding. Não precisa de um passo por app.
|
||||
#
|
||||
# Uso:
|
||||
# ./scripts/vps-backup.sh
|
||||
#
|
||||
# Cron sugerido (diário às 3h, log em backups/vps/cron.log):
|
||||
# 0 3 * * * cd /opt/lronetto-main && ./scripts/vps-backup.sh >> backups/vps/cron.log 2>&1
|
||||
#
|
||||
# Env vars (todas opcionais, com default):
|
||||
# BACKUP_DIR destino local dos tarballs (default: ./backups/vps)
|
||||
# BACKUP_RETENTION_DAYS dias de retenção local (default: 14)
|
||||
# BACKUP_REMOTE remote do rclone pra cópia offsite, ex.: r2:meu-bucket/vps-backup
|
||||
# (vazio = não envia offsite)
|
||||
# GITEA_DIR caminho do repo lronetto-gitea (default: ../lronetto-gitea)
|
||||
# WEDDING_DIR caminho do repo lronetto-wedding (default: ../lronetto-wedding)
|
||||
# FINANCE_DIR caminho do repo lronetto-finance (default: ../lronetto-finance)
|
||||
# CLAUDEWEB_DIR caminho do repo lronetto-claudeweb (default: ../lronetto-claudeweb)
|
||||
# MINIO_ROOT_USER / MINIO_ROOT_PASSWORD só necessários se quiser o mirror
|
||||
# direto do MinIO por aqui (ver passo 4)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
MAIN_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
BACKUP_DIR="${BACKUP_DIR:-$MAIN_DIR/backups/vps}"
|
||||
BACKUP_RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-14}"
|
||||
BACKUP_REMOTE="${BACKUP_REMOTE:-}"
|
||||
GITEA_DIR="${GITEA_DIR:-$MAIN_DIR/../lronetto-gitea}"
|
||||
WEDDING_DIR="${WEDDING_DIR:-$MAIN_DIR/../lronetto-wedding}"
|
||||
FINANCE_DIR="${FINANCE_DIR:-$MAIN_DIR/../lronetto-finance}"
|
||||
CLAUDEWEB_DIR="${CLAUDEWEB_DIR:-$MAIN_DIR/../lronetto-claudeweb}"
|
||||
|
||||
TS="$(date +%Y%m%d-%H%M%S)"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
STAGE="$WORK_DIR/vps-backup-$TS"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
log() { echo "[vps-backup] $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
||||
cleanup() { rm -rf "$WORK_DIR"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
container_up() {
|
||||
docker ps --format '{{.Names}}' | grep -qx "$1"
|
||||
}
|
||||
|
||||
# 1. Postgres: dump lógico de TODOS os bancos + roles (pg_dumpall)
|
||||
if container_up postgres; then
|
||||
log "Dump do Postgres (pg_dumpall)..."
|
||||
docker exec -i postgres pg_dumpall -U postgres | gzip > "$STAGE/postgres-all.sql.gz"
|
||||
else
|
||||
log "AVISO: container 'postgres' não está rodando — pulando dump, backup incompleto"
|
||||
fi
|
||||
|
||||
# 2. Redis: snapshot RDB (BGSAVE + copia o dump.rdb gerado)
|
||||
if container_up redis; then
|
||||
log "Snapshot do Redis (BGSAVE)..."
|
||||
docker exec redis redis-cli BGSAVE >/dev/null
|
||||
for _ in $(seq 1 30); do
|
||||
sleep 1
|
||||
docker exec redis redis-cli INFO persistence 2>/dev/null | grep -q 'rdb_bgsave_in_progress:0' && break
|
||||
done
|
||||
docker cp redis:/data/dump.rdb "$STAGE/redis-dump.rdb" 2>/dev/null \
|
||||
|| log "AVISO: não consegui copiar dump.rdb do redis"
|
||||
else
|
||||
log "AVISO: container 'redis' não está rodando — pulando snapshot"
|
||||
fi
|
||||
|
||||
# 3. Gitea: repos + config + dados (tudo que o docker-compose do gitea monta em bind mount)
|
||||
if [ -d "$GITEA_DIR" ]; then
|
||||
if [ -d "$GITEA_DIR/gitea/data" ]; then
|
||||
log "Compactando dados do Gitea ($GITEA_DIR/gitea/data)..."
|
||||
tar -czf "$STAGE/gitea-data.tar.gz" -C "$GITEA_DIR" gitea/data
|
||||
else
|
||||
log "AVISO: $GITEA_DIR/gitea/data não encontrado (confira o bind mount no docker-compose.yml do gitea)"
|
||||
fi
|
||||
else
|
||||
log "AVISO: $GITEA_DIR não encontrado — pulando backup do Gitea (ajuste GITEA_DIR= se o caminho for outro)"
|
||||
fi
|
||||
|
||||
# 4. MinIO: mirror incremental do bucket de mídia via mc (opcional — a stack wedding já
|
||||
# tem o sidecar media-backup fazendo isso continuamente; isso aqui é um extra/fallback
|
||||
# se você rodar esse script num host que não tem o sidecar, ou quiser um snapshot avulso)
|
||||
if command -v mc >/dev/null 2>&1 && container_up minio; then
|
||||
log "Mirror do MinIO (bucket ${WEDDING_BUCKET:-wedding-media})..."
|
||||
mc alias set vps-backup-src "http://localhost:9000" \
|
||||
"${MINIO_ROOT_USER:?defina MINIO_ROOT_USER pra fazer o mirror do MinIO, ou remova o binário mc do PATH pra pular essa etapa}" \
|
||||
"${MINIO_ROOT_PASSWORD:?defina MINIO_ROOT_PASSWORD pra fazer o mirror do MinIO}" >/dev/null
|
||||
mkdir -p "$STAGE/minio-media"
|
||||
mc mirror --quiet "vps-backup-src/${WEDDING_BUCKET:-wedding-media}" "$STAGE/minio-media" \
|
||||
|| log "AVISO: mc mirror falhou"
|
||||
mc alias remove vps-backup-src >/dev/null 2>&1 || true
|
||||
else
|
||||
log "mc indisponível (ou minio parado) — pulando mirror direto; confie no sidecar media-backup da stack wedding"
|
||||
fi
|
||||
|
||||
# 5. .env de todos os consumidores da infra base (segredos!) — o tarball final
|
||||
# fica com permissão 600. Cobre os repos "core" (wedding/gitea) e os demais
|
||||
# consumidores do platform layer (finance/claudeweb), se estiverem clonados.
|
||||
log "Copiando .env de todos os repos..."
|
||||
mkdir -p "$STAGE/envs"
|
||||
[ -f "$MAIN_DIR/.env" ] && cp "$MAIN_DIR/.env" "$STAGE/envs/lronetto-main.env"
|
||||
[ -f "$GITEA_DIR/.env" ] && cp "$GITEA_DIR/.env" "$STAGE/envs/lronetto-gitea.env"
|
||||
[ -f "$WEDDING_DIR/.env" ] && cp "$WEDDING_DIR/.env" "$STAGE/envs/lronetto-wedding.env"
|
||||
[ -f "$FINANCE_DIR/.env" ] && cp "$FINANCE_DIR/.env" "$STAGE/envs/lronetto-finance.env"
|
||||
[ -f "$CLAUDEWEB_DIR/.env" ] && cp "$CLAUDEWEB_DIR/.env" "$STAGE/envs/lronetto-claudeweb.env"
|
||||
|
||||
# 6. Certs do Caddy (evita reemissão no Let's Encrypt depois de um restore)
|
||||
if [ -d "$MAIN_DIR/caddy/data" ]; then
|
||||
log "Compactando certs do Caddy..."
|
||||
tar -czf "$STAGE/caddy-data.tar.gz" -C "$MAIN_DIR/caddy" data
|
||||
fi
|
||||
|
||||
# 7. Empacota tudo num único tarball final, permissão restrita (tem segredo dentro)
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
FINAL="$BACKUP_DIR/vps-backup-$TS.tar.gz"
|
||||
log "Gerando tarball final: $FINAL"
|
||||
tar -czf "$FINAL" -C "$WORK_DIR" "vps-backup-$TS"
|
||||
chmod 600 "$FINAL"
|
||||
|
||||
# 8. Envia offsite via rclone, se configurado (BACKUP_REMOTE + rclone instalado)
|
||||
if [ -n "$BACKUP_REMOTE" ]; then
|
||||
if command -v rclone >/dev/null 2>&1; then
|
||||
log "Enviando offsite pra $BACKUP_REMOTE..."
|
||||
rclone copy "$FINAL" "$BACKUP_REMOTE" || log "AVISO: rclone copy falhou"
|
||||
else
|
||||
log "AVISO: BACKUP_REMOTE definido mas rclone não está instalado — pulando envio offsite"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 9. Rotação: apaga tarballs locais mais velhos que a retenção configurada
|
||||
log "Rotacionando backups locais (> $BACKUP_RETENTION_DAYS dias)..."
|
||||
find "$BACKUP_DIR" -name 'vps-backup-*.tar.gz' -mtime "+$BACKUP_RETENTION_DAYS" -print -delete
|
||||
|
||||
log "Backup concluído: $FINAL ($(du -h "$FINAL" | cut -f1))"
|
||||
Loading…
Reference in New Issue
Block a user