diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..813726d --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,76 @@ +name: Build y Deploy a Producción + +on: + push: + branches: + - main # O la rama que uses para producción + +jobs: + build: + name: Construir y Subir Imágenes Docker + runs-on: ubuntu-latest + + steps: + - name: Checkout del código + uses: actions/checkout@v4 + + - name: Login al Registro de Gitea + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY_URL }} + # Usamos el token de acceso personal para el login + username: ${{ gitea.actor }} + password: ${{ secrets.ACTIONS_PAT }} + + - name: Construir y Subir API Backend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Backend/GestionIntegral.Api/Dockerfile + push: true + tags: | + ${{ secrets.REGISTRY_URL }}/${{ gitea.repository }}/api:latest + ${{ secrets.REGISTRY_URL }}/${{ gitea.repository }}/api:${{ gitea.sha_short }} + + - name: Construir y Subir Frontend + uses: docker/build-push-action@v5 + with: + # ¡Asegúrate de que esta ruta a tu frontend es correcta! + context: . + file: ./Frontend/Dockerfile + push: true + tags: | + ${{ secrets.REGISTRY_URL }}/${{ gitea.repository }}/frontend:latest + ${{ secrets.REGISTRY_URL }}/${{ gitea.repository }}/frontend:${{ gitea.sha_short }} + + deploy: + name: Desplegar a Producción + runs-on: ubuntu-latest + needs: build + + steps: + - name: Desplegar con Docker Compose + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PROD_SERVER_HOST }} + username: ${{ secrets.PROD_SERVER_USER }} + key: ${{ secrets.PROD_SERVER_SSH_KEY }} + script: | + cd /opt/gestion-integral + + # Pasamos los secretos como variables de entorno + export DB_SA_PASSWORD='${{ secrets.DB_SA_PASSWORD }}' + export JWT_KEY='${{ secrets.JWT_SECRET_KEY }}' + + # Login al registro de Gitea DENTRO del servidor de producción + # Aquí también usamos el ACTIONS_PAT + docker login ${{ secrets.REGISTRY_URL }} -u ${{ gitea.actor }} -p ${{ secrets.ACTIONS_PAT }} + + # Descargamos las nuevas versiones de las imágenes + docker compose pull + + # Levantamos el stack con los cambios + docker compose up -d + + # Limpiamos imágenes viejas que ya no se usan + docker image prune -af \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Dockerfile b/Backend/GestionIntegral.Api/Dockerfile new file mode 100644 index 0000000..4a9f1db --- /dev/null +++ b/Backend/GestionIntegral.Api/Dockerfile @@ -0,0 +1,37 @@ +# --- Etapa 1: Build --- +# Usamos el SDK de .NET 9 (o el que corresponda) para compilar. +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copiamos los archivos de proyecto (.sln y .csproj) y restauramos las dependencias. +# Esto es una optimización de caché de Docker. +COPY GestionIntegralWeb.sln . +COPY Backend/GestionIntegral.Api/GestionIntegral.Api.csproj Backend/GestionIntegral.Api/ + +# Restauramos los paquetes NuGet. +RUN dotnet restore "GestionIntegralWeb.sln" + +# Copiamos todo el resto del código fuente. +COPY . . + +# Nos movemos al directorio del proyecto y lo construimos en modo Release. +WORKDIR "/src/Backend/GestionIntegral.Api" +RUN dotnet build "GestionIntegral.Api.csproj" -c Release -o /app/build + +# --- Etapa 2: Publish --- +# Publicamos la aplicación, lo que genera los artefactos listos para producción. +FROM build AS publish +RUN dotnet publish "GestionIntegral.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false + +# --- Etapa 3: Final --- +# Usamos la imagen de runtime de ASP.NET, que es mucho más ligera que el SDK. +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app +COPY --from=publish /app/publish . + +# El puerto en el que la API escuchará DENTRO del contenedor. +# Usaremos 8080 para evitar conflictos si en el futuro corres algo en el puerto 80. +EXPOSE 8080 + +# El comando para iniciar la API cuando el contenedor arranque. +ENTRYPOINT ["dotnet", "GestionIntegral.Api.dll"] \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/appsettings.json b/Backend/GestionIntegral.Api/appsettings.json index 2e51861..3fcf26c 100644 --- a/Backend/GestionIntegral.Api/appsettings.json +++ b/Backend/GestionIntegral.Api/appsettings.json @@ -6,7 +6,7 @@ } }, "Jwt": { - "Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2", + "Key": "", "Issuer": "GestionIntegralApi", "Audience": "GestionIntegralClient", "DurationInHours": 8 diff --git a/Frontend/Dockerfile b/Frontend/Dockerfile new file mode 100644 index 0000000..81dfe7e --- /dev/null +++ b/Frontend/Dockerfile @@ -0,0 +1,31 @@ +# --- Etapa 1: Build --- +# Usamos una imagen de Node.js para construir los archivos estáticos de React. +FROM node:20-alpine AS build +WORKDIR /app + +# Copiamos los archivos de dependencias y las instalamos. +COPY package.json package-lock.json ./ +RUN npm install + +# Copiamos el resto del código del frontend. +COPY . . + +# Ejecutamos el script de build de Vite, que genera la carpeta 'dist'. +RUN npm run build + +# --- Etapa 2: Serve --- +# Usamos una imagen de Nginx súper ligera para servir los archivos estáticos. +FROM nginx:stable-alpine +WORKDIR /usr/share/nginx/html + +# Eliminamos el index.html por defecto de Nginx. +RUN rm -f index.html + +# Copiamos los archivos construidos desde la etapa anterior a la carpeta que Nginx sirve. +COPY --from=build /app/dist . + +# Nginx por defecto escucha en el puerto 80, así que lo exponemos. +EXPOSE 80 + +# Comando para iniciar Nginx. Esto asegura que se mantenga corriendo. +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5d43c3d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,164 @@ +name: Build y Deploy a Producción + +on: + push: + branches: + - main # Se activa solo con push a la rama 'main' + +# Definimos las variables de entorno para todo el workflow +# Esto hace más fácil cambiar los valores en un solo lugar +env: + # --- VALORES A REEMPLAZAR --- + GITEA_URL: "http://192.168.4.128:3000/" # URL base de tu Gitea + GITEA_REGISTRY_URL: "192.168.4.128:5000" # URL del registro Docker de Gitea (asegúrate de que esté activo) + GITEA_USER: "dmolinari" # Usuario que tiene acceso al repo + PROD_HOST: "192.168.4.128" # IP donde corre Docker + PROD_USER: "root" # ej: 'root' o un usuario del grupo 'docker' + DEPLOY_PATH: "/opt/GestionIntegralWeb" # Ruta en el servidor Debian donde se guardará el docker-compose.yml + DB_SA_PASSWORD: "@gestiones713550@" + JWT_KEY: "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2" + # --- FIN DE VALORES A REEMPLAZAR --- + +jobs: + build-and-push: + name: Construir y Subir Imágenes Docker + runs-on: ubuntu-latest + + steps: + - name: Checkout del código + uses: actions/checkout@v4 + + - name: Obtener SHA del commit + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Login al Registro de Gitea + uses: docker/login-action@v3 + with: + registry: ${{ env.GITEA_REGISTRY_URL }} + username: ${{ env.GITEA_USER }} + # Para el password, necesitas un token de Gitea. + # Lo pondremos aquí, pero idealmente sería un secreto. + password: "OuOCQzb6QV42z9vL9m5rgX2t4xdnZCkXblev0u5O" # Genera un token en Gitea -> Configuración -> Aplicaciones + + - name: Construir y Subir Imagen del Backend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Backend/GestionIntegral.Api/Dockerfile + push: true + tags: | + ${{ env.GITEA_REGISTRY_URL }}/${{ gitea.repository }}/api:latest + ${{ env.GITEA_REGISTRY_URL }}/${{ gitea.repository }}/api:${{ steps.vars.outputs.sha_short }} + + - name: Construir y Subir Imagen del Frontend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Frontend/Dockerfile + push: true + tags: | + ${{ env.GITEA_REGISTRY_URL }}/${{ gitea.repository }}/frontend:latest + ${{ env.GITEA_REGISTRY_URL }}/${{ gitea.repository }}/frontend:${{ steps.vars.outputs.sha_short }} + + deploy: + name: Desplegar a Producción + runs-on: ubuntu-latest + needs: build-and-push + + steps: + - name: Checkout del código + uses: actions/checkout@v4 + + - name: Obtener SHA del commit + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Desplegar con Docker Compose + uses: appleboy/ssh-action@master + with: + host: ${{ env.PROD_HOST }} + username: ${{ env.PROD_USER }} + # La clave SSH privada se pega directamente aquí. + # Es muy importante el formato con la sangría correcta. + key: | + -----BEGIN OPENSSH PRIVATE KEY----- + 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 + -----END OPENSSH PRIVATE KEY----- + script: | + # Nos aseguramos de que el directorio de despliegue exista + mkdir -p ${{ env.DEPLOY_PATH }} + cd ${{ env.DEPLOY_PATH }} + + # Copiamos el archivo docker-compose.yml del repositorio al servidor + # Usamos 'cat' para escribir el contenido en el servidor remoto + # Esto evita tener que hacer checkout del repo en el servidor + cat << 'EOF' > docker-compose.yml + ${{ toJSON(fromJSON(readFile('docker-compose.yml'))) }} + EOF + + # Exportamos las variables para que docker-compose las use + # Estas variables son leídas desde el docker-compose.yml (${VARIABLE}) + export GITEA_REGISTRY=${{ env.GITEA_REGISTRY_URL }} + export GITEA_SHA=${{ steps.vars.outputs.sha_short }} + export DB_SA_PASSWORD=${{ env.DB_SA_PASSWORD }} + export JWT_KEY=${{ env.JWT_KEY }} + + # Autenticamos Docker en el host de producción contra el registro de Gitea + # Esto es necesario para que `docker-compose pull` funcione + docker login ${{ env.GITEA_REGISTRY_URL }} -u ${{ env.GITEA_USER }} -p + + echo "Descargando imágenes nuevas..." + docker-compose -f docker-compose.yml pull + + echo "Levantando la aplicación..." + docker-compose -f docker-compose.yml up -d --remove-orphans + + echo "Limpiando imágenes antiguas sin usar..." + docker image prune -f \ No newline at end of file