sistema-de-chamados/.github/workflows/ci-cd-web-desktop.yml

304 lines
11 KiB
YAML

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
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: 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: 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
if: ${{ env.CONVEX_SELF_HOSTED_URL != '' && env.CONVEX_SELF_HOSTED_ADMIN_KEY != '' }}
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