feat: Agregar configuracion de despliegue con Gitea Actions
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s

- Dockerfile para build de Nuxt 4
- docker-compose.yml con traefik y Authentik
- Workflow de Gitea Actions (single step)
- Hook de Claude Code para monitorear deploys
This commit is contained in:
2025-11-25 10:32:02 -06:00
parent 22614a9a66
commit d694d2f7f0
4 changed files with 348 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
#!/bin/bash
# Monitor Gitea Action after git push
# Este script se ejecuta despues de un git push y espera a que termine la Gitea Action
set -euo pipefail
# Configuracion
GITEA_URL="https://gitea.nucleoriofrio.com"
OWNER="nucleo000"
REPO="printerCentral"
# Intentar cargar el token desde el entorno o desde ~/.bashrc
GITEA_TOKEN="${GITEA_TOKEN:-}"
if [ -z "$GITEA_TOKEN" ] && [ -f "$HOME/.bashrc" ]; then
# Intentar extraer el token de .bashrc
GITEA_TOKEN=$(grep -oP "export GITEA_TOKEN=['\"]?\K[^'\"]*" "$HOME/.bashrc" 2>/dev/null || echo "")
fi
MAX_WAIT_SECONDS=600 # 10 minutos
POLL_INTERVAL=10 # Consultar cada 10 segundos
# Leer el input JSON del hook
INPUT=$(cat)
# Verificar si el comando fue un git push
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if [[ ! "$COMMAND" =~ git[[:space:]]+push ]]; then
# No fue un git push, salir sin hacer nada
exit 0
fi
# Verificar que existe el token
if [ -z "$GITEA_TOKEN" ]; then
cat <<EOF
{
"decision": "block",
"reason": "Git push exitoso, pero no puedo monitorear la Gitea Action: falta GITEA_TOKEN.\n\nConfigura el token en ~/.bashrc o ~/.zshrc:\nexport GITEA_TOKEN='tu_token_aqui'"
}
EOF
exit 0
fi
# Funcion para verificar si las actions estan habilitadas
check_actions_enabled() {
local repo_info=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO")
echo "$repo_info" | jq -r '.has_actions // false'
}
# Funcion para consultar el estado de la ultima action
get_latest_action_status() {
local response=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/actions/tasks?limit=1")
# Verificar si hay un error de permisos
if echo "$response" | jq -e '.message' > /dev/null 2>&1; then
echo "ERROR: $(echo "$response" | jq -r '.message')" >&2
echo ""
return 1
fi
echo "$response" | jq -r '.workflow_runs[0] // .data[0] // empty'
}
# Funcion para formatear el resultado
format_result() {
local status="$1"
local task_data="$2"
local id=$(echo "$task_data" | jq -r '.id // "N/A"')
local workflow_name=$(echo "$task_data" | jq -r '.name // "N/A"')
local workflow_file=$(echo "$task_data" | jq -r '.workflow_id // "N/A"')
local run_number=$(echo "$task_data" | jq -r '.run_number // "N/A"')
local event=$(echo "$task_data" | jq -r '.event // "N/A"')
local branch=$(echo "$task_data" | jq -r '.head_branch // "N/A"')
local title=$(echo "$task_data" | jq -r '.display_title // "N/A"')
local created=$(echo "$task_data" | jq -r '.created_at // "N/A"')
local started=$(echo "$task_data" | jq -r '.run_started_at // .started_at // "N/A"')
local updated=$(echo "$task_data" | jq -r '.updated_at // .stopped_at // "N/A"')
local commit=$(echo "$task_data" | jq -r '.head_sha[0:8] // "N/A"')
local run_url=$(echo "$task_data" | jq -r '.url // ""')
# Calcular duracion si es posible
local duration="N/A"
if [[ "$started" != "N/A" && "$updated" != "N/A" ]]; then
local start_ts=$(date -d "$started" +%s 2>/dev/null || echo "0")
local end_ts=$(date -d "$updated" +%s 2>/dev/null || echo "0")
if [[ $start_ts -gt 0 && $end_ts -gt 0 ]]; then
local diff=$((end_ts - start_ts))
if [[ $diff -lt 60 ]]; then
duration="${diff}s"
else
local mins=$((diff / 60))
local secs=$((diff % 60))
duration="${mins}m ${secs}s"
fi
fi
fi
case "$status" in
success)
local emoji="[OK]"
local msg="EXITOSO"
;;
failure)
local emoji="[ERROR]"
local msg="FALLO"
;;
cancelled)
local emoji="[CANCELADO]"
local msg="CANCELADO"
;;
*)
local emoji="[?]"
local msg="DESCONOCIDO ($status)"
;;
esac
# Construir URL de logs si no esta disponible
if [[ -z "$run_url" || "$run_url" == "null" ]]; then
run_url="$GITEA_URL/$OWNER/$REPO/actions/runs/$id"
fi
cat <<EOF
{
"decision": "block",
"reason": "$emoji Gitea Action completada: $msg\n\nDetalles:\n - Workflow: $workflow_name ($workflow_file)\n - Run #$run_number\n - Evento: $event\n - Branch: $branch\n - Commit: $commit\n - Titulo: $title\n - Duracion: $duration\n - Iniciado: $started\n - Finalizado: $updated\n\nVer logs completos:\n $run_url"
}
EOF
}
# Verificar si las actions estan habilitadas
echo "Verificando si Actions estan habilitadas..." >&2
ACTIONS_ENABLED=$(check_actions_enabled)
if [[ "$ACTIONS_ENABLED" != "true" ]]; then
cat <<EOF
{
"decision": "block",
"reason": "Git push exitoso, pero las Gitea Actions NO estan habilitadas en este repositorio.\n\nPara habilitar Actions:\n1. Ve a: $GITEA_URL/$OWNER/$REPO/settings\n2. Busca la seccion 'Actions' o 'Workflows'\n3. Activa las Actions\n\nLuego podras ver tus workflows en:\n$GITEA_URL/$OWNER/$REPO/actions"
}
EOF
exit 0
fi
# Notificar que empezamos a monitorear
echo "Monitoreando Gitea Action (maximo ${MAX_WAIT_SECONDS}s)..." >&2
# Esperar un poco antes de la primera consulta (dar tiempo a que Gitea cree la action)
sleep 5
# Polling loop
elapsed=0
while [ $elapsed -lt $MAX_WAIT_SECONDS ]; do
# Consultar el estado
TASK_DATA=$(get_latest_action_status)
if [ -z "$TASK_DATA" ]; then
echo "Esperando que Gitea cree la action... (${elapsed}s)" >&2
sleep $POLL_INTERVAL
elapsed=$((elapsed + POLL_INTERVAL))
continue
fi
STATUS=$(echo "$TASK_DATA" | jq -r '.status // "unknown"')
echo "Estado actual: $STATUS (${elapsed}s)" >&2
# Verificar si termino
case "$STATUS" in
success|failure|cancelled)
# Action termino!
format_result "$STATUS" "$TASK_DATA"
exit 0
;;
running|pending|waiting)
# Todavia corriendo
sleep $POLL_INTERVAL
elapsed=$((elapsed + POLL_INTERVAL))
;;
*)
# Estado desconocido
echo "Estado desconocido: $STATUS" >&2
sleep $POLL_INTERVAL
elapsed=$((elapsed + POLL_INTERVAL))
;;
esac
done
# Timeout alcanzado
cat <<EOF
{
"decision": "block",
"reason": "Timeout: La Gitea Action todavia esta corriendo despues de ${MAX_WAIT_SECONDS}s.\n\nVerifica el estado manualmente en:\n$GITEA_URL/$OWNER/$REPO/actions"
}
EOF

View File

@@ -0,0 +1,57 @@
name: build-and-deploy
on:
push:
branches: [ main, master ]
jobs:
build-and-deploy:
runs-on: docker
env:
REG: ${{ vars.REGISTRY_URL }}
REPO_OWNER: ${{ github.repository_owner }}
APP_NAME: ${{ vars.APP_NAME }}
APP_DOMAIN: ${{ vars.APP_DOMAIN }}
# Printer configuration
PRINTER_HOST: ${{ vars.PRINTER_HOST }}
PRINTER_DEVICE_ID: ${{ vars.PRINTER_DEVICE_ID }}
PRINTER_TIMEOUT_MS: ${{ vars.PRINTER_TIMEOUT_MS }}
# Authentik configuration
NUXT_PUBLIC_AUTHENTIK_URL: ${{ vars.NUXT_PUBLIC_AUTHENTIK_URL }}
NUXT_PUBLIC_AUTH_ENABLED: ${{ vars.NUXT_PUBLIC_AUTH_ENABLED }}
steps:
- name: Build, Push and Deploy
run: |
set -e
echo "=== Checkout ==="
git clone --depth 1 ${{ github.server_url }}/${{ github.repository }}.git repo
cd repo
echo "=== Setup Docker Buildx ==="
docker buildx create --use --name builder || docker buildx use builder
echo "=== Login to Registry ==="
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ vars.REGISTRY_URL }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
echo "=== Build and Push Image ==="
docker build -t $REG/${{ github.repository_owner }}/$APP_NAME:${{ github.sha }} -t $REG/${{ github.repository_owner }}/$APP_NAME:latest .
docker push $REG/${{ github.repository_owner }}/$APP_NAME:${{ github.sha }}
docker push $REG/${{ github.repository_owner }}/$APP_NAME:latest
echo "=== Deploy Info ==="
echo "Deploying $APP_NAME"
echo " Domain: $APP_DOMAIN"
echo " Image: $REG/${{ github.repository_owner }}/$APP_NAME:latest"
echo " Network: principal"
echo "=== Pull Fresh Images ==="
docker compose pull || true
echo "=== Clean Up Stack ==="
docker compose --project-name $APP_NAME down || true
echo "=== Update Stack ==="
docker compose --project-name $APP_NAME up -d --remove-orphans --wait
echo "=== Deployment Complete ==="

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
# Install build dependencies
RUN apk add --no-cache python3 make g++
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install --prefer-offline --no-audit
# Copy app source
COPY . ./
# Build the application
RUN npm run build
# Production stage
FROM node:22-alpine
WORKDIR /app
# Copy built application from builder
COPY --from=builder /app/.output /app/.output
# Expose port (internal, no published externally)
EXPOSE 3000
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
# Start the application
CMD ["node", ".output/server/index.mjs"]

57
docker-compose.yml Normal file
View File

@@ -0,0 +1,57 @@
version: '3.8'
services:
nuxt-app:
image: ${REG}/${REPO_OWNER}/${APP_NAME}:latest
container_name: ${APP_NAME}
restart: unless-stopped
environment:
- NODE_ENV=production
# Printer configuration (prefijo NUXT_ para runtime config)
- NUXT_PRINTER_HOST=${PRINTER_HOST:-192.168.87.147}
- NUXT_PRINTER_DEVICE_ID=${PRINTER_DEVICE_ID:-matricial2}
- NUXT_PRINTER_TIMEOUT_MS=${PRINTER_TIMEOUT_MS:-60000}
# Variables originales para compatibilidad
- PRINTER_HOST=${PRINTER_HOST:-192.168.87.147}
- PRINTER_DEVICE_ID=${PRINTER_DEVICE_ID:-matricial2}
- PRINTER_TIMEOUT_MS=${PRINTER_TIMEOUT_MS:-60000}
# Authentik configuration
- NUXT_PUBLIC_AUTHENTIK_URL=${NUXT_PUBLIC_AUTHENTIK_URL:-https://authentik.nucleoriofrio.com}
- NUXT_PUBLIC_AUTH_ENABLED=${NUXT_PUBLIC_AUTH_ENABLED:-false}
networks:
- principal
- traefik-network
labels:
- "traefik.enable=true"
- "traefik.docker.network=principal"
# Service
- "traefik.http.services.${APP_NAME}.loadbalancer.server.port=3000"
# Router para assets estaticos de Nuxt y PWA (sin autenticacion) - mayor prioridad
# Incluye recursos PWA y .well-known para scope extensions
- "traefik.http.routers.${APP_NAME}-public.rule=Host(`${APP_DOMAIN}`) && (PathPrefix(`/_nuxt`) || PathPrefix(`/.well-known`) || PathPrefix(`/icons`) || Path(`/manifest.webmanifest`) || Path(`/sw.js`) || PathPrefix(`/workbox-`) || Path(`/favicon.ico`) || Path(`/offline.html`))"
- "traefik.http.routers.${APP_NAME}-public.entrypoints=websecure"
- "traefik.http.routers.${APP_NAME}-public.tls=true"
- "traefik.http.routers.${APP_NAME}-public.tls.certresolver=letsencrypt"
- "traefik.http.routers.${APP_NAME}-public.service=${APP_NAME}"
- "traefik.http.routers.${APP_NAME}-public.priority=100"
- "traefik.http.routers.${APP_NAME}-public.middlewares=${APP_NAME}-headers"
# Router principal con Authentik Forward Auth (menor prioridad)
- "traefik.http.routers.${APP_NAME}.rule=Host(`${APP_DOMAIN}`)"
- "traefik.http.routers.${APP_NAME}.entrypoints=websecure"
- "traefik.http.routers.${APP_NAME}.tls=true"
- "traefik.http.routers.${APP_NAME}.tls.certresolver=letsencrypt"
- "traefik.http.routers.${APP_NAME}.service=${APP_NAME}"
- "traefik.http.routers.${APP_NAME}.priority=50"
- "traefik.http.routers.${APP_NAME}.middlewares=authentik-forward-auth@file,${APP_NAME}-headers"
# Custom headers middleware
- "traefik.http.middlewares.${APP_NAME}-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
networks:
principal:
external: true
traefik-network:
external: true