feat: Agregar configuracion de despliegue con Gitea Actions
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 2m46s
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:
197
.claude/hooks/monitor-gitea-action.sh
Executable file
197
.claude/hooks/monitor-gitea-action.sh
Executable 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
|
||||||
57
.gitea/workflows/build-and-deploy.yml
Normal file
57
.gitea/workflows/build-and-deploy.yml
Normal 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
37
Dockerfile
Normal 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
57
docker-compose.yml
Normal 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
|
||||||
Reference in New Issue
Block a user