Ajustes de CI/CD.
This commit is contained in:
76
.gitea/workflows/deploy.yml
Normal file
76
.gitea/workflows/deploy.yml
Normal file
@@ -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
|
||||||
37
Backend/GestionIntegral.Api/Dockerfile
Normal file
37
Backend/GestionIntegral.Api/Dockerfile
Normal file
@@ -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"]
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
|
"Key": "",
|
||||||
"Issuer": "GestionIntegralApi",
|
"Issuer": "GestionIntegralApi",
|
||||||
"Audience": "GestionIntegralClient",
|
"Audience": "GestionIntegralClient",
|
||||||
"DurationInHours": 8
|
"DurationInHours": 8
|
||||||
|
|||||||
31
Frontend/Dockerfile
Normal file
31
Frontend/Dockerfile
Normal file
@@ -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;"]
|
||||||
164
docker-compose.yml
Normal file
164
docker-compose.yml
Normal file
@@ -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 <PEGA_AQUÍ_TU_TOKEN_DE_GITEA>
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user