diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..1a12f45 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,91 @@ +{ + "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 2fbeb51..739bb8b 100644 --- a/.env.example +++ b/.env.example @@ -5,23 +5,33 @@ NEXT_PUBLIC_APP_URL=http://localhost:3000 # Better Auth BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET=change-me-in-prod +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 -# SQLite database (local dev) -DATABASE_URL=file:./prisma/db.dev.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 -# Optional SMTP (dev) -# SMTP_ADDRESS=localhost -# SMTP_PORT=1025 -# SMTP_TLS=false -# SMTP_USERNAME= -# SMTP_PASSWORD= -# SMTP_AUTHENTICATION=login -# SMTP_ENABLE_STARTTLS_AUTO=false -# MAILER_SENDER_EMAIL=no-reply@example.com +# 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 # Dev-only bypass to simplify local testing (do NOT enable in prod) # DEV_BYPASS_AUTH=0 diff --git a/.forgejo/workflows/ci-cd-web-desktop.yml b/.forgejo/workflows/ci-cd-web-desktop.yml new file mode 100644 index 0000000..db80c21 --- /dev/null +++ b/.forgejo/workflows/ci-cd-web-desktop.yml @@ -0,0 +1,492 @@ +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 new file mode 100644 index 0000000..daed18b --- /dev/null +++ b/.forgejo/workflows/quality-checks.yml @@ -0,0 +1,54 @@ +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/ci-cd-web-desktop.yml b/.github/workflows.disabled/ci-cd-web-desktop.yml similarity index 73% rename from .github/workflows/ci-cd-web-desktop.yml rename to .github/workflows.disabled/ci-cd-web-desktop.yml index 3296b69..e95322c 100644 --- a/.github/workflows/ci-cd-web-desktop.yml +++ b/.github/workflows.disabled/ci-cd-web-desktop.yml @@ -25,6 +25,7 @@ jobs: changes: name: Detect changes runs-on: ubuntu-latest + timeout-minutes: 5 outputs: convex: ${{ steps.filter.outputs.convex }} web: ${{ steps.filter.outputs.web }} @@ -52,6 +53,7 @@ jobs: 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 ] @@ -154,11 +156,38 @@ jobs: - 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 "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: | @@ -188,12 +217,27 @@ jobs: restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml', 'bun.lock') }}- - - name: Install and build (Next.js) + - name: Lint check (fail fast before build) run: | cd "$EFFECTIVE_APP_DIR" - bun install --frozen-lockfile --filter '!appsdesktop' - bun run prisma:generate - bun run build:bun + 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: | @@ -218,38 +262,44 @@ jobs: - 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_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: Ensure Convex service envs and restart + - name: Wait for services to be healthy 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 + 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: | @@ -309,14 +359,16 @@ jobs: run: | docker service update --force sistema_web - - name: Restart Convex backend service (optional) - run: | - # Fail the job if the convex backend cannot restart - docker service update --force sistema_convex_backend + # 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 ] @@ -361,11 +413,38 @@ jobs: - 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 "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: | @@ -393,19 +472,22 @@ jobs: -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; \ + -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: Ensure .env is not present for Convex deploy + - 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 @@ -417,7 +499,8 @@ jobs: -e CI=true \ -e CONVEX_SELF_HOSTED_URL \ -e CONVEX_SELF_HOSTED_ADMIN_KEY \ - 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; bunx convex deploy" + -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: | @@ -440,6 +523,7 @@ jobs: desktop_release: name: Desktop Release (Windows) + timeout-minutes: 30 if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: [ self-hosted, windows, desktop ] defaults: @@ -486,6 +570,7 @@ jobs: diagnose_convex: name: Diagnose Convex (env + register test) + timeout-minutes: 10 if: ${{ github.event_name == 'workflow_dispatch' }} runs-on: [ self-hosted, linux, vps ] steps: @@ -499,9 +584,30 @@ jobs: - 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 "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 diff --git a/.github/workflows/desktop-release.yml b/.github/workflows.disabled/desktop-release.yml similarity index 100% rename from .github/workflows/desktop-release.yml rename to .github/workflows.disabled/desktop-release.yml diff --git a/.github/workflows/quality-checks.yml b/.github/workflows.disabled/quality-checks.yml similarity index 95% rename from .github/workflows/quality-checks.yml rename to .github/workflows.disabled/quality-checks.yml index 73446cb..a14a2d7 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows.disabled/quality-checks.yml @@ -48,6 +48,8 @@ jobs: ${{ 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 diff --git a/.gitignore b/.gitignore index 7e0652c..30d6e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,40 @@ -# 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 +# 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 *.sqlite # external experiments nova-calendar-main/ # debug npm-debug.log* -yarn-debug.log* -yarn-error.log* +yarn-debug.log* +yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) @@ -58,3 +62,12 @@ 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 new file mode 100644 index 0000000..bb79ec4 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,29 @@ +# 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/README.md b/README.md index dc5db70..47cd254 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ ## Sistema de Chamados -Aplicação **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better Auth** para gestão de tickets da Rever. A stack ainda inclui **Prisma 6** (SQLite padrão para DEV), **Tailwind** e **Turbopack** em desenvolvimento (o build de produção roda com o webpack padrão do Next). Todo o código-fonte fica na raiz do monorepo seguindo as convenções do App Router. +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. ## 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. ## Configuração rápida @@ -16,7 +17,7 @@ Aplicação **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better ``` 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` (por padrão `file:./db.dev.sqlite`, que mapeia para `prisma/db.dev.sqlite`) + - `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `DATABASE_URL` (PostgreSQL, ex: `postgresql://postgres:dev@localhost:5432/sistema_chamados`) 3. Aplique as migrações e gere o client Prisma: ```bash bunx prisma migrate deploy @@ -30,22 +31,25 @@ Aplicação **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better ### Resetar rapidamente o ambiente local -1. Garanta que `DATABASE_URL` aponte para o arquivo desejado (ex.: `file:./db.dev.sqlite` para desenvolvimento, `file:./db.sqlite` em produção local). -2. Aplique as migrações no arquivo informado: +1. Suba um PostgreSQL local (Docker recomendado): ```bash - DATABASE_URL=file:./db.dev.sqlite bunx prisma migrate deploy + docker run -d --name postgres-dev -p 5432:5432 -e POSTGRES_PASSWORD=dev -e POSTGRES_DB=sistema_chamados postgres:18 ``` -3. Recrie/garanta as contas padrão de login: +2. Aplique as migracoes: ```bash - DATABASE_URL=file:./db.dev.sqlite bun run auth:seed + bunx prisma migrate deploy ``` -4. Suba o servidor normalmente com `bun run dev`. Esses três comandos bastam para reconstruir o ambiente sempre que trocar de computador. +3. Recrie/garanta as contas padrao de login: + ```bash + bun run auth:seed + ``` +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 (Turpoback) com `bun run dev:bun` (`bun run dev:webpack` serve como fallback). +- 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). > 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. @@ -65,20 +69,20 @@ Aplicação **Next.js 16 (App Router)** com **React 19**, **Convex** e **Better ### Guia de DEV (Prisma, Auth e Desktop/Tauri) -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`. +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`. ## 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; `bun run build` mantém o fallback 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 --webpack` (webpack padrão do Next). -- `bun run build:turbopack` — executa `next build --turbopack` para reproduzir/debugar problemas. -- `bun run auth:seed` — atualiza/cria contas padrão do Better Auth (credenciais em `agents.md`). -- `bunx prisma migrate deploy` — aplica migrações ao banco SQLite local. +- `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 @@ -96,7 +100,7 @@ Sem o reset de agente, o Convex reaproveita o token anterior e o inventário con - `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, migrações e banco SQLite (`prisma/db.sqlite`). +- `prisma/` — schema e migracoes do Prisma (PostgreSQL). - `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. @@ -113,7 +117,7 @@ Consulte `PROXIMOS_PASSOS.md` para acompanhar o backlog funcional e o progresso - `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 ", { - previousName: "", - nextName: "Bruno & Co", - }) - - expect(html).toContain("<Ana>") - expect(html).toContain("Bruno & Co") - expect(html).not.toContain("