629 lines
28 KiB
YAML
629 lines
28 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
|
|
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 [ -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 -v "$VOLUME":/convex/data "$CONVEX_IMAGE" /bin/sh -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)
|
|
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 [ -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 -v "$VOLUME":/convex/data "$CONVEX_IMAGE" /bin/sh -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 \
|
|
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; \
|
|
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
|
|
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: 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 \
|
|
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"
|
|
|
|
- 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 [ -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 -v "$VOLUME":/convex/data "$CONVEX_IMAGE" /bin/sh -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
|