diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f1d6686 --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +ASPNETCORE_ENVIRONMENT=Production +APP_USE_HTTPS_REDIRECTION=false +DOMAIN=linegestao.inglinesystems.com.br +ACME_EMAIL=seu-email@dominio.com + +JWT_KEY=troque-por-uma-chave-bem-forte +JWT_ISSUER=LineGestao +JWT_AUDIENCE=LineGestao + +POSTGRES_DB=linegestao +POSTGRES_USER=linegestao_app +POSTGRES_PASSWORD=troque-por-uma-senha-forte + +SEED_ADMIN_EMAIL=admin@linegestao.local +SEED_ADMIN_PASSWORD=troque-por-uma-senha-forte +SEED_ADMIN_NAME=Administrador +SEED_DEFAULT_TENANT_NAME=Default + +FRONTEND_PUBLIC_URL=https://linegestao.inglinesystems.com.br diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..ee84c88 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,5 @@ +{$DOMAIN:linegestao.inglinesystems.com.br} { + encode zstd gzip + + reverse_proxy api:8080 +} diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 3d4fde5..6130932 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -74,6 +74,97 @@ FRONTEND_PUBLIC_URL=https://seu-dominio.com ## 4) Subir a stack com Docker Compose +### Alternativa principal (com domínio + HTTPS automático via Caddy) + +Agora que o domínio está apontando para o servidor, use `docker-compose.domain.yml` para publicar em **HTTPS** com certificado automático (Let's Encrypt). + +1. Ajuste o `.env` na pasta da API: + +```env +DOMAIN=linegestao.inglinesystems.com.br +ACME_EMAIL=seu-email@dominio.com +FRONTEND_PUBLIC_URL=https://linegestao.inglinesystems.com.br +APP_USE_HTTPS_REDIRECTION=false +``` + +2. Libere portas públicas no firewall (uma vez): + +```bash +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +``` + +3. Suba stack com Caddy: + +```bash +cd ~/apps/line-gestao-api +docker compose -f docker-compose.domain.yml up -d --build +``` + +4. Valide certificado e API em HTTPS: + +```bash +curl -Iv https://linegestao.inglinesystems.com.br/health +``` + +Resultado esperado: +- status `HTTP/2 200` +- certificado emitido para `linegestao.inglinesystems.com.br` + +> Importante: no primeiro boot o Caddy pode levar alguns segundos para obter o certificado. Se falhar, confira se DNS já propagou e se as portas 80/443 estão acessíveis. + + +### Erro comum: `DOMAIN is required` + +Se aparecer este erro ao subir `docker-compose.domain.yml`, o `.env` da API está sem a variável `DOMAIN`. + +Corrija assim no servidor: + +```bash +cd ~/apps/line-gestao-api +cp -n .env.example .env + +# confirme variáveis mínimas +grep -E '^(DOMAIN|ACME_EMAIL|FRONTEND_PUBLIC_URL|JWT_KEY|POSTGRES_PASSWORD)=' .env + +# se DOMAIN não aparecer, adicione +echo 'DOMAIN=linegestao.inglinesystems.com.br' >> .env +``` + +Depois suba novamente: + +```bash +docker compose -f docker-compose.domain.yml up -d --build --remove-orphans +``` + +Comandos úteis de diagnóstico: + +```bash +docker compose -f docker-compose.domain.yml logs -f caddy +docker compose -f docker-compose.domain.yml logs -f api +``` + +### Alternativa A (provisória e mais rápida): sem Caddy, expondo API direto + +Se você ainda não tem acesso ao DNS/domínio no Wix, use o arquivo `docker-compose.prod.yml` deste repositório para publicar só a API (porta `8080`) e o banco. + +```bash +cd ~/apps/line-gestao-api + +docker compose -f docker-compose.prod.yml up -d --build +curl -i http://localhost:8080/health +``` + +Teste externo (do seu PC), trocando pelo IP público do servidor: + +```bash +curl -i http://76.13.231.13:8080/health +``` + +> Se o UFW estiver ativo, libere a porta temporariamente: `sudo ufw allow 8080/tcp`. + +### Stack completa (quando domínio/DNS estiver pronto) + ```bash cd ~/apps/line-gestao-api @@ -95,18 +186,122 @@ docker compose logs -f --tail=200 - API respondendo: ```bash -curl -I http://SEU_SERVIDOR:4000 +curl -I http://SEU_SERVIDOR:8080 ``` - Logs do backend: ```bash -docker compose logs backend --tail=200 +docker compose -f docker-compose.prod.yml logs api --tail=200 ``` - Login admin: - Use `SEED_ADMIN_EMAIL` e `SEED_ADMIN_PASSWORD` do `.env`. +### Próximo passo após `HTTP 200` no `/health` + +Se você já recebeu `HTTP/1.1 200 OK` em `http://localhost:8080/health` **e** no IP público (`http://SEU_IP:8080/health`), o back-end está pronto para testes funcionais. + +Checklist recomendado: + +1. Confirmar documentação da API: + +```bash +curl -I http://SEU_IP:8080/swagger +``` + +2. Testar autenticação (via Swagger/UI ou cliente HTTP) com o admin seed. +3. Criar um registro real do seu fluxo principal (ex.: cliente/chamado/ordem) e validar leitura/listagem. +4. Verificar logs enquanto testa: + +```bash +docker compose -f docker-compose.prod.yml logs -f api +``` + +--- + +## 5.1) Como testar o front-end agora (sem DNS) + +Sem Wix/DNS, você ainda consegue testar o front-end por IP público. O essencial é apontar o front para a API externa: + +- API base URL: `http://SEU_IP:8080` + +Se o front for Vite/React (exemplo comum), configure algo como: + +```env +VITE_API_URL=http://SEU_IP:8080 +``` + +Depois gere e publique o front da forma que o repositório dele definir (build estático + servidor web/container). + +Teste final esperado no navegador: + +1. Abrir o front por IP (porta do front). +2. Fazer login com o admin seed. +3. Executar o fluxo principal da aplicação sem erro de CORS ou 401 inesperado. + +> Se houver erro de CORS, ajuste `FRONTEND_PUBLIC_URL` no `.env` da API para a URL exata usada no navegador (incluindo porta), e recrie o container da API. + +## 5.2) Passo a passo (copiar e colar) para subir o front no servidor + +> Objetivo: abrir o front no navegador usando IP público, consumindo a API em `http://SEU_IP:8080`. + +No servidor (SSH), como usuário `deploy`: + +```bash +cd ~/apps/line-gestao-frontend + +# 1) Atualiza código do front +git fetch --all --prune +git pull --ff-only origin + +# 2) Configura URL da API para build de produção (Vite) +cat > .env.production << 'EOF' +VITE_API_URL=http://SEU_IP:8080 +EOF + +# 3) Build do front +npm ci +npm run build + +# 4) Publica build estático do front em container Nginx (porta 4173) +docker rm -f linegestao-frontend 2>/dev/null || true +docker run -d \ + --name linegestao-frontend \ + --restart unless-stopped \ + -p 4173:80 \ + -v "$PWD/dist:/usr/share/nginx/html:ro" \ + nginx:alpine +``` + +Agora teste: + +```bash +# no servidor +curl -I http://localhost:4173 + +# no seu computador +# http://SEU_IP:4173 +``` + +Se a porta 4173 não abrir externamente, libere no firewall: + +```bash +sudo ufw allow 4173/tcp +``` + +### Checklist final no navegador + +1. Abrir `http://SEU_IP:4173`. +2. Fazer login com o usuário admin seed da API. +3. Navegar no fluxo principal (cadastro/listagem/edição) e confirmar persistência. +4. Se aparecer erro de CORS, setar `FRONTEND_PUBLIC_URL=http://SEU_IP:4173` no `.env` da API e recriar o backend: + +```bash +cd ~/apps/line-gestao-api +docker compose -f docker-compose.prod.yml up -d --build +``` + --- ## 6) HTTPS (domínio público) @@ -181,3 +376,36 @@ docker exec -t pg_dump -U > backup.sql ```bash docker compose logs -f --tail=200 ``` + + +## 5.3) Front-end + Back-end no mesmo servidor (com domínio) + +Sim, é possível e esse é o fluxo recomendado quando os repositórios estão em pastas irmãs. + +1. Build do front: + +```bash +cd ~/apps/line-gestao-frontend + +git fetch --all --prune +git pull --ff-only origin + +cat > .env.production << 'EOF' +VITE_API_URL=https://linegestao.inglinesystems.com.br +EOF + +npm ci +npm run build +``` + +2. Subir API + Caddy com HTTPS: + +```bash +cd ~/apps/line-gestao-api +docker compose -f docker-compose.domain.yml up -d --build --remove-orphans +``` + +3. Testar no navegador: +- Front: `https://linegestao.inglinesystems.com.br` +- API health: `https://linegestao.inglinesystems.com.br/health` + diff --git a/docker-compose.domain.yml b/docker-compose.domain.yml new file mode 100644 index 0000000..a1e55aa --- /dev/null +++ b/docker-compose.domain.yml @@ -0,0 +1,65 @@ +services: + api: + build: + context: . + dockerfile: Dockerfile + container_name: linegestao-api + restart: unless-stopped + env_file: + - .env + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + ASPNETCORE_URLS: http://+:8080 + App__UseHttpsRedirection: ${APP_USE_HTTPS_REDIRECTION:-false} + ConnectionStrings__Default: Host=db;Port=5432;Database=${POSTGRES_DB:-linegestao};Username=${POSTGRES_USER:-linegestao_app};Password=${POSTGRES_PASSWORD:-CHANGE_ME} + Jwt__Issuer: ${JWT_ISSUER:-LineGestao} + Jwt__Audience: ${JWT_AUDIENCE:-LineGestao} + Jwt__Key: ${JWT_KEY:?JWT_KEY is required} + Seed__AdminEmail: ${SEED_ADMIN_EMAIL:-admin@linegestao.local} + Seed__AdminPassword: ${SEED_ADMIN_PASSWORD:-CHANGE_ME} + Seed__AdminName: ${SEED_ADMIN_NAME:-Administrador} + Seed__DefaultTenantName: ${SEED_DEFAULT_TENANT_NAME:-Default} + Cors__AllowedOrigins__0: ${FRONTEND_PUBLIC_URL:-https://linegestao.inglinesystems.com.br} + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + container_name: linegestao-db + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-linegestao} + POSTGRES_USER: ${POSTGRES_USER:-linegestao_app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-CHANGE_ME} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-linegestao_app} -d ${POSTGRES_DB:-linegestao}"] + interval: 10s + timeout: 5s + retries: 10 + + caddy: + image: caddy:2-alpine + container_name: linegestao-caddy + restart: unless-stopped + depends_on: + api: + condition: service_started + environment: + DOMAIN: ${DOMAIN:-linegestao.inglinesystems.com.br} + ACME_AGREE: "true" + EMAIL: ${ACME_EMAIL:-} + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + +volumes: + postgres_data: + caddy_data: + caddy_config: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..4c21c3d --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,46 @@ +services: + api: + build: + context: . + dockerfile: Dockerfile + container_name: linegestao-api + restart: unless-stopped + env_file: + - .env + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + ASPNETCORE_URLS: http://+:8080 + App__UseHttpsRedirection: ${APP_USE_HTTPS_REDIRECTION:-false} + ConnectionStrings__Default: Host=db;Port=5432;Database=${POSTGRES_DB:-linegestao};Username=${POSTGRES_USER:-linegestao_app};Password=${POSTGRES_PASSWORD:-CHANGE_ME} + Jwt__Issuer: ${JWT_ISSUER:-LineGestao} + Jwt__Audience: ${JWT_AUDIENCE:-LineGestao} + Jwt__Key: ${JWT_KEY:?JWT_KEY is required} + Seed__AdminEmail: ${SEED_ADMIN_EMAIL:-admin@linegestao.local} + Seed__AdminPassword: ${SEED_ADMIN_PASSWORD:-CHANGE_ME} + Seed__AdminName: ${SEED_ADMIN_NAME:-Administrador} + Seed__DefaultTenantName: ${SEED_DEFAULT_TENANT_NAME:-Default} + Cors__AllowedOrigins__0: ${FRONTEND_PUBLIC_URL:-http://localhost:4200} + depends_on: + db: + condition: service_healthy + ports: + - "8080:8080" + + db: + image: postgres:16-alpine + container_name: linegestao-db + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-linegestao} + POSTGRES_USER: ${POSTGRES_USER:-linegestao_app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-CHANGE_ME} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-linegestao_app} -d ${POSTGRES_DB:-linegestao}"] + interval: 10s + timeout: 5s + retries: 10 + +volumes: + postgres_data: