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) # 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="" 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: container not ready yet; forcing service restart and sleeping 5s" docker service ps sistema_convex_backend || true docker service update --force sistema_convex_backend || true sleep 5 done CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" if [ -z "$CID" ]; then echo "No live container after restarts — performing hard restart (scale 0/1)" docker service scale sistema_convex_backend=0 || true sleep 5 docker service scale sistema_convex_backend=1 || true sleep 10 CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') fi 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 não encontrado; não foi possível 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 docker service ps sistema_convex_backend || true exit 1 fi if [ -z "$KEY" ]; then 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: Install and build (Next.js) env: PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" run: | cd "$EFFECTIVE_APP_DIR" 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: Ensure SQLite volume ownership (sistema_db) run: | set -e VOLUME_NAME="sistema_sistema_db" if ! docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then echo "Volume $VOLUME_NAME não encontrado; pulando ajuste de permissões" exit 0 fi echo "Ajustando permissões em $VOLUME_NAME..." docker run --rm -v "$VOLUME_NAME":/data alpine:3 sh -lc ' set -e chown -R 1000:1000 /data 2>/dev/null || true chmod -R ug+rwX /data 2>/dev/null || true if [ ! -e /data/db.sqlite ]; then touch /data/db.sqlite chown 1000:1000 /data/db.sqlite 2>/dev/null || true chmod 660 /data/db.sqlite 2>/dev/null || true fi ls -ld /data && ls -l /data/db.sqlite ' echo "Permissões do volume ajustadas com sucesso" - 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 echo "Using APP_DIR (stable)=$APP_DIR_STABLE" 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 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 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 - name: Restart Convex backend service (optional) run: | # Fail the job if the convex backend cannot restart docker service update --force sistema_convex_backend convex_deploy: name: Deploy Convex functions needs: changes # 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="" 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: container not ready yet; sleeping 5s" sleep 5 done CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" if [ -z "$CID" ]; then echo "No live container after restarts — performing hard restart (scale 0/1)" docker service scale sistema_convex_backend=0 || true sleep 5 docker service scale sistema_convex_backend=1 || true sleep 10 CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') fi 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 não encontrado; não foi possível 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 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) 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) 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="" 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: container not ready yet; sleeping 5s" sleep 5 done CONVEX_IMAGE="ghcr.io/get-convex/convex-backend:latest" if [ -z "$CID" ]; then echo "No live container after restarts — performing hard restart (scale 0/1)" docker service scale sistema_convex_backend=0 || true sleep 5 docker service scale sistema_convex_backend=1 || true sleep 10 CID=$(docker ps --format '{{.ID}} {{.Names}}' | awk '/sistema_convex_backend/{print $1; exit}') fi 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 não encontrado; não foi possível 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