# Migração do stack `sistema` para a VPS 154.12.253.40 (15/11/2025)

Este documento registra o procedimento completo usado para clonar e mover o stack `sistema` (Convex self-hosted + dashboard + web/Next.js) da VPS antiga `217.216.64.168` para a VPS nova `154.12.253.40` (ambas Ubuntu 24.04 com Docker Swarm). Use-o como referência para futuros blue/green ou rollbacks.

## 1. Componentes inventariados

| Item | VPS antiga | VPS nova |
| --- | --- | --- |
| Hostname | `vmi2889318` | `vmi2872619` |
| CPU/RAM | 4 vCPU / 8 GB | 8 vCPU / 24 GB |
| Stack | `sistema_convex_backend`, `sistema_convex_dashboard`, `sistema_web` | os mesmos + outros stacks existentes |
| Redes relevantes | `traefik_public`, `digital_network` | idem |
| Volumes do stack | `sistema_convex_data`, `sistema_sistema_db`, `sistema_db` | copiados 1:1 |
| Bind mount | `/apps/sistema` (5 GB) | `/home/renan/apps/sistema` + symlink `/apps/sistema` |

Arquivos auxiliares recuperados da VPS antiga e copiados para `/root` na nova:

- `/root/services.json` – inventário completo de serviços, útil para recriar stacks.
- `/root/replay_services_from_json.sh` – script que recria serviços usando `services.json`.

## 2. Pré-requisitos e boas práticas

1. **Autenticação**: senha root na VPS antiga (`sshpass`) e chave `codex_ed25519` para a nova (chmod 600).
2. **Congelamento de escrita**: antes do delta final, escalar a 0 os serviços que escrevem (`docker service scale sistema_* =0`).
3. **Snapshots/volumes**: cópia via contêiner Alpine + tar em streaming evita salvar arquivos temporários.
4. **DNS**: prepare um alias (ex.: `vps2.esdrasrenan.com.br`) e reduza o TTL dos CNAMEs *antes* do corte para minimizar caches antigos.
5. **TLS**: copie `acme.json` entre as VPS e recarregue o Traefik para manter os certificados válidos após o corte.

## 3. Procedimento realizado

### 3.1. Inventário e auditoria

```
# CPU/memória, Swarm, stacks e volumes na VPS antiga
sshpass -p '...' ssh root@217.216.64.168 'lscpu; free -h; docker info; docker stack ls; docker stack services sistema; docker volume ls'

# Idem na VPS nova (confirmar recursos extras)
ssh -i codex_ed25519 root@154.12.253.40 'lscpu; free -h; docker stack ls; docker service ls'
```

### 3.2. Cópia inicial do código e volumes

```
# Código Next.js (bind mount)
sshpass -p '...' ssh root@217.216.64.168 'tar czf - -C /apps sistema'   | ssh -i codex_ed25519 root@154.12.253.40 'tar xzf - -C /apps'

# Volumes Docker em streaming (repetir para cada volume)
for vol in sistema_convex_data sistema_sistema_db sistema_db; do
  sshpass -p '...' ssh root@217.216.64.168     "docker run --rm -v $vol:/data alpine tar cz -C /data ."   | ssh -i codex_ed25519 root@154.12.253.40       "docker volume create $vol >/dev/null; docker run --rm -i -v $vol:/data alpine tar xz -C /data"
done

# Ajustar caminho esperado pelo serviço web
ssh root@154.12.253.40 'mkdir -p /home/renan/apps && mv /apps/sistema /home/renan/apps/ && ln -sfn /home/renan/apps/sistema /apps/sistema'
```

### 3.3. Recriação dos serviços no Swarm da VPS nova

Recriação manual (mais controle que o script) mantendo labels do Traefik:

```
# Convex backend
NET=traefik_public
STACK=sistema
**docker service create**   --name ${STACK}_convex_backend   --replicas 1   --constraint 'node.role == manager'   --network $NET   --mount type=volume,src=sistema_convex_data,dst=/convex/data   --limit-memory 12g   --env CONVEX_CLOUD_ORIGIN=https://convex.esdrasrenan.com.br   --env CONVEX_SITE_ORIGIN=https://convex.esdrasrenan.com.br   --env FLEET_SYNC_SECRET=...   --env MACHINE_PROVISIONING_SECRET=...   --env MACHINE_TOKEN_TTL_MS=2592000000   --env RUST_LOG=info   --label traefik.http.routers.sistema_convex.rule='Host(`convex.esdrasrenan.com.br`)'   ghcr.io/get-convex/convex-backend:latest

# Dashboard (mantido 0/0 até ser necessário)
docker service create ... --replicas 0 ghcr.io/get-convex/convex-dashboard:latest

# Web (Next.js/Bun) – notar bind mount + volume SQLite
docker service create   --name ${STACK}_web   --replicas 1   --constraint 'node.role == manager'   --network $NET   --mount type=bind,src=/home/renan/apps/sistema,dst=/app   --mount type=volume,src=sistema_sistema_db,dst=/app/data   --limit-memory 2g   --user 1000:1000   --workdir /app   --env NEXT_PUBLIC_APP_URL=https://tickets.esdrasrenan.com.br   --env NEXT_PUBLIC_CONVEX_URL=https://convex.esdrasrenan.com.br   --env CONVEX_INTERNAL_URL=http://sistema_convex_backend:3210   --env BETTER_AUTH_SECRET=...   --label traefik.http.routers.sistema_web.rule='Host(`tickets.esdrasrenan.com.br`)'   oven/bun:1.3.1 bash -lc 'bash /app/scripts/start-web.sh'
```

### 3.4. Certificados TLS

1. Copiar `acme.json` das duas VPS.
2. Mesclar certificados (para não perder a conta/chain nova) e reenviar para o volume `certificados`.
3. Escalar `traefik_traefik` para 0, gravar o arquivo e retornar a 1.

```
sshpass -p '...' ssh root@217.216.64.168 'docker run --rm -v certificados:/data alpine cat /data/acme.json' > acme-old.json
ssh root@154.12.253.40 'docker run --rm -v certificados:/data alpine cat /data/acme.json' > acme-new.json
python3 merge_acme.py  # script que mescla Certificates
cat acme-merged.json | ssh root@154.12.253.40   'docker run --rm -i -v certificados:/data alpine sh -c "cat > /data/acme.json && chmod 600 /data/acme.json"'
```

### 3.5. Delta final antes do corte

1. `docker service scale sistema_* =0` na VPS antiga.
2. Repetir o loop de volumes + `tar` do código para pegar qualquer alteração mais recente.
3. Reaplicar `chown -R 1000:1000 /home/renan/apps/sistema` e re-symlink `/apps/sistema`.
4. Forçar rolling update nos serviços da VPS nova:

```
ssh root@154.12.253.40 'docker service update --force sistema_convex_backend sistema_web'
```

### 3.6. DNS

- Criado `vps2.esdrasrenan.com.br` (A → 154.12.253.40).
- Atualizados os CNAMEs `tickets.esdrasrenan.com.br` e `convex.esdrasrenan.com.br` para apontar ao alias novo (TTL efetivo 300 s após propagação).
- Observação: durante ~4 h alguns clientes ainda enxergaram o IP antigo porque o TTL prévio era 14.400 s; na próxima migração, reduzir o TTL horas antes do corte para evitar 404 temporário.

### 3.7. Imagem Bun com OpenSSL

Para eliminar o warning do Prisma, foi criada uma imagem local (`sistema-web:openssl-20251115`) adicionando `apt-get install openssl`. Atualização do serviço:

```
ssh root@154.12.253.40 'docker build -t sistema-web:openssl-20251115 - <<"EOF"
FROM oven/bun:1.3.1
RUN apt-get update && apt-get install -y --no-install-recommends openssl \n  && rm -rf /var/lib/apt/lists/*
EOF'

docker service update --image sistema-web:openssl-20251115 sistema_web
```

**Atenção**: essa imagem só existe localmente; se outro nó entrar no Swarm, publique-a em um registry ou reconstrua no novo nó antes de escalar o serviço.

## 4. Verificações pós-migração

- `docker service ls | grep sistema` → `convex_backend 1/1`, `web 1/1`, `dashboard 0/0`.
- `curl -k -I --resolve tickets.esdrasrenan.com.br:443:154.12.253.40 https://tickets.esdrasrenan.com.br` → 307 `/login`.
- `curl -k -I --resolve convex.esdrasrenan.com.br:443:154.12.253.40 https://convex.esdrasrenan.com.br` → 200.
- `docker logs <container web>` sem warnings de OpenSSL.
- `dig @1.1.1.1 +short tickets.esdrasrenan.com.br` → `vps2.esdrasrenan.com.br` → `154.12.253.40`.

## 5. Recomendações para futuras trocas

1. **TTL primeiro, migração depois**: reduza CNAMEs para 300 s pelo menos 4 h antes de alterar IPs.
2. **Dashboard opcional**: mantenha `sistema_convex_dashboard` em `0/0` até precisar, escalando com `docker service scale sistema_convex_dashboard=1`.
3. **Imagem Bun custom**: mantenha o Dockerfile acima em repositório (ex.: `containers/sistema-web/Dockerfile`) para reconstruções reproduzíveis.
4. **Rollback**: para voltar à VPS antiga, restaure o DNS (CNAME → `vps.esdrasrenan.com.br`) e escale os serviços antigos para `1`. Guarde a VPS por 24–48 h antes de desativá-la definitivamente.
5. **Logs**: monitore `docker service logs sistema_convex_backend` para garantir que não haja `exit 137`. Se reaparecer, mantenha o limite de memória ≥ 12 GB ou ajuste conforme uso real.

Com este fluxo documentado, repetir ou reverter a migração passa a ser um processo guiado e audível.
