diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 1a12f45..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(ssh:*)", - "Bash(bun run lint)", - "Bash(bun run prisma:generate:*)", - "Bash(bun run build:bun:*)", - "WebSearch", - "Bash(bun add:*)", - "Bash(bun run tauri:*)", - "Bash(curl:*)", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\")", - "Bash(findstr:*)", - "Bash(cat:*)", - "Bash(chmod:*)", - "Bash(find:*)", - "Bash(grep:*)", - "WebFetch(domain:medium.com)", - "WebFetch(domain:henrywithu.com)", - "WebFetch(domain:hub.docker.com)", - "Bash(python3:*)", - "WebFetch(domain:www.npmjs.com)", - "WebFetch(domain:docs.strapi.io)", - "Bash(tablename)", - "Bash(\"\"\" OWNER TO renan; FROM pg_tables WHERE schemaname = public;\"\" | docker exec -i c95ebc27eb82 psql -U sistema -d strapi_blog\")", - "Bash(sequence_name)", - "Bash(\"\"\" OWNER TO renan; FROM information_schema.sequences WHERE sequence_schema = public;\"\" | docker exec -i c95ebc27eb82 psql -U sistema -d strapi_blog\")", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(cargo check:*)", - "Bash(bun run:*)", - "Bash(icacls \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\")", - "Bash(copy \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\" \"%TEMP%\\codex_key\")", - "Bash(icacls \"%TEMP%\\codex_key\" /inheritance:r /grant:r \"%USERNAME%:R\")", - "Bash(cmd /c \"echo %TEMP%\")", - "Bash(cmd /c \"dir \"\"%TEMP%\\codex_key\"\"\")", - "Bash(where:*)", - "Bash(ssh-keygen:*)", - "Bash(/c/Program\\ Files/Git/usr/bin/ssh:*)", - "Bash(npx convex deploy:*)", - "Bash(dir \"%LOCALAPPDATA%\\Raven\")", - "Bash(dir \"%APPDATA%\\Raven\")", - "Bash(dir \"%LOCALAPPDATA%\\com.raven.app\")", - "Bash(dir \"%APPDATA%\\com.raven.app\")", - "Bash(tasklist:*)", - "Bash(dir /s /b %LOCALAPPDATA%*raven*)", - "Bash(cmd /c \"tasklist | findstr /i raven\")", - "Bash(cmd /c \"dir /s /b %LOCALAPPDATA%\\*raven* 2>nul\")", - "Bash(powershell -Command \"Get-Process | Where-Object {$_ProcessName -like ''*raven*'' -or $_ProcessName -like ''*appsdesktop*''} | Select-Object ProcessName, Id\")", - "Bash(node:*)", - "Bash(bun scripts/test-all-emails.tsx:*)", - "Bash(bun scripts/send-test-react-email.tsx:*)", - "Bash(dir:*)", - "Bash(git reset:*)", - "Bash(npx convex:*)", - "Bash(bun tsc:*)", - "Bash(scp:*)", - "Bash(docker run:*)", - "Bash(cmd /c \"docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18\")", - "Bash(cmd /c \"docker ps -a --filter name=postgres-dev\")", - "Bash(cmd /c \"docker --version && docker ps -a\")", - "Bash(powershell -Command \"docker --version\")", - "Bash(powershell -Command \"docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18\")", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\" /b)", - "Bash(bunx prisma migrate:*)", - "Bash(bunx prisma db push:*)", - "Bash(bun run auth:seed:*)", - "Bash(set DATABASE_URL=postgresql://postgres:dev@localhost:5432/sistema_chamados:*)", - "Bash(bun tsx:*)", - "Bash(DATABASE_URL=\"postgresql://postgres:dev@localhost:5432/sistema_chamados\" bun tsx:*)", - "Bash(docker stop:*)", - "Bash(docker rm:*)", - "Bash(git commit -m \"$(cat <<''EOF''\nfeat(checklist): exibe descricao do template e do item no ticket\n\n- Adiciona campo templateDescription ao schema do checklist\n- Copia descricao do template ao aplicar checklist no ticket\n- Exibe ambas descricoes na visualizacao do ticket (template em italico)\n- Adiciona documentacao de desenvolvimento local (docs/LOCAL-DEV.md)\n- Corrige prisma-client.mjs para usar PostgreSQL em vez de SQLite\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n)\")", - "Bash(timeout 90 git push:*)", - "Bash(docker ps:*)", - "Bash(docker start:*)", - "Bash(docker inspect:*)", - "Bash(docker exec:*)", - "Bash(timeout 90 git push)", - "Bash(bun test:*)", - "Bash(git restore:*)", - "Bash(cd:*)", - "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\\src\\components\\ui\" /b)", - "Bash(timeout 120 bun:*)", - "Bash(bun run tauri:build:*)", - "Bash(git remote:*)", - "Bash(powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"D:/Projetos IA/sistema-de-chamados/scripts/test-windows-collection.ps1\")" - ] - } -} diff --git a/.env.example b/.env.example index 739bb8b..87e4178 100644 --- a/.env.example +++ b/.env.example @@ -1,38 +1,62 @@ -NODE_ENV=development +# Ambiente local — Sistema de Chamados +# Copie este arquivo para `.env` e preencha os valores sensíveis. +# Nunca faça commit de `.env` com segredos reais. -# Public app URL +# Convex +CONVEX_DEPLOYMENT=anonymous:anonymous-sistema-de-chamados +NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 +CONVEX_SYNC_SECRET=dev-sync-secret + +# Next.js / App URL NEXT_PUBLIC_APP_URL=http://localhost:3000 # Better Auth +# Gere um segredo forte (ex.: `openssl rand -hex 32`) +BETTER_AUTH_SECRET=change-me BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET=your-secret-key-at-least-32-chars-long -# Convex (dev server URL) -NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 -CONVEX_INTERNAL_URL=http://127.0.0.1:3210 -# Intervalo (ms) para aceitar token revogado ao sincronizar acessos remotos (opcional) -REMOTE_ACCESS_TOKEN_GRACE_MS=900000 -# Token interno opcional para o dashboard de saude (/admin/health) e queries internas -INTERNAL_HEALTH_TOKEN=dev-health-token -# Segredo para crons HTTP (reutilize em prod se preferir um unico token) -REPORTS_CRON_SECRET=reports-cron-secret -# Diretório para arquivamento local de tickets (JSONL/backup) -ARCHIVE_DIR=./archives +# Banco de dados (Prisma) +DATABASE_URL=file:./prisma/db.sqlite -# PostgreSQL database (versao 18) -# Para desenvolvimento local, use Docker: -# docker run -d --name postgres-chamados -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18 -DATABASE_URL=postgresql://postgres:dev@localhost:5432/sistema_chamados +# Seeds automáticos (Better Auth) +# Por padrão (true), garantindo apenas existência dos usuários padrão sem resetar senhas +SEED_ENSURE_ONLY=true -# SMTP Configuration (production values in docs/SMTP.md) -SMTP_HOST=smtp.c.inova.com.br -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=envio@rever.com.br -SMTP_PASS=CAAJQm6ZT6AUdhXRTDYu -SMTP_FROM_NAME=Sistema de Chamados -SMTP_FROM_EMAIL=envio@rever.com.br +# Provisionamento e inventário de máquinas +# Segredo obrigatório para registrar/atualizar máquinas (Convex) +MACHINE_PROVISIONING_SECRET=change-me-provisioning +# Tempo de vida do token de máquina (ms) — padrão 30 dias +MACHINE_TOKEN_TTL_MS=2592000000 +# Opcional: segredo dedicado para webhook do FleetDM (senão usa o de provisionamento) +FLEET_SYNC_SECRET= -# Dev-only bypass to simplify local testing (do NOT enable in prod) -# DEV_BYPASS_AUTH=0 -# NEXT_PUBLIC_DEV_BYPASS_AUTH=0 +# SMTP (envio de e-mails) +SMTP_ADDRESS= +SMTP_PORT=465 +SMTP_DOMAIN= +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_AUTHENTICATION=login +SMTP_ENABLE_STARTTLS_AUTO=false +SMTP_TLS=true +MAILER_SENDER_EMAIL="Suporte " + +# Alertas (actions do Convex) +# Hora local (America/Sao_Paulo) para rodar alertas automáticos +ALERTS_LOCAL_HOUR=8 + +# Seeds e sincronizações auxiliares +SYNC_TENANT_ID=tenant-atlas +SYNC_DEFAULT_ASSIGNEE=agent@example.com +SEED_TENANT_ID=tenant-atlas +SEED_ADMIN_PASSWORD=admin123 +SEED_AGENT_PASSWORD=agent123 +SEED_USER_TENANT=tenant-atlas +SEED_USER_EMAIL= +SEED_USER_PASSWORD= +SEED_USER_NAME= +SEED_USER_ROLE=admin + +# Desenvolvimento Desktop (Tauri/Vite) +# Em redes locais, defina o IP do host para HMR. +TAURI_DEV_HOST= diff --git a/.forgejo/workflows/ci-cd-web-desktop.yml b/.forgejo/workflows/ci-cd-web-desktop.yml deleted file mode 100644 index db80c21..0000000 --- a/.forgejo/workflows/ci-cd-web-desktop.yml +++ /dev/null @@ -1,492 +0,0 @@ -name: CI/CD Web + Desktop - -on: - push: - branches: [ main ] - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - force_web_deploy: - description: 'Forcar deploy do Web (ignorar filtro)?' - type: boolean - required: false - default: false - force_convex_deploy: - description: 'Forcar deploy do Convex (ignorar filtro)?' - type: boolean - required: false - default: false - -env: - APP_DIR: /srv/apps/sistema - VPS_UPDATES_DIR: /var/www/updates - -jobs: - changes: - name: Detect changes - runs-on: [ self-hosted, linux, vps ] - timeout-minutes: 5 - outputs: - convex: ${{ steps.filter.outputs.convex }} - web: ${{ steps.filter.outputs.web }} - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - name: Paths filter - id: filter - uses: https://github.com/dorny/paths-filter@v3 - with: - filters: | - convex: - - 'convex/**' - web: - - 'src/**' - - 'public/**' - - 'prisma/**' - - 'next.config.ts' - - 'package.json' - - 'bun.lock' - - 'tsconfig.json' - - 'middleware.ts' - - 'stack.yml' - - deploy: - name: Deploy (VPS Linux) - needs: changes - timeout-minutes: 30 - if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - FALLBACK_DIR="$HOME/apps/web.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Setup Bun - uses: https://github.com/oven-sh/setup-bun@v2 - with: - bun-version: 1.3.4 - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'" - if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then - EXCLUDE_ENV="" - fi - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --filter='protect .env' \ - --filter='protect .env*' \ - --filter='protect apps/desktop/.env*' \ - --filter='protect convex/.env*' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - $EXCLUDE_ENV \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Copy production .env if present - run: | - DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}" - if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then - echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR" - cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env" - fi - - - name: Ensure Next.js cache directory exists and is writable - run: | - cd "$EFFECTIVE_APP_DIR" - mkdir -p .next/cache - chmod -R u+rwX .next || true - - - name: Cache Next.js build cache (.next/cache) - uses: https://github.com/actions/cache@v4 - with: - path: ${{ env.EFFECTIVE_APP_DIR }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-${{ hashFiles('next.config.ts') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}- - ${{ runner.os }}-nextjs- - - - name: Lint check (fail fast before build) - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run lint" - - - name: Install and build (Next.js) - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -e PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING="$PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING" \ - -e NODE_OPTIONS="--max-old-space-size=4096" \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run prisma:generate; bun run build:bun" - - - name: Fix Docker-created file permissions - run: | - # Docker cria arquivos como root - corrigir para o usuario runner (UID 1000) - docker run --rm -v "$EFFECTIVE_APP_DIR":/target alpine:3 \ - chown -R 1000:1000 /target - echo "Permissoes do build corrigidas" - - - name: Atualizar symlink do APP_DIR estavel (deploy atomico) - run: | - set -euo pipefail - ROOT="$HOME/apps" - STABLE_LINK="$ROOT/sistema.current" - - mkdir -p "$ROOT" - - # Sanidade: se esses arquivos nao existirem, o container vai falhar no boot. - test -f "$EFFECTIVE_APP_DIR/scripts/start-web.sh" || { echo "ERROR: scripts/start-web.sh nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; } - test -f "$EFFECTIVE_APP_DIR/stack.yml" || { echo "ERROR: stack.yml nao encontrado em $EFFECTIVE_APP_DIR" >&2; exit 1; } - test -d "$EFFECTIVE_APP_DIR/node_modules" || { echo "ERROR: node_modules nao encontrado em $EFFECTIVE_APP_DIR (necessario para next start)" >&2; exit 1; } - test -d "$EFFECTIVE_APP_DIR/.next" || { echo "ERROR: .next nao encontrado em $EFFECTIVE_APP_DIR (build nao gerado)" >&2; exit 1; } - - PREV="" - if [ -L "$STABLE_LINK" ]; then - PREV="$(readlink -f "$STABLE_LINK" || true)" - fi - echo "PREV_APP_DIR=$PREV" >> "$GITHUB_ENV" - - ln -sfn "$EFFECTIVE_APP_DIR" "$STABLE_LINK" - - # Compat: mantem $HOME/apps/sistema como symlink quando possivel (nao mexe se for pasta). - if [ -L "$ROOT/sistema" ] || [ ! -e "$ROOT/sistema" ]; then - ln -sfn "$STABLE_LINK" "$ROOT/sistema" - fi - - echo "APP_DIR estavel -> $(readlink -f "$STABLE_LINK")" - - - name: Swarm deploy (stack.yml) - run: | - APP_DIR_STABLE="$HOME/apps/sistema.current" - if [ ! -d "$APP_DIR_STABLE" ]; then - echo "ERROR: Stable APP_DIR does not exist: $APP_DIR_STABLE" >&2; exit 1 - fi - cd "$APP_DIR_STABLE" - set -o allexport - if [ -f .env ]; then - echo "Loading .env from $APP_DIR_STABLE" - . ./.env - else - echo "WARNING: No .env found at $APP_DIR_STABLE - stack vars may be empty!" - fi - set +o allexport - echo "Using APP_DIR (stable)=$APP_DIR_STABLE" - echo "NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}" - echo "NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}" - APP_DIR="$APP_DIR_STABLE" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema - - - name: Wait for services to be healthy - run: | - echo "Aguardando servicos ficarem saudaveis..." - for i in $(seq 1 18); do - WEB_STATUS=$(docker service ls --filter "name=sistema_web" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - CONVEX_STATUS=$(docker service ls --filter "name=sistema_convex_backend" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - echo "Tentativa $i/18: web=$WEB_STATUS convex=$CONVEX_STATUS" - if echo "$WEB_STATUS" | grep -q "2/2" && echo "$CONVEX_STATUS" | grep -q "1/1"; then - echo "Todos os servicos estao saudaveis!" - exit 0 - fi - sleep 10 - done - echo "ERRO: Timeout aguardando servicos. Status atual:" - docker service ls --filter "label=com.docker.stack.namespace=sistema" || true - docker service ps sistema_web --no-trunc || true - docker service logs sistema_web --since 5m --raw 2>/dev/null | tail -n 200 || true - - if [ -n "${PREV_APP_DIR:-}" ]; then - echo "Rollback: revertendo APP_DIR estavel para: $PREV_APP_DIR" - ln -sfn "$PREV_APP_DIR" "$HOME/apps/sistema.current" - cd "$HOME/apps/sistema.current" - set -o allexport - if [ -f .env ]; then - . ./.env - fi - set +o allexport - APP_DIR="$HOME/apps/sistema.current" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema || true - fi - - exit 1 - - - name: Cleanup old build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='web.build.*' - ACTIVE="$(readlink -f "$HOME/apps/sistema.current" 2>/dev/null || true)" - echo "Scanning $ROOT for old $PATTERN dirs" - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - if [ -n "$ACTIVE" ] && [ "$(readlink -f "$dir")" = "$ACTIVE" ]; then - echo "Skipping active dir (in use by APP_DIR): $dir"; continue - fi - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - echo "Disk usage (top 10 under $ROOT):" - du -sh "$ROOT"/* 2>/dev/null | sort -rh | head -n 10 || true - - convex_deploy: - name: Deploy Convex functions - needs: changes - timeout-minutes: 20 - if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.convex == 'true' }} - runs-on: [ self-hosted, linux, vps ] - env: - APP_DIR: /srv/apps/sistema - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - FALLBACK_DIR="$HOME/apps/convex.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --exclude '.env*' \ - --exclude 'apps/desktop/.env*' \ - --exclude 'convex/.env*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Bring convex.json from live app if present - run: | - if [ -f "$APP_DIR/convex.json" ]; then - echo "Copying $APP_DIR/convex.json -> $EFFECTIVE_APP_DIR/convex.json" - cp -f "$APP_DIR/convex.json" "$EFFECTIVE_APP_DIR/convex.json" - else - echo "No existing convex.json found at $APP_DIR; convex CLI will need self-hosted vars" - fi - - - name: Set Convex env vars (self-hosted) - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }} - MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }} - FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }} - run: | - set -e - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e MACHINE_PROVISIONING_SECRET \ - -e MACHINE_TOKEN_TTL_MS \ - -e FLEET_SYNC_SECRET \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - - name: Prepare Convex deploy workspace - run: | - cd "$EFFECTIVE_APP_DIR" - if [ -f .env ]; then - echo "Renaming .env -> .env.bak (Convex self-hosted deploy)" - mv -f .env .env.bak - fi - mkdir -p .convex-tmp - - - name: Deploy functions to Convex self-hosted - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CI=true \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; bunx convex deploy" - - - name: Cleanup old convex build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='convex.build.*' - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - - # NOTA: Job comentado porque nao ha runner Windows configurado. - # Descomentar quando configurar um runner com labels: [self-hosted, windows, desktop] - # - # desktop_release: - # name: Desktop Release (Windows) - # timeout-minutes: 30 - # if: ${{ startsWith(github.ref, 'refs/tags/v') }} - # runs-on: [ self-hosted, windows, desktop ] - # defaults: - # run: - # working-directory: apps/desktop - # steps: - # - name: Checkout - # uses: https://github.com/actions/checkout@v4 - # - # - name: Setup pnpm - # uses: https://github.com/pnpm/action-setup@v4 - # with: - # version: 10.20.0 - # - # - name: Setup Node.js - # uses: https://github.com/actions/setup-node@v4 - # with: - # node-version: 20 - # - # - name: Install deps (desktop) - # run: pnpm install --frozen-lockfile - # - # - name: Build with Tauri - # uses: https://github.com/tauri-apps/tauri-action@v0 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - # TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - # with: - # projectPath: apps/desktop - # - # - name: Upload bundles to VPS - # run: | - # # Upload via SCP (configurar chave SSH no runner Windows) - # # scp -r src-tauri/target/release/bundle/* user@vps:/var/www/updates/ - # echo "TODO: Configurar upload para VPS" diff --git a/.forgejo/workflows/quality-checks.yml b/.forgejo/workflows/quality-checks.yml deleted file mode 100644 index daed18b..0000000 --- a/.forgejo/workflows/quality-checks.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Quality Checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint-test-build: - name: Lint, Test and Build - runs-on: [ self-hosted, linux, vps ] - env: - BETTER_AUTH_SECRET: test-secret - NEXT_PUBLIC_APP_URL: http://localhost:3000 - BETTER_AUTH_URL: http://localhost:3000 - NEXT_PUBLIC_CONVEX_URL: http://localhost:3210 - DATABASE_URL: file:./prisma/db.dev.sqlite - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Setup Bun - uses: https://github.com/oven-sh/setup-bun@v2 - with: - bun-version: 1.3.4 - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Cache Next.js build cache - uses: https://github.com/actions/cache@v4 - with: - path: | - ${{ github.workspace }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}-${{ hashFiles('**/*.{js,jsx,ts,tsx}') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }}- - - - name: Generate Prisma client - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: bun run prisma:generate - - - name: Lint - run: bun run lint - - - name: Test - run: bun test - - - name: Build - run: bun run build:bun diff --git a/.github/workflows.disabled/ci-cd-web-desktop.yml b/.github/workflows.disabled/ci-cd-web-desktop.yml deleted file mode 100644 index e95322c..0000000 --- a/.github/workflows.disabled/ci-cd-web-desktop.yml +++ /dev/null @@ -1,639 +0,0 @@ -name: CI/CD Web + Desktop - -on: - push: - branches: [ main ] - tags: - - 'v*.*.*' - workflow_dispatch: - inputs: - force_web_deploy: - description: 'Forçar deploy do Web (ignorar filtro)?' - required: false - default: 'false' - force_convex_deploy: - description: 'Forçar deploy do Convex (ignorar filtro)?' - required: false - default: 'false' - -env: - APP_DIR: /srv/apps/sistema - VPS_UPDATES_DIR: /var/www/updates - RUN_MACHINE_SMOKE: ${{ vars.RUN_MACHINE_SMOKE || secrets.RUN_MACHINE_SMOKE || 'false' }} - -jobs: - changes: - name: Detect changes - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - convex: ${{ steps.filter.outputs.convex }} - web: ${{ steps.filter.outputs.web }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Paths filter - id: filter - uses: dorny/paths-filter@v3 - with: - filters: | - convex: - - 'convex/**' - web: - - 'src/**' - - 'public/**' - - 'prisma/**' - - 'next.config.ts' - - 'package.json' - - 'pnpm-lock.yaml' - - 'tsconfig.json' - - 'middleware.ts' - - 'stack.yml' - - deploy: - name: Deploy (VPS Linux) - needs: changes - timeout-minutes: 30 - # Executa em qualquer push na main (independente do filtro) ou quando disparado manualmente - if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - # Use a web-specific build dir to avoid clashes with convex job - FALLBACK_DIR="$HOME/apps/web.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.20.0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.1 - - - name: Verify Bun runtime - run: bun --version - - - name: Permissions diagnostic (server paths) - run: | - set +e - echo "== Basic context ==" - whoami || true - id || true - groups || true - umask || true - echo "HOME=$HOME" - echo "APP_DIR(default)=${APP_DIR:-/srv/apps/sistema}" - echo "EFFECTIVE_APP_DIR=$EFFECTIVE_APP_DIR" - - echo "\n== Permissions check ==" - check_path() { - P="$1" - echo "-- $P" - if [ -e "$P" ]; then - stat -c '%A %U:%G %n' "$P" 2>/dev/null || ls -ld "$P" || true - echo -n "WRITABLE? "; [ -w "$P" ] && echo yes || echo no - if command -v namei >/dev/null 2>&1; then - namei -l "$P" || true - fi - TMP="$P/.permtest.$$" - (echo test > "$TMP" 2>/dev/null && echo "CREATE_FILE: ok" && rm -f "$TMP") || echo "CREATE_FILE: failed" - else - echo "(missing)" - fi - } - check_path "/srv/apps/sistema" - check_path "/srv/apps/sistema/src/app/machines/handshake" - check_path "/srv/apps/sistema/apps/desktop/node_modules" - check_path "/srv/apps/sistema/node_modules" - check_path "$EFFECTIVE_APP_DIR" - check_path "$EFFECTIVE_APP_DIR/node_modules" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - # Excluir .env apenas quando copiando para o diretório padrão (/srv) para preservar segredos locais - EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'" - if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then - EXCLUDE_ENV="" - fi - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --filter='protect .env' \ - --filter='protect .env*' \ - --filter='protect apps/desktop/.env*' \ - --filter='protect convex/.env*' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - $EXCLUDE_ENV \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - # Nao forca restart - deixa o Swarm gerenciar via health checks - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Copy production .env if present - run: | - DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}" - if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then - echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR" - cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env" - fi - - - name: Prune workspace for server-only build - run: | - cd "$EFFECTIVE_APP_DIR" - # Keep only root (web) as a package in this effective workspace - printf "packages:\n - .\n\nignoredBuiltDependencies:\n - '@prisma/client'\n - '@prisma/engines'\n - '@tailwindcss/oxide'\n - esbuild\n - prisma\n - sharp\n - unrs-resolver\n" > pnpm-workspace.yaml - - - name: Ensure Next.js cache directory exists and is writable - run: | - cd "$EFFECTIVE_APP_DIR" - mkdir -p .next/cache - chmod -R u+rwX .next || true - - - name: Cache Next.js build cache (.next/cache) - uses: actions/cache@v4 - with: - path: ${{ env.EFFECTIVE_APP_DIR }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}-${{ hashFiles('src/**/*.ts', 'src/**/*.tsx', 'src/**/*.js', 'src/**/*.jsx', 'next.config.ts') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}- - - - name: Lint check (fail fast before build) - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run lint" - - - name: Install and build (Next.js) - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: | - cd "$EFFECTIVE_APP_DIR" - docker run --rm \ - -e PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING="$PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING" \ - -e NODE_OPTIONS="--max-old-space-size=4096" \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - sistema_web:node22-bun \ - bash -lc "set -euo pipefail; bun install --frozen-lockfile --filter '!appsdesktop'; bun run prisma:generate; bun run build:bun" - - - name: Publish build to stable APP_DIR directory - run: | - set -e - DEST="$HOME/apps/sistema" - mkdir -p "$DEST" - mkdir -p "$DEST/.next/static" - # One-time fix for old root-owned files (esp. .pnpm-store) left by previous containers - docker run --rm -v "$DEST":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true' || true - # Preserve previously published static assets to keep stale chunks available for clients mid-navigation - if [ -d "$EFFECTIVE_APP_DIR/.next/static" ]; then - rsync -a \ - "$EFFECTIVE_APP_DIR/.next/static/" "$DEST/.next/static/" - fi - # Publish new build; exclude .pnpm-store to avoid Permission denied on old entries - rsync -a --delete \ - --chown=1000:1000 \ - --exclude '.pnpm-store' --exclude '.pnpm-store/**' \ - --exclude '.next/static' \ - "$EFFECTIVE_APP_DIR"/ "$DEST"/ - echo "Published build to: $DEST" - - - name: Swarm deploy (stack.yml) - run: | - APP_DIR_STABLE="$HOME/apps/sistema" - if [ ! -d "$APP_DIR_STABLE" ]; then - echo "ERROR: Stable APP_DIR does not exist: $APP_DIR_STABLE" >&2; exit 1 - fi - cd "$APP_DIR_STABLE" - # Exporta variáveis do .env (do diretório de produção) para substituição no stack - # IMPORTANTE: Usar o .env do APP_DIR_STABLE, não do EFFECTIVE_APP_DIR (build temporário) - set -o allexport - if [ -f .env ]; then - echo "Loading .env from $APP_DIR_STABLE" - . ./.env - else - echo "WARNING: No .env found at $APP_DIR_STABLE - stack vars may be empty!" - fi - set +o allexport - echo "Using APP_DIR (stable)=$APP_DIR_STABLE" - echo "NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}" - echo "NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}" - APP_DIR="$APP_DIR_STABLE" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema - - - name: Wait for services to be healthy - run: | - echo "Aguardando servicos ficarem saudaveis..." - # Aguarda ate 3 minutos (18 tentativas x 10s) pelos servicos - for i in $(seq 1 18); do - WEB_STATUS=$(docker service ls --filter "name=sistema_web" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - CONVEX_STATUS=$(docker service ls --filter "name=sistema_convex_backend" --format "{{.Replicas}}" 2>/dev/null || echo "0/0") - echo "Tentativa $i/18: web=$WEB_STATUS convex=$CONVEX_STATUS" - # Verifica se web tem 2/2 replicas e convex tem 1/1 - if echo "$WEB_STATUS" | grep -q "2/2" && echo "$CONVEX_STATUS" | grep -q "1/1"; then - echo "Todos os servicos estao saudaveis!" - exit 0 - fi - sleep 10 - done - echo "AVISO: Timeout aguardando servicos. Status atual:" - docker service ls --filter "label=com.docker.stack.namespace=sistema" - # Nao falha o deploy, apenas avisa (o Swarm continua o rolling update em background) - - - name: Smoke test — register + heartbeat - run: | - set -e - if [ "${RUN_MACHINE_SMOKE:-false}" != "true" ]; then - echo "RUN_MACHINE_SMOKE != true — pulando smoke test"; exit 0 - fi - # Load MACHINE_PROVISIONING_SECRET from production .env on the host - if [ -f /srv/apps/sistema/.env ]; then - set -o allexport - . /srv/apps/sistema/.env - set +o allexport - fi - if [ -z "${MACHINE_PROVISIONING_SECRET:-}" ]; then - echo "MACHINE_PROVISIONING_SECRET ausente — pulando smoke test"; exit 0 - fi - HOSTNAME_TEST="ci-smoke-$(date +%s)" - BODY='{"provisioningSecret":"'"$MACHINE_PROVISIONING_SECRET"'","tenantId":"tenant-atlas","hostname":"'"$HOSTNAME_TEST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventory":{"cpu":"i7","ramGb":16}},"registeredBy":"ci-smoke"}' - HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$BODY" https://tickets.esdrasrenan.com.br/api/machines/register || true) - echo "Register HTTP=$HTTP" - if [ "$HTTP" != "201" ]; then - echo "Register failed:"; tail -c 600 resp.json || true; exit 1; fi - TOKEN=$(node -e 'try{const j=require("fs").readFileSync("resp.json","utf8");process.stdout.write(JSON.parse(j).machineToken||"");}catch(e){process.stdout.write("")}' ) - if [ -z "$TOKEN" ]; then echo "Missing token in register response"; exit 1; fi - HB=$(curl -sS -o /dev/null -w "%{http_code}" -H 'Content-Type: application/json' -d '{"machineToken":"'"$TOKEN"'","status":"online","metrics":{"cpuPct":5,"memFreePct":70}}' https://tickets.esdrasrenan.com.br/api/machines/heartbeat || true) - echo "Heartbeat HTTP=$HB" - if [ "$HB" != "200" ]; then echo "Heartbeat failed"; exit 1; fi - - - name: Cleanup old build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='web.build.*' - ACTIVE="$HOME/apps/sistema" - echo "Scanning $ROOT for old $PATTERN dirs" - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - if [ -n "$ACTIVE" ] && [ "$(readlink -f "$dir")" = "$ACTIVE" ]; then - echo "Skipping active dir (in use by APP_DIR): $dir"; continue - fi - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - echo "Disk usage (top 10 under $ROOT):" - du -sh "$ROOT"/* 2>/dev/null | sort -rh | head -n 10 || true - - - name: Restart web service with new code (skip — stack deploy already updated) - if: ${{ always() && false }} - run: | - docker service update --force sistema_web - - # Comentado: o stack deploy já atualiza os serviços com update_config.order: start-first - # Forçar update aqui causa downtime porque ignora a estratégia de rolling update - # - name: Restart Convex backend service (optional) - # run: | - # docker service update --force sistema_convex_backend - - convex_deploy: - name: Deploy Convex functions - needs: changes - timeout-minutes: 20 - # Executa quando convex/** mudar ou via workflow_dispatch - if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.convex == 'true' }} - runs-on: [ self-hosted, linux, vps ] - env: - APP_DIR: /srv/apps/sistema - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Determine APP_DIR (fallback safe path) - id: appdir - run: | - TS=$(date +%s) - # Use a convex-specific build dir to avoid clashes with web job - FALLBACK_DIR="$HOME/apps/convex.build.$TS" - mkdir -p "$FALLBACK_DIR" - echo "Using APP_DIR (fallback)=$FALLBACK_DIR" - echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" - - - name: Sync workspace to APP_DIR (preserving local env) - run: | - mkdir -p "$EFFECTIVE_APP_DIR" - RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" - rsync $RSYNC_FLAGS \ - --filter='protect .next.old*' \ - --exclude '.next.old*' \ - --exclude '.env*' \ - --exclude 'apps/desktop/.env*' \ - --exclude 'convex/.env*' \ - --filter='protect node_modules' \ - --filter='protect node_modules/**' \ - --filter='protect .pnpm-store' \ - --filter='protect .pnpm-store/**' \ - --exclude '.git' \ - --exclude '.next' \ - --exclude 'node_modules' \ - --exclude 'node_modules/**' \ - --exclude '.pnpm-store' \ - --exclude '.pnpm-store/**' \ - ./ "$EFFECTIVE_APP_DIR"/ - - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - # Nao forca restart - deixa o Swarm gerenciar via health checks - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - if [ -z "$KEY" ]; then - echo "ERRO: Nao foi possivel obter a chave admin do Convex" - docker service ps sistema_convex_backend || true - exit 1 - fi - - - name: Bring convex.json from live app if present - run: | - if [ -f "$APP_DIR/convex.json" ]; then - echo "Copying $APP_DIR/convex.json -> $EFFECTIVE_APP_DIR/convex.json" - cp -f "$APP_DIR/convex.json" "$EFFECTIVE_APP_DIR/convex.json" - else - echo "No existing convex.json found at $APP_DIR; convex CLI will need self-hosted vars" - fi - - - name: Set Convex env vars (self-hosted) - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }} - MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }} - FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }} - run: | - set -e - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e MACHINE_PROVISIONING_SECRET \ - -e MACHINE_TOKEN_TTL_MS \ - -e FLEET_SYNC_SECRET \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - - name: Prepare Convex deploy workspace - run: | - cd "$EFFECTIVE_APP_DIR" - if [ -f .env ]; then - echo "Renaming .env -> .env.bak (Convex self-hosted deploy)" - mv -f .env .env.bak - fi - # Dedicated tmp dir outside convex/_generated so CLI cleanups don't remove it - mkdir -p .convex-tmp - - name: Deploy functions to Convex self-hosted - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - docker run --rm -i \ - -v "$EFFECTIVE_APP_DIR":/app \ - -w /app \ - -e CI=true \ - -e CONVEX_SELF_HOSTED_URL \ - -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - -e CONVEX_TMPDIR=/app/.convex-tmp \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; export CONVEX_TMPDIR=/app/.convex-tmp; bun install --frozen-lockfile; bunx convex deploy" - - - name: Cleanup old convex build workdirs (keep last 2) - run: | - set -e - ROOT="$HOME/apps" - KEEP=2 - PATTERN='convex.build.*' - LIST=$(find "$ROOT" -maxdepth 1 -type d -name "$PATTERN" | sort -r || true) - echo "$LIST" | sed -n "1,${KEEP}p" | sed 's/^/Keeping: /' || true - echo "$LIST" | sed "1,${KEEP}d" | while read dir; do - [ -z "$dir" ] && continue - echo "Removing $dir" - chmod -R u+rwX "$dir" 2>/dev/null || true - rm -rf "$dir" || { - echo "Local rm failed, falling back to docker (root) cleanup for $dir..." - docker run --rm -v "$dir":/target alpine:3 sh -lc 'chown -R 1000:1000 /target 2>/dev/null || true; chmod -R u+rwX /target 2>/dev/null || true; rm -rf /target/* /target/.[!.]* /target/..?* 2>/dev/null || true' || true - rm -rf "$dir" 2>/dev/null || rmdir "$dir" 2>/dev/null || true - } - done - - desktop_release: - name: Desktop Release (Windows) - timeout-minutes: 30 - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - runs-on: [ self-hosted, windows, desktop ] - defaults: - run: - working-directory: apps/desktop - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.20.0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install deps (desktop) - run: pnpm install --frozen-lockfile - - - name: Build with Tauri - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - with: - projectPath: apps/desktop - - - - name: Upload latest.json + bundles to VPS - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.VPS_HOST }} - username: ${{ secrets.VPS_USER }} - key: ${{ secrets.VPS_SSH_KEY }} - source: | - **/bundle/**/latest.json - **/bundle/**/* - target: ${{ env.VPS_UPDATES_DIR }} - overwrite: true - - diagnose_convex: - name: Diagnose Convex (env + register test) - timeout-minutes: 10 - if: ${{ github.event_name == 'workflow_dispatch' }} - runs-on: [ self-hosted, linux, vps ] - steps: - - name: Print service env and .env subset - run: | - echo "=== Convex service env ===" - docker service inspect sistema_convex_backend --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' || true - echo - echo "=== /srv/apps/sistema/.env subset ===" - [ -f /srv/apps/sistema/.env ] && grep -E '^(MACHINE_PROVISIONING_SECRET|MACHINE_TOKEN_TTL_MS|FLEET_SYNC_SECRET|NEXT_PUBLIC_CONVEX_URL)=' -n /srv/apps/sistema/.env || echo '(no .env)' - - name: Acquire Convex admin key - id: key - run: | - echo "Waiting for Convex container..." - CID="" - # Aguarda ate 60s (12 tentativas x 5s) pelo container ficar pronto - for attempt in $(seq 1 12); do - CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') - if [ -n "$CID" ]; then - echo "Convex container ready (CID=$CID)" - break - fi - echo "Attempt $attempt/12: container not ready yet; waiting 5s..." - sleep 5 - done - CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" - if [ -n "$CID" ]; then - KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "No running convex container detected; attempting offline admin key extraction..." - VOLUME="sistema_convex_data" - if docker volume inspect "$VOLUME" >/dev/null 2>&1; then - KEY=$(docker run --rm --entrypoint /bin/sh -v "$VOLUME":/convex/data "$CONVEX_IMAGE" -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) - else - echo "Volume $VOLUME nao encontrado; nao foi possivel extrair a chave admin" - fi - fi - echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT - echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" - - name: List Convex env and set missing - env: - CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br - ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} - run: | - set -e - if [ -f /srv/apps/sistema/.env ]; then - set -o allexport - . /srv/apps/sistema/.env - set +o allexport - fi - docker run --rm -i \ - -v /srv/apps/sistema:/app -w /app \ - -e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY="$ADMIN_KEY" \ - -e MACHINE_PROVISIONING_SECRET -e MACHINE_TOKEN_TTL_MS -e FLEET_SYNC_SECRET \ - node:20-bullseye bash -lc "set -euo pipefail; curl -fsSL https://bun.sh/install | bash >/tmp/bun-install.log; export BUN_INSTALL=\"\${BUN_INSTALL:-/root/.bun}\"; export PATH=\"\$BUN_INSTALL/bin:\$PATH\"; bun install --frozen-lockfile; \ - unset CONVEX_DEPLOYMENT; bunx convex env list; \ - if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then bunx convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\"; fi; \ - if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then bunx convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\"; fi; \ - if [ -n \"$FLEET_SYNC_SECRET\" ]; then bunx convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\"; fi; \ - bunx convex env list" - - name: Test register from runner - run: | - HOST="vm-teste-$(date +%s)" - DATA='{"provisioningSecret":"'"${MACHINE_PROVISIONING_SECRET:-"71daa9ef54cb224547e378f8121ca898b614446c142a132f73c2221b4d53d7d6"}"'","tenantId":"tenant-atlas","hostname":"'"$HOST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventario":{"cpu":"i7","ramGb":16}},"registeredBy":"diag-test"}' - HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$DATA" https://tickets.esdrasrenan.com.br/api/machines/register || true) - echo "Register HTTP=$HTTP" && tail -c 400 resp.json || true diff --git a/.github/workflows.disabled/quality-checks.yml b/.github/workflows.disabled/quality-checks.yml deleted file mode 100644 index a14a2d7..0000000 --- a/.github/workflows.disabled/quality-checks.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Quality Checks - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint-test-build: - name: Lint, Test and Build - runs-on: ubuntu-latest - env: - BETTER_AUTH_SECRET: test-secret - NEXT_PUBLIC_APP_URL: http://localhost:3000 - BETTER_AUTH_URL: http://localhost:3000 - NEXT_PUBLIC_CONVEX_URL: http://localhost:3210 - DATABASE_URL: file:./prisma/db.dev.sqlite - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.1 - - - name: Verify Bun - run: bun --version - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Cache Next.js build cache - uses: actions/cache@v4 - with: - path: | - ${{ github.workspace }}/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}-${{ hashFiles('**/*.{js,jsx,ts,tsx}') }} - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}- - - - name: Generate Prisma client - env: - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - run: bun run prisma:generate - - - name: Lint - run: bun run lint - - - name: Test - run: bun test - - - name: Build - run: bun run build:bun diff --git a/.github/workflows/ci-cd-web-desktop.yml b/.github/workflows/ci-cd-web-desktop.yml new file mode 100644 index 0000000..84aaa73 --- /dev/null +++ b/.github/workflows/ci-cd-web-desktop.yml @@ -0,0 +1,432 @@ +name: CI/CD Web + Desktop + +on: + push: + branches: [ main ] + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + force_web_deploy: + description: 'Forçar deploy do Web (ignorar filtro)?' + required: false + default: 'false' + force_convex_deploy: + description: 'Forçar deploy do Convex (ignorar filtro)?' + required: false + default: 'false' + +env: + APP_DIR: /srv/apps/sistema + VPS_UPDATES_DIR: /var/www/updates + RUN_MACHINE_SMOKE: ${{ vars.RUN_MACHINE_SMOKE || secrets.RUN_MACHINE_SMOKE || 'false' }} + +jobs: + changes: + name: Detect changes + runs-on: ubuntu-latest + outputs: + convex: ${{ steps.filter.outputs.convex }} + web: ${{ steps.filter.outputs.web }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Paths filter + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + convex: + - 'convex/**' + web: + - 'src/**' + - 'public/**' + - 'prisma/**' + - 'next.config.ts' + - 'package.json' + - 'pnpm-lock.yaml' + - 'tsconfig.json' + - 'middleware.ts' + - 'stack.yml' + + deploy: + name: Deploy (VPS Linux) + needs: changes + # Executa em qualquer push na main (independente do filtro) ou quando disparado manualmente + if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }} + runs-on: [ self-hosted, linux, vps ] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Determine APP_DIR (fallback safe path) + id: appdir + run: | + TS=$(date +%s) + FALLBACK_DIR="$HOME/apps/sistema.build.$TS" + mkdir -p "$FALLBACK_DIR" + echo "Using APP_DIR (fallback)=$FALLBACK_DIR" + echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Permissions diagnostic (server paths) + run: | + set +e + echo "== Basic context ==" + whoami || true + id || true + groups || true + umask || true + echo "HOME=$HOME" + echo "APP_DIR(default)=${APP_DIR:-/srv/apps/sistema}" + echo "EFFECTIVE_APP_DIR=$EFFECTIVE_APP_DIR" + + echo "\n== Permissions check ==" + check_path() { + P="$1" + echo "-- $P" + if [ -e "$P" ]; then + stat -c '%A %U:%G %n' "$P" 2>/dev/null || ls -ld "$P" || true + echo -n "WRITABLE? "; [ -w "$P" ] && echo yes || echo no + if command -v namei >/dev/null 2>&1; then + namei -l "$P" || true + fi + TMP="$P/.permtest.$$" + (echo test > "$TMP" 2>/dev/null && echo "CREATE_FILE: ok" && rm -f "$TMP") || echo "CREATE_FILE: failed" + else + echo "(missing)" + fi + } + check_path "/srv/apps/sistema" + check_path "/srv/apps/sistema/src/app/machines/handshake" + check_path "/srv/apps/sistema/apps/desktop/node_modules" + check_path "/srv/apps/sistema/node_modules" + check_path "$EFFECTIVE_APP_DIR" + check_path "$EFFECTIVE_APP_DIR/node_modules" + + - name: Sync workspace to APP_DIR (preserving local env) + run: | + mkdir -p "$EFFECTIVE_APP_DIR" + RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" + # Excluir .env apenas quando copiando para o diretório padrão (/srv) para preservar segredos locais + EXCLUDE_ENV="--exclude '.env*' --exclude 'apps/desktop/.env*' --exclude 'convex/.env*'" + if [ "$EFFECTIVE_APP_DIR" != "${APP_DIR:-/srv/apps/sistema}" ]; then + EXCLUDE_ENV="" + fi + rsync $RSYNC_FLAGS \ + --filter='protect .next.old*' \ + --exclude '.next.old*' \ + --filter='protect node_modules' \ + --filter='protect node_modules/**' \ + --filter='protect .pnpm-store' \ + --filter='protect .pnpm-store/**' \ + --filter='protect .env' \ + --filter='protect .env*' \ + --filter='protect apps/desktop/.env*' \ + --filter='protect convex/.env*' \ + --exclude '.git' \ + --exclude '.next' \ + --exclude 'node_modules' \ + --exclude 'node_modules/**' \ + --exclude '.pnpm-store' \ + --exclude '.pnpm-store/**' \ + $EXCLUDE_ENV \ + ./ "$EFFECTIVE_APP_DIR"/ + + - name: Copy production .env if present + run: | + DEFAULT_DIR="${APP_DIR:-/srv/apps/sistema}" + if [ "$EFFECTIVE_APP_DIR" != "$DEFAULT_DIR" ] && [ -f "$DEFAULT_DIR/.env" ]; then + echo "Copying production .env from $DEFAULT_DIR to $EFFECTIVE_APP_DIR" + cp -f "$DEFAULT_DIR/.env" "$EFFECTIVE_APP_DIR/.env" + fi + + - name: Prune workspace for server-only build + run: | + cd "$EFFECTIVE_APP_DIR" + # Keep only root (web) as a package in this effective workspace + printf "packages:\n - .\n\nignoredBuiltDependencies:\n - '@prisma/client'\n - '@prisma/engines'\n - '@tailwindcss/oxide'\n - esbuild\n - prisma\n - sharp\n - unrs-resolver\n" > pnpm-workspace.yaml + # Remove desktop app to avoid pnpm touching its node_modules on this runner + rm -rf apps/desktop || true + + - name: Clean Next.js cache (.next) to avoid EACCES + run: | + cd "$EFFECTIVE_APP_DIR" + if [ -e .next ]; then + echo "Removing existing .next (may be root-owned from previous container)" + rm -rf .next || (mv .next ".next.old.$(date +%s)" || true) + fi + mkdir -p .next + chmod -R u+rwX .next || true + + - name: Install and build (Next.js) + run: | + cd "$EFFECTIVE_APP_DIR" + corepack enable || true + pnpm --filter web install --no-frozen-lockfile + pnpm prisma:generate + pnpm build + + - name: Swarm deploy (stack.yml) + run: | + cd "$EFFECTIVE_APP_DIR" + # Exporta variáveis do .env para substituição no stack (ex.: MACHINE_PROVISIONING_SECRET) + set -o allexport + if [ -f .env ]; then . ./.env; fi + set +o allexport + APP_DIR="$EFFECTIVE_APP_DIR" RELEASE_SHA=${{ github.sha }} docker stack deploy --with-registry-auth -c stack.yml sistema + + - name: Ensure Convex service envs and restart + run: | + cd "$EFFECTIVE_APP_DIR" + set -o allexport + if [ -f .env ]; then . ./.env; fi + set +o allexport + echo "Ensuring Convex envs on service: sistema_convex_backend" + if [ -n "${MACHINE_PROVISIONING_SECRET:-}" ]; then + docker service update --env-add MACHINE_PROVISIONING_SECRET="${MACHINE_PROVISIONING_SECRET}" sistema_convex_backend || true + fi + if [ -n "${MACHINE_TOKEN_TTL_MS:-}" ]; then + docker service update --env-add MACHINE_TOKEN_TTL_MS="${MACHINE_TOKEN_TTL_MS}" sistema_convex_backend || true + fi + if [ -n "${FLEET_SYNC_SECRET:-}" ]; then + docker service update --env-add FLEET_SYNC_SECRET="${FLEET_SYNC_SECRET}" sistema_convex_backend || true + fi + echo "Current envs:" + docker service inspect sistema_convex_backend --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' || true + echo "Forcing service restart..." + docker service update --force sistema_convex_backend || true + + - name: Smoke test — register + heartbeat + run: | + set -e + if [ "${RUN_MACHINE_SMOKE:-false}" != "true" ]; then + echo "RUN_MACHINE_SMOKE != true — pulando smoke test"; exit 0 + fi + # Load MACHINE_PROVISIONING_SECRET from production .env on the host + if [ -f /srv/apps/sistema/.env ]; then + set -o allexport + . /srv/apps/sistema/.env + set +o allexport + fi + if [ -z "${MACHINE_PROVISIONING_SECRET:-}" ]; then + echo "MACHINE_PROVISIONING_SECRET ausente — pulando smoke test"; exit 0 + fi + HOSTNAME_TEST="ci-smoke-$(date +%s)" + BODY='{"provisioningSecret":"'"$MACHINE_PROVISIONING_SECRET"'","tenantId":"tenant-atlas","hostname":"'"$HOSTNAME_TEST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventory":{"cpu":"i7","ramGb":16}},"registeredBy":"ci-smoke"}' + HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$BODY" https://tickets.esdrasrenan.com.br/api/machines/register || true) + echo "Register HTTP=$HTTP" + if [ "$HTTP" != "201" ]; then + echo "Register failed:"; tail -c 600 resp.json || true; exit 1; fi + TOKEN=$(node -e 'try{const j=require("fs").readFileSync("resp.json","utf8");process.stdout.write(JSON.parse(j).machineToken||"");}catch(e){process.stdout.write("")}' ) + if [ -z "$TOKEN" ]; then echo "Missing token in register response"; exit 1; fi + HB=$(curl -sS -o /dev/null -w "%{http_code}" -H 'Content-Type: application/json' -d '{"machineToken":"'"$TOKEN"'","status":"online","metrics":{"cpuPct":5,"memFreePct":70}}' https://tickets.esdrasrenan.com.br/api/machines/heartbeat || true) + echo "Heartbeat HTTP=$HB" + if [ "$HB" != "200" ]; then echo "Heartbeat failed"; exit 1; fi + + - name: Cleanup old build workdirs (keep last 3) + run: | + set -e + find "$HOME/apps" -maxdepth 1 -type d -name 'sistema.build.*' | sort -r | tail -n +4 | while read dir; do + echo "Removing $dir" + sudo rm -rf "$dir" + done || true + + - name: Restart web service with new code + run: | + docker service update --force sistema_web || true + + - name: Restart Convex backend service (optional) + run: | + docker service update --force sistema_convex_backend || true + + convex_deploy: + name: Deploy Convex functions + needs: changes + # Executa em workflow_dispatch, push na main, ou quando convex/** mudar + if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || needs.changes.outputs.convex == 'true' }} + runs-on: [ self-hosted, linux, vps ] + env: + APP_DIR: /srv/apps/sistema + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Determine APP_DIR (fallback safe path) + id: appdir + run: | + TS=$(date +%s) + FALLBACK_DIR="$HOME/apps/sistema.build.$TS" + mkdir -p "$FALLBACK_DIR" + echo "Using APP_DIR (fallback)=$FALLBACK_DIR" + echo "EFFECTIVE_APP_DIR=$FALLBACK_DIR" >> "$GITHUB_ENV" + + - name: Sync workspace to APP_DIR (preserving local env) + run: | + mkdir -p "$EFFECTIVE_APP_DIR" + RSYNC_FLAGS="-az --inplace --no-times --no-perms --no-owner --no-group --delete" + rsync $RSYNC_FLAGS \ + --filter='protect .next.old*' \ + --exclude '.next.old*' \ + --exclude '.env*' \ + --exclude 'apps/desktop/.env*' \ + --exclude 'convex/.env*' \ + --filter='protect node_modules' \ + --filter='protect node_modules/**' \ + --filter='protect .pnpm-store' \ + --filter='protect .pnpm-store/**' \ + --exclude '.git' \ + --exclude '.next' \ + --exclude 'node_modules' \ + --exclude 'node_modules/**' \ + --exclude '.pnpm-store' \ + --exclude '.pnpm-store/**' \ + ./ "$EFFECTIVE_APP_DIR"/ + + - name: Set Convex env vars (self-hosted) + env: + CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }} + CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }} + MACHINE_PROVISIONING_SECRET: ${{ secrets.MACHINE_PROVISIONING_SECRET }} + MACHINE_TOKEN_TTL_MS: ${{ secrets.MACHINE_TOKEN_TTL_MS }} + FLEET_SYNC_SECRET: ${{ secrets.FLEET_SYNC_SECRET }} + run: | + set -e + docker run --rm -i \ + -v "$EFFECTIVE_APP_DIR":/app \ + -w /app \ + -e CONVEX_SELF_HOSTED_URL \ + -e CONVEX_SELF_HOSTED_ADMIN_KEY \ + -e MACHINE_PROVISIONING_SECRET \ + -e MACHINE_TOKEN_TTL_MS \ + -e FLEET_SYNC_SECRET \ + node:20-bullseye bash -lc "set -euo pipefail; unset CONVEX_DEPLOYMENT; corepack enable; corepack prepare pnpm@9 --activate; pnpm install --frozen-lockfile --prod=false; \ + if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then pnpm exec convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\" -y; fi; \ + if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then pnpm exec convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\" -y; fi; \ + if [ -n \"$FLEET_SYNC_SECRET\" ]; then pnpm exec convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\" -y; fi; \ + pnpm exec convex env list" + + - name: Ensure .env is not present for Convex deploy + run: | + cd "$EFFECTIVE_APP_DIR" + if [ -f .env ]; then + echo "Renaming .env -> .env.bak (Convex self-hosted deploy)" + mv -f .env .env.bak + fi + - name: Deploy functions to Convex self-hosted + env: + CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }} + CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }} + run: | + docker run --rm -i \ + -v "$EFFECTIVE_APP_DIR":/app \ + -w /app \ + -e CI=true \ + -e CONVEX_SELF_HOSTED_URL \ + -e CONVEX_SELF_HOSTED_ADMIN_KEY \ + node:20-bullseye bash -lc "set -euo pipefail; unset CONVEX_DEPLOYMENT; corepack enable; corepack prepare pnpm@9 --activate; pnpm install --frozen-lockfile --prod=false; pnpm exec convex deploy" + + desktop_release: + name: Desktop Release (Windows) + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: [ self-hosted, windows, desktop ] + defaults: + run: + working-directory: apps/desktop + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install deps (desktop) + run: pnpm install --frozen-lockfile + + - name: Build with Tauri + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} + TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} + with: + projectPath: apps/desktop + + + - name: Upload latest.json + bundles to VPS + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.VPS_HOST }} + username: ${{ secrets.VPS_USER }} + key: ${{ secrets.VPS_SSH_KEY }} + source: | + **/bundle/**/latest.json + **/bundle/**/* + target: ${{ env.VPS_UPDATES_DIR }} + overwrite: true + + diagnose_convex: + name: Diagnose Convex (env + register test) + if: ${{ github.event_name == 'workflow_dispatch' }} + runs-on: [ self-hosted, linux, vps ] + steps: + - name: Print service env and .env subset + run: | + echo "=== Convex service env ===" + docker service inspect sistema_convex_backend --format '{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' || true + echo + echo "=== /srv/apps/sistema/.env subset ===" + [ -f /srv/apps/sistema/.env ] && grep -E '^(MACHINE_PROVISIONING_SECRET|MACHINE_TOKEN_TTL_MS|FLEET_SYNC_SECRET|NEXT_PUBLIC_CONVEX_URL)=' -n /srv/apps/sistema/.env || echo '(no .env)' + - name: Acquire Convex admin key + id: key + run: | + CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') + if [ -z "$CID" ]; then echo "No convex container"; exit 1; fi + KEY=$(docker exec -i "$CID" /bin/sh -lc './generate_admin_key.sh' | tr -d '\r' | grep -o 'convex-self-hosted|[^ ]*' | tail -n1) + echo "ADMIN_KEY=$KEY" >> $GITHUB_OUTPUT + echo "Admin key acquired? $([ -n "$KEY" ] && echo yes || echo no)" + - name: List Convex env and set missing + env: + CONVEX_SELF_HOSTED_URL: https://convex.esdrasrenan.com.br + ADMIN_KEY: ${{ steps.key.outputs.ADMIN_KEY }} + run: | + set -e + if [ -f /srv/apps/sistema/.env ]; then + set -o allexport + . /srv/apps/sistema/.env + set +o allexport + fi + docker run --rm -i \ + -v /srv/apps/sistema:/app -w /app \ + -e CONVEX_SELF_HOSTED_URL -e CONVEX_SELF_HOSTED_ADMIN_KEY="$ADMIN_KEY" \ + -e MACHINE_PROVISIONING_SECRET -e MACHINE_TOKEN_TTL_MS -e FLEET_SYNC_SECRET \ + node:20-bullseye bash -lc "set -euo pipefail; corepack enable; corepack prepare pnpm@9 --activate; pnpm i --frozen-lockfile --prod=false; \ + unset CONVEX_DEPLOYMENT; pnpm exec convex env list; \ + if [ -n \"$MACHINE_PROVISIONING_SECRET\" ]; then pnpm exec convex env set MACHINE_PROVISIONING_SECRET \"$MACHINE_PROVISIONING_SECRET\" -y; fi; \ + if [ -n \"$MACHINE_TOKEN_TTL_MS\" ]; then pnpm exec convex env set MACHINE_TOKEN_TTL_MS \"$MACHINE_TOKEN_TTL_MS\" -y; fi; \ + if [ -n \"$FLEET_SYNC_SECRET\" ]; then pnpm exec convex env set FLEET_SYNC_SECRET \"$FLEET_SYNC_SECRET\" -y; fi; \ + pnpm exec convex env list" + - name: Test register from runner + run: | + HOST="vm-teste-$(date +%s)" + DATA='{"provisioningSecret":"'"${MACHINE_PROVISIONING_SECRET:-"71daa9ef54cb224547e378f8121ca898b614446c142a132f73c2221b4d53d7d6"}"'","tenantId":"tenant-atlas","hostname":"'"$HOST"'","os":{"name":"Linux","version":"6.1.0","architecture":"x86_64"},"macAddresses":["AA:BB:CC:DD:EE:FF"],"serialNumbers":[],"metadata":{"inventario":{"cpu":"i7","ramGb":16}},"registeredBy":"diag-test"}' + HTTP=$(curl -sS -o resp.json -w "%{http_code}" -H 'Content-Type: application/json' -d "$DATA" https://tickets.esdrasrenan.com.br/api/machines/register || true) + echo "Register HTTP=$HTTP" && tail -c 400 resp.json || true diff --git a/.github/workflows.disabled/desktop-release.yml b/.github/workflows/desktop-release.yml similarity index 95% rename from .github/workflows.disabled/desktop-release.yml rename to .github/workflows/desktop-release.yml index d0b3c21..c72241b 100644 --- a/.github/workflows.disabled/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -36,7 +36,7 @@ jobs: node-version: 20 - name: Enable Corepack - run: corepack enable && corepack prepare pnpm@10.20.0 --activate + run: corepack enable && corepack prepare pnpm@9 --activate - name: Install Rust (stable) uses: dtolnay/rust-toolchain@stable diff --git a/.gitignore b/.gitignore index 30d6e0c..fa788b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,34 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# React Email -/.react-email/ -/emails/out/ - -# production -/build - -# misc -.DS_Store -*.pem +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem *.sqlite -# external experiments -nova-calendar-main/ - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) @@ -42,10 +36,6 @@ yarn-error.log* !.env.example !apps/desktop/.env.example -# Accidental Windows duplicate downloads (e.g., "env (1)") -env (*) -env (1) - # vercel .vercel @@ -62,12 +52,3 @@ Screenshot*.png # Ignore NTFS ADS streams accidentally committed from Windows downloads *:*Zone.Identifier *:\:Zone.Identifier -# Infrastructure secrets -.ci.env - -# ferramentas externas -rustdesk/ - -# Prisma generated files -src/generated/ -apps/desktop/service/target/ diff --git a/Dockerfile.prod b/Dockerfile.prod deleted file mode 100644 index bb79ec4..0000000 --- a/Dockerfile.prod +++ /dev/null @@ -1,29 +0,0 @@ -# Runtime image with Node 22 + Bun 1.3.4 and build toolchain preinstalled -FROM node:22-bullseye-slim - -ENV BUN_INSTALL=/root/.bun -ENV PATH="$BUN_INSTALL/bin:$PATH" - -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - gnupg \ - unzip \ - build-essential \ - python3 \ - make \ - pkg-config \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Install Bun 1.3.4 -RUN curl -fsSL https://bun.sh/install \ - | bash -s -- bun-v1.3.4 \ - && ln -sf /root/.bun/bin/bun /usr/local/bin/bun \ - && ln -sf /root/.bun/bin/bun /usr/local/bin/bunx - -WORKDIR /app - -# We'll mount the app code at runtime; image just provides runtimes/toolchains. -CMD ["bash"] diff --git a/docs/PROXIMOS_PASSOS.md b/PROXIMOS_PASSOS.md similarity index 100% rename from docs/PROXIMOS_PASSOS.md rename to PROXIMOS_PASSOS.md diff --git a/README.md b/README.md index 47cd254..cd37367 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,51 @@ ## Sistema de Chamados -Aplicacao **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better Auth** para gestao de tickets da Rever. A stack ainda inclui **Prisma 7** (PostgreSQL), **Tailwind** e **Turbopack** como bundler padrao (webpack permanece disponivel como fallback). Todo o codigo-fonte fica na raiz do monorepo seguindo as convencoes do App Router. +Aplicação Next.js 15 com Convex e Better Auth para gestão de tickets da Rever. Todo o código-fonte está organizado diretamente na raiz do repositório, conforme convenções do Next.js. ## Requisitos -- Bun >= 1.3 (recomendado 1.3.1). Após instalar via script oficial, adicione `export PATH="$HOME/.bun/bin:$PATH"` ao seu shell (ex.: `.bashrc`) para ter `bun` disponível globalmente. -- Node.js >= 20 (necessário para ferramentas auxiliares como Prisma CLI e Next.js em modo fallback). -- CLI do Convex (`bunx convex dev` instalará automaticamente no primeiro uso, se ainda não estiver presente). -- GitHub Actions/autodeploy dependem dessas versões e do CLI do Convex disponível; use `npx convex --help` para confirmar. +- Node.js >= 20 +- pnpm >= 8 +- CLI do Convex (`pnpm dlx convex dev` instalará automaticamente no primeiro uso) ## Configuração rápida 1. Instale as dependências: ```bash - bun install + pnpm install ``` 2. Ajuste o arquivo `.env` (ou crie a partir de `.env.example`) e confirme os valores de: - `NEXT_PUBLIC_CONVEX_URL` (gerado pelo Convex Dev) - - `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `DATABASE_URL` (PostgreSQL, ex: `postgresql://postgres:dev@localhost:5432/sistema_chamados`) + - `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `DATABASE_URL` 3. Aplique as migrações e gere o client Prisma: ```bash - bunx prisma migrate deploy - bun run prisma:generate + pnpm prisma migrate deploy + pnpm prisma:generate ``` 4. Popule usuários padrão do Better Auth: ```bash - bun run auth:seed + pnpm auth:seed ``` - > Sempre que trocar de máquina ou quiser “zerar†o ambiente local, basta repetir os passos 3 e 4 com a mesma `DATABASE_URL`. - -### Resetar rapidamente o ambiente local - -1. Suba um PostgreSQL local (Docker recomendado): +5. (Opcional) Para re-sincronizar manualmente as filas padrão, execute: ```bash - docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18 + pnpm queues:ensure ``` -2. Aplique as migracoes: +6. Em um terminal, execute o backend em tempo real do Convex: ```bash - bunx prisma migrate deploy + pnpm convex:dev ``` -3. Recrie/garanta as contas padrao de login: +7. Em outro terminal, suba o frontend Next.js: ```bash - bun run auth:seed + pnpm dev ``` -4. Suba o servidor normalmente com `bun run dev`. - -### Subir serviços locais - -- (Opcional) Para re-sincronizar manualmente as filas padrão, execute `bun run queues:ensure`. -- Em um terminal, rode o backend em tempo real do Convex com `bun run convex:dev:bun` (ou `bun run convex:dev` para o runtime Node). -- Em outro terminal, suba o frontend Next.js (Turbopack) com `bun run dev:bun` (`bun run dev:webpack` serve como fallback). -- Com o Convex rodando, acesse `http://localhost:3000/dev/seed` uma vez para popular dados de demonstração (tickets, usuários, comentários). +8. Com o Convex ativo, acesse `http://localhost:3000/dev/seed` uma vez para popular dados de demonstração (tickets, usuários, comentários) diretamente no banco do Convex. > Se o CLI perguntar sobre configuração do projeto Convex, escolha criar um novo deployment local (opção padrão) e confirme. As credenciais são armazenadas em `.convex/` automaticamente. -### Documentação -- Ãndice de docs: `docs/README.md` -- Operações (produção): `docs/OPERATIONS.md` (versão EN) e `docs/OPERACAO-PRODUCAO.md` (PT-BR) -- Guia de DEV: `docs/DEV.md` -- Testes automatizados (Vitest/Playwright): `docs/testes-vitest.md` -- Stack Swarm: `stack.yml` (roteado por Traefik, rede `traefik_public`). +### Deploy em produção (Traefik + Convex self‑hosted) +- Guia completo: `docs/OPERACAO-PRODUCAO.md:1`. +- Histórico de setup/decisões: `docs/SETUP-HISTORICO.md:1`. +- Stack Swarm: `stack.yml:1` (roteado por Traefik, rede `traefik_public`). ### Variáveis de ambiente @@ -69,63 +55,32 @@ Aplicacao **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better Au ### Guia de DEV (Prisma, Auth e Desktop/Tauri) -Para fluxos detalhados de desenvolvimento — banco de dados local (PostgreSQL/Prisma), seed do Better Auth, ajustes do Prisma CLI no DEV e build do Desktop (Tauri) — consulte `docs/DEV.md`. +Para fluxos detalhados de desenvolvimento — banco de dados local (SQLite/Prisma), seed do Better Auth, ajustes do Prisma CLI no DEV e build do Desktop (Tauri) — consulte `docs/DEV.md`. ## Scripts úteis -- `bun run dev:bun` — padrão atual para o Next.js com runtime Bun (`bun run dev:webpack` permanece como fallback). -- `bun run convex:dev:bun` — runtime Bun para o Convex (`bun run convex:dev` mantém o fluxo antigo usando Node). -- `bun run build:bun` / `bun run start:bun` — build e serve com Bun usando Turbopack (padrão atual). -- `bun run dev:webpack` — fallback do Next.js em modo desenvolvimento (webpack). -- `bun run lint` — ESLint com as regras do projeto. -- `bun test` — suíte de testes unitários usando o runner do Bun (o teste de screenshot fica automaticamente ignorado se o matcher não existir). -- `bun run build` — executa `next build --turbopack` (runtime Node, caso prefira evitar o `--bun`). -- `bun run build:webpack` — executa `next build --webpack` como fallback oficial. -- `bun run auth:seed` — atualiza/cria contas padrao do Better Auth (credenciais em `agents.md`). -- `bunx prisma migrate deploy` — aplica migracoes ao banco PostgreSQL. -- `bun run convex:dev` — roda o Convex em modo desenvolvimento com Node, gerando tipos em `convex/_generated`. - -## Transferir dispositivo entre colaboradores - -Quando uma dispositivo trocar de responsável: - -1. Abra `Admin > Dispositivos`, selecione o equipamento e clique em **Resetar agente**. -2. No equipamento, execute o reset local do agente (`rever-agent reset` ou reinstale o serviço) e reprovisione com o código da empresa. -3. Após o agente gerar um novo token, associe a dispositivo ao novo colaborador no painel. - -Sem o reset de agente, o Convex reaproveita o token anterior e o inventário continua vinculado ao usuário antigo. +- `pnpm lint` — ESLint com as regras do projeto. +- `pnpm exec vitest run` — suíte de testes unitários. +- `pnpm auth:seed` — atualiza/cria contas padrão do Better Auth (credenciais em `agents.md`). +- `pnpm prisma migrate deploy` — aplica migrações ao banco SQLite local. +- `pnpm convex:dev` — roda o Convex em modo desenvolvimento, gerando tipos em `convex/_generated`. ## Estrutura principal - `app/` dentro de `src/` — rotas e layouts do Next.js (App Router). - `components/` — componentes reutilizáveis (UI, formulários, layouts). - `convex/` — queries, mutations e seeds do Convex. -- `prisma/` — schema e migracoes do Prisma (PostgreSQL). +- `prisma/` — schema, migrações e banco SQLite (`prisma/db.sqlite`). - `scripts/` — utilitários em Node para sincronização e seeds adicionais. - `agents.md` — guia operacional e contexto funcional (em PT-BR). - `PROXIMOS_PASSOS.md` — backlog de melhorias futuras. ## Credenciais de demonstração -Após executar `bun run auth:seed`, as credenciais padrão ficam disponíveis conforme descrito em `agents.md` (seção “Credenciais padrãoâ€). Ajuste variáveis `SEED_USER_*` se precisar sobrepor usuários ou senhas durante o seed. +Após executar `pnpm auth:seed`, as credenciais padrão ficam disponíveis conforme descrito em `agents.md` (seção “Credenciais padrãoâ€). Ajuste variáveis `SEED_USER_*` se precisar sobrepor usuários ou senhas durante o seed. ## Próximos passos Consulte `PROXIMOS_PASSOS.md` para acompanhar o backlog funcional e o progresso das iniciativas planejadas. -### Executar com Bun - -- `bun install` é o fluxo padrão (o arquivo `bun.lock` deve ser versionado; use `bun install --frozen-lockfile` em CI). -- `bun run dev:bun`, `bun run convex:dev:bun`, `bun run build:bun` e `bun run start:bun` já estão configurados; internamente executam `bun run --bun