Initial commit: Nuxt 4 template with Authentik OAuth

- Add Nuxt 4 application structure
- Add Docker and docker-compose configuration
- Add Gitea Actions CI/CD workflow
- Add Claude Code hooks for action monitoring
This commit is contained in:
2025-10-12 17:09:21 -06:00
commit c794a883fa
17 changed files with 18281 additions and 0 deletions

134
.claude/hooks/README.md Normal file
View File

@@ -0,0 +1,134 @@
# Gitea Actions Monitor Hook
Este hook monitorea automáticamente tus Gitea Actions después de hacer `git push` y te notifica cuando terminan.
## Configuración
### 1. Crear un token de acceso en Gitea
1. Ve a: https://gitea.nucleoriofrio.com/user/settings/applications
2. En la sección **"Generate New Token"**:
- **Token Name**: `claude-code-monitor` (o el nombre que prefieras)
- **Select Permissions**: Marca `read:repository` o `repo` (lectura de repositorio)
3. Click en **"Generate Token"**
4. **IMPORTANTE**: Copia el token inmediatamente (solo se muestra una vez)
### 2. Configurar la variable de entorno
Agrega el token a tu archivo de configuración de shell:
**Para Bash** (`~/.bashrc`):
```bash
export GITEA_TOKEN='tu_token_aqui'
```
**Para Zsh** (`~/.zshrc`):
```bash
export GITEA_TOKEN='tu_token_aqui'
```
**Para Fish** (`~/.config/fish/config.fish`):
```fish
set -x GITEA_TOKEN 'tu_token_aqui'
```
Luego recarga la configuración:
```bash
source ~/.bashrc # o ~/.zshrc o reinicia la terminal
```
### 3. Verificar que funciona
Verifica que la variable está configurada:
```bash
echo $GITEA_TOKEN
```
Deberías ver tu token.
## Uso
Una vez configurado, el hook se activa automáticamente cuando Claude Code ejecuta `git push`:
1. Claude ejecuta `git push`
2. El hook se activa automáticamente
3. **Claude se congela** mientras espera a que termine la Gitea Action (máximo 10 minutos)
4. Puedes presionar **Ctrl+C** para interrumpir la espera si es necesario
5. Cuando termine, Claude te muestra el resultado:
```
✅ Gitea Action completada: EXITOSO
📋 Detalles:
• ID: 123
• Commit: a1b2c3d4
• Iniciado: 2025-10-12T14:30:05Z
• Finalizado: 2025-10-12T14:35:00Z
🔗 Ver en Gitea: https://gitea.nucleoriofrio.com/nucleo000/plantillaNuxtAuthentikProxy/actions
```
## Configuración Avanzada
### Cambiar el timeout
Edita `.claude/settings.local.json` y modifica el valor `timeout` (en segundos):
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/monitor-gitea-action.sh",
"timeout": 300 // 5 minutos en lugar de 10
}
]
}
]
}
}
```
### Cambiar el intervalo de polling
Edita `.claude/hooks/monitor-gitea-action.sh` y modifica:
```bash
POLL_INTERVAL=10 # Consultar cada 10 segundos (puedes cambiar a 5, 15, 30, etc.)
```
## Troubleshooting
### El hook no se activa
1. Verifica que el archivo de configuración es correcto: `cat .claude/settings.local.json`
2. Reinicia Claude Code para que recargue la configuración
3. Revisa los logs con `claude --debug`
### Error "falta GITEA_TOKEN"
El token no está configurado. Sigue los pasos de configuración arriba.
### Timeout: La action todavía está corriendo
La action tardó más de 10 minutos. Puedes:
- Aumentar el `timeout` en la configuración
- Verificar el estado manualmente en Gitea
- Optimizar tu Gitea Action para que sea más rápida
### El script no puede conectarse a la API
1. Verifica que puedes acceder a Gitea: `curl https://gitea.nucleoriofrio.com`
2. Verifica que el token es válido:
```bash
curl -H "Authorization: token $GITEA_TOKEN" \
https://gitea.nucleoriofrio.com/api/v1/repos/nucleo000/plantillaNuxtAuthentikProxy/actions/tasks?limit=1
```
## Desactivar el hook
Si quieres desactivar temporalmente el hook, comenta o elimina la sección `hooks` en `.claude/settings.local.json`.

View File

@@ -0,0 +1,132 @@
#!/bin/bash
# Monitor Gitea Action after git push
# Este script se ejecuta después de un git push y espera a que termine la Gitea Action
set -euo pipefail
# Configuración
GITEA_URL="https://gitea.nucleoriofrio.com"
OWNER="nucleo000"
REPO="plantillaNuxtAuthentikProxy"
GITEA_TOKEN="${GITEA_TOKEN:-}" # Debe estar en variable de entorno
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
# Función para consultar el estado de la última action
get_latest_action_status() {
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/actions/tasks?limit=1" | \
jq -r '.workflow_runs[0] // .data[0] // empty'
}
# Función para formatear el resultado
format_result() {
local status="$1"
local task_data="$2"
local id=$(echo "$task_data" | jq -r '.id // "N/A"')
local created=$(echo "$task_data" | jq -r '.created_at // "N/A"')
local started=$(echo "$task_data" | jq -r '.started_at // "N/A"')
local stopped=$(echo "$task_data" | jq -r '.stopped_at // "N/A"')
local commit=$(echo "$task_data" | jq -r '.head_sha[0:8] // "N/A"')
case "$status" in
success)
local emoji="✅"
local msg="EXITOSO"
;;
failure)
local emoji="❌"
local msg="FALLÓ"
;;
cancelled)
local emoji="🚫"
local msg="CANCELADO"
;;
*)
local emoji="⚠️"
local msg="DESCONOCIDO ($status)"
;;
esac
cat <<EOF
{
"decision": "block",
"reason": "$emoji Gitea Action completada: $msg\n\n📋 Detalles:\n • ID: $id\n • Commit: $commit\n • Iniciado: $started\n • Finalizado: $stopped\n\n🔗 Ver en Gitea: $GITEA_URL/$OWNER/$REPO/actions"
}
EOF
}
# Notificar que empezamos a monitorear
echo "🔄 Monitoreando Gitea Action (máximo ${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 terminó
case "$STATUS" in
success|failure|cancelled)
# Action terminó!
format_result "$STATUS" "$TASK_DATA"
exit 0
;;
running|pending|waiting)
# Todavía 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 todavía está corriendo después de ${MAX_WAIT_SECONDS}s.\n\n🔗 Verifica el estado manualmente en:\n$GITEA_URL/$OWNER/$REPO/actions"
}
EOF

View File

@@ -0,0 +1,62 @@
name: build-and-deploy
on:
push:
branches: [ main, master ]
jobs:
#───────────────── build & push ─────────────────
build:
runs-on: docker
env:
REG: ${{ vars.REGISTRY_URL }}
steps:
- uses: actions/checkout@v3
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
registry: ${{ vars.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build+push plantilla-nuxt-authentik
run: |
cd nuxt4
docker build -t $REG/plantilla-nuxt-authentik:${{ github.sha }} -t $REG/plantilla-nuxt-authentik:latest .
docker push $REG/plantilla-nuxt-authentik:${{ github.sha }}
docker push $REG/plantilla-nuxt-authentik:latest
#───────────────── deploy ─────────────────
deploy:
needs: build
runs-on: docker
env:
REG: ${{ vars.REGISTRY_URL }}
# Variables de entorno para docker-compose
APP_DOMAIN: ${{ vars.APP_DOMAIN }}
NUXT_OAUTH_AUTHENTIK_CLIENT_ID: ${{ secrets.NUXT_OAUTH_AUTHENTIK_CLIENT_ID }}
NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET: ${{ secrets.NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET }}
NUXT_OAUTH_AUTHENTIK_SERVER_URL: ${{ vars.NUXT_OAUTH_AUTHENTIK_SERVER_URL }}
NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL: ${{ vars.NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL }}
NUXT_OAUTH_AUTHENTIK_REDIRECT_URL: ${{ vars.NUXT_OAUTH_AUTHENTIK_REDIRECT_URL }}
NUXT_PUBLIC_APP_URL: ${{ vars.NUXT_PUBLIC_APP_URL }}
NUXT_SESSION_PASSWORD: ${{ secrets.NUXT_SESSION_PASSWORD }}
steps:
- uses: actions/checkout@v3
- name: Login to registry
run: docker login ${{ vars.REGISTRY_URL }} -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_PASSWORD }}
- name: Info about environment
run: |
echo " Deploying Plantilla Nuxt + Authentik Proxy"
echo " Domain: ${{ vars.APP_DOMAIN }}"
echo " Network: principal"
- name: Pull fresh images used in compose
run: docker compose pull
- name: Clean up stack
run: docker compose --project-name plantilla-nuxt-authentik down
- name: Update stack
run: docker compose --project-name plantilla-nuxt-authentik up -d --remove-orphans --wait

41
.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Directorio de configuración local de Claude
.claude/settings.local.json
# Node modules
node_modules/
# Build outputs
dist/
.output/
.nuxt/
.nitro/
# Environment files
.env
.env.*
!.env.example
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Local data
.data/
# Docker
*.dockerignore

56
README.md Normal file
View File

@@ -0,0 +1,56 @@
# Plantilla Nuxt + Authentik Proxy
Plantilla base para aplicaciones Nuxt 4 con autenticación mediante Authentik OAuth.
## Estructura del Proyecto
```
.
├── nuxt4/ # Aplicación Nuxt 4
├── docker-compose.yml # Configuración de despliegue
└── .gitea/ # Gitea Actions CI/CD
```
## Características
- ✅ Nuxt 4
- ✅ Autenticación OAuth con Authentik
- ✅ Docker y Docker Compose
- ✅ CI/CD con Gitea Actions
- ✅ Traefik para proxy reverso y SSL
## Desarrollo Local
```bash
cd nuxt4
npm install
npm run dev
```
## Despliegue
El proyecto incluye Gitea Actions que automáticamente:
1. Construye la imagen Docker
2. La sube al registro
3. Despliega usando docker-compose
### Variables Requeridas en Gitea
**Secrets:**
- `REGISTRY_USERNAME` - Usuario del registro Docker
- `REGISTRY_PASSWORD` - Contraseña del registro Docker
- `NUXT_OAUTH_AUTHENTIK_CLIENT_ID` - Client ID de Authentik
- `NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET` - Client Secret de Authentik
- `NUXT_SESSION_PASSWORD` - Password para sesiones (32+ caracteres)
**Variables:**
- `REGISTRY_URL` - URL del registro Docker
- `APP_DOMAIN` - Dominio de la aplicación
- `NUXT_OAUTH_AUTHENTIK_SERVER_URL` - URL pública de Authentik
- `NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL` - URL interna de Authentik
- `NUXT_OAUTH_AUTHENTIK_REDIRECT_URL` - URL de callback OAuth
- `NUXT_PUBLIC_APP_URL` - URL pública de la app
## Licencia
MIT

33
docker-compose.yml Normal file
View File

@@ -0,0 +1,33 @@
version: '3.8'
services:
app:
image: ${REG}/plantilla-nuxt-authentik:latest
container_name: plantilla-nuxt-authentik
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NUXT_HOST=0.0.0.0
- NUXT_PORT=3000
# OAuth Authentik configuration
- NUXT_OAUTH_AUTHENTIK_CLIENT_ID=${NUXT_OAUTH_AUTHENTIK_CLIENT_ID}
- NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET=${NUXT_OAUTH_AUTHENTIK_CLIENT_SECRET}
- NUXT_OAUTH_AUTHENTIK_SERVER_URL=${NUXT_OAUTH_AUTHENTIK_SERVER_URL}
- NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL=${NUXT_OAUTH_AUTHENTIK_SERVER_URL_INTERNAL}
- NUXT_OAUTH_AUTHENTIK_REDIRECT_URL=${NUXT_OAUTH_AUTHENTIK_REDIRECT_URL}
- NUXT_PUBLIC_APP_URL=${NUXT_PUBLIC_APP_URL}
- NUXT_SESSION_PASSWORD=${NUXT_SESSION_PASSWORD}
networks:
- principal
labels:
- "traefik.enable=true"
- "traefik.http.routers.plantilla-nuxt.rule=Host(`${APP_DOMAIN}`)"
- "traefik.http.routers.plantilla-nuxt.entrypoints=websecure"
- "traefik.http.routers.plantilla-nuxt.tls.certresolver=letsencrypt"
- "traefik.http.services.plantilla-nuxt.loadbalancer.server.port=3000"
networks:
principal:
external: true

24
nuxt4/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

35
nuxt4/Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
# Multi-stage build for Nuxt 4 application
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
# Copy built application from builder stage
COPY --from=builder /app/.output /app/.output
# Expose port
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
ENV NUXT_HOST=0.0.0.0
ENV NUXT_PORT=3000
# Start the application
CMD ["node", ".output/server/index.mjs"]

75
nuxt4/README.md Normal file
View File

@@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

6
nuxt4/app/app.vue Normal file
View File

@@ -0,0 +1,6 @@
<template>
<div>
<NuxtRouteAnnouncer />
<NuxtWelcome />
</div>
</template>

6
nuxt4/eslint.config.mjs Normal file
View File

@@ -0,0 +1,6 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt(
// Your custom configs here
)

13
nuxt4/nuxt.config.ts Normal file
View File

@@ -0,0 +1,13 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: [
'@nuxt/ui',
'@nuxt/test-utils',
'@nuxt/image',
'@nuxt/eslint',
'@nuxt/content'
]
})

17619
nuxt4/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
nuxt4/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "nuxt4",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/content": "^3.7.1",
"@nuxt/eslint": "^1.9.0",
"@nuxt/image": "^1.11.0",
"@nuxt/test-utils": "^3.19.2",
"@nuxt/ui": "^4.0.1",
"better-sqlite3": "^12.4.1",
"eslint": "^9.37.0",
"nuxt": "^4.1.3",
"typescript": "^5.9.3",
"vue": "^3.5.22",
"vue-router": "^4.5.1"
}
}

BIN
nuxt4/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

2
nuxt4/public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

18
nuxt4/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}