Initial commit: Terraform infrastructure, pipelines, docs and scripts

This commit is contained in:
Evert Daniel Romero Garrido
2026-04-14 14:53:05 -06:00
commit 85297b12a2
31 changed files with 4015 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# Skill Registry — proyectosacc
> Inventario de skills recomendados para el proyecto `proyectosacc`.
> Sub-proyecto de AWS/CI-CD de Cómputo Contable Soft SA de CV.
## Descripción del Proyecto
- **Tipo**: IaC/CD — Despliegue de aplicaciones vía SSH desde Bitbucket Pipelines
- **Target**: VM `172.16.20.208` (SSH, usuario + password)
- **Trigger CI/CD**: Bitbucket Pipelines
- **Componentes**: Pipeline config, deployment scripts, infra docs, operational runbooks
## Skills Principales
### CI/CD y Pipelines
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `cicd-expert` | Pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins). Seguridad, supply chain, GitOps. | Diseño de pipelines, gates de seguridad, troubleshooting CI/CD. |
| `bitbucket-workflow` | Mejores prácticas Bitbucket: PRs, Pipelines, Jira integration. | Trabajo con Bitbucket Cloud, pipelines, integración Atlassian. |
| `bitbucket-server` | Interacción con Bitbucket Server REST API. | Gestión de PRs vía API. |
### Infrastructure as Code (IaC)
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `terraform-best-practices` | Optimización Terraform/OpenTofu. | Código Terraform, IaC. |
| `terraform-specialist` | Terraform avanzado: state management, enterprise patterns. | Infraestructura compleja. |
| `ansible-expert` | Configuration management con Ansible. | Playbooks, roles, inventarios. |
### AWS y Cloud
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `aws-solution-architect` | Arquitecturas AWS serverless, CloudFormation, cost optimization. | Diseño de arquitectura AWS. |
### Documentación y Gestión
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `confluence` | Gestión de documentación en Confluence. | Lectura/escritura de páginas. |
| `jira` | Gestión de issues en Jira. | Tickets, sprints, backlog. |
| `jira-cli` | Jira desde línea de comandos. | Operaciones rápidas de issues. |
| `engram-memory-protocol` | Memoria persistente entre sesiones. | Decisiones, descubrimientos, preferencias. |
### Control de Versiones
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `branch-pr` | Workflow de PRs con sistema issue-first. | Creación de PRs. |
| `issue-creation` | Workflow de issues con sistema issue-first. | Reporte de bugs, features. |
### Diagramas y Visualización
| Skill | Descripción | Trigger |
|-------|-------------|---------|
| `excalidraw-diagram-generator` | Diagramas Excalidraw desde lenguaje natural. | Arquitectura, flujos, mind maps. |
## Convenciones
1. Los secrets se almacenan en Bitbucket Repository Variables o `.env` (nunca en el repo).
2. Pipeline estándar de 7 pasos (image → repo → deps → build → publish → install → deploy).
3. Scripts con `set -euo pipefail` y headers de CCsoft.
4. SSH deployment con usuario `osiris` o `thoth`.
5. Notificaciones vía Telegram (@CCAlertasBot, @CCDevRoBot).
---
*Cómputo Contable Soft SA de CV — Skill Registry proyectosacc — Abril 2026*
+57
View File
@@ -0,0 +1,57 @@
# ===============================================================================
# 🚫 ARCHIVOS Y DIRECTORIOS IGNORADOS — Proyecto proyectosacc
# ===============================================================================
# Variables de entorno y secrets
.env
.env.*
!.env.example
# Terraform
terraform/.terraform/
terraform/*.tfstate
terraform/*.tfstate.*
terraform/*.tfplan
terraform/.terraform.lock.hcl
terraform/crash.log
terraform/crash.*.log
terraform/override.tf
terraform/override.tf.json
terraform/*_override.tf
terraform/*_override.tf.json
# Claves SSH y certificados
*.pem
*.key
*.pub
*.crt
*.p12
*.pfx
# Logs
*.log
logs/
# IDEs y editores
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Dependencias y builds
node_modules/
build/
dist/
.gradle/
gradle/
*.jar
*.war
*.zip
# Archivos temporales
tmp/
temp/
*.tmp
*.bak
+92
View File
@@ -0,0 +1,92 @@
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
"Business Source License" is a trademark of MariaDB Corporation Ab.
Parameters
Licensor: HashiCorp, Inc.
Licensed Work: Terraform Version 1.6.0 or later. The Licensed Work is (c) 2024
HashiCorp, Inc.
Additional Use Grant: You may make production use of the Licensed Work, provided
Your use does not include offering the Licensed Work to third
parties on a hosted or embedded basis in order to compete with
HashiCorp's paid version(s) of the Licensed Work. For purposes
of this license:
A "competitive offering" is a Product that is offered to third
parties on a paid basis, including through paid support
arrangements, that significantly overlaps with the capabilities
of HashiCorp's paid version(s) of the Licensed Work. If Your
Product is not a competitive offering when You first make it
generally available, it will not become a competitive offering
later due to HashiCorp releasing a new version of the Licensed
Work with additional capabilities. In addition, Products that
are not provided on a paid basis are not competitive.
"Product" means software that is offered to end users to manage
in their own environments or offered as a service on a hosted
basis.
"Embedded" means including the source code or executable code
from the Licensed Work in a competitive offering. "Embedded"
also means packaging the competitive offering in such a way
that the Licensed Work must be accessed or downloaded for the
competitive offering to operate.
Hosting or using the Licensed Work(s) for internal purposes
within an organization is not considered a competitive
offering. HashiCorp considers your organization to include all
of your affiliates under common control.
For binding interpretive guidance on using HashiCorp products
under the Business Source License, please visit our FAQ.
(https://www.hashicorp.com/license-faq)
Change Date: Four years from the date the Licensed Work is published.
Change License: MPL 2.0
For information about alternative licensing arrangements for the Licensed Work,
please contact licensing@hashicorp.com.
Notice
Business Source License 1.1
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited production use.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
BIN
View File
Binary file not shown.
+186
View File
@@ -0,0 +1,186 @@
# ===============================================================================================================
# bitbucket-pipelines.yml - Pipeline CI/CD para proyectosacc
# Descripción:
# Pipeline de 7 pasos estándar de CCsoft para desplegar el frontend
# React (S3+CloudFront) y la API backend (EC2) de SACC.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
image: atlassian/default-image:5
definitions:
steps:
- step: &notify-start
name: Notify Start
script:
- export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}"
- bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline de proyectosacc (${BITBUCKET_BRANCH})"
- step: &notify-fail
name: Notify Failure
script:
- export TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}"
- bash ci-cd-commons/telegram_alert.sh "❌ Pipeline de proyectosacc falló en el paso ${BITBUCKET_STEP_KEY}"
pipelines:
default:
- step:
name: 04_build
script:
- set -euo pipefail
- echo "=== Build de proyectosacc (sin deploy) ==="
- npm ci
- npm run build
- ./gradlew clean bootJar
branches:
develop:
- step:
name: 01_image-setup
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk awscli
- mkdir -p ~/.ssh
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- ssh-keyscan -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" "${DEV_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
- export TELEGRAM_BOT_TOKEN="${DEV_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${DEV_TELEGRAM_CHAT_ID}"
- bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline DEV de proyectosacc"
- step:
name: 02_repo-config
script:
- set -euo pipefail
- git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-commons.git" ci-cd-commons
- git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-saac4.git" ci-cd-saac4
- step:
name: 03_dependencies
script:
- set -euo pipefail
- npm ci
- ./gradlew dependencies
- step:
name: 04_build
script:
- set -euo pipefail
- npm ci
- npm run build
- ./gradlew clean bootJar
artifacts:
- build/**
- build/libs/*.jar
- step:
name: 05_publish
script:
- set -euo pipefail
- aws s3 sync build/ "s3://${S3_FRONTEND_BUCKET}/" --delete
- aws s3 cp build/libs/*.jar "s3://${S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar"
- step:
name: 06_install
script:
- set -euo pipefail
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- |
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp s3://${S3_ARTIFACTS_BUCKET}/develop/proyectosacc-app.jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
- step:
name: 07_deploy
script:
- set -euo pipefail
- echo "${DEV_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- |
ssh -p "${DEV_SSH_PORT_PROYECTOSACC:-22}" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${DEV_SERVER_USER_PROYECTOSACC:-thoth}@${DEV_SERVER_IP_PROYECTOSACC}" \
"bash /home/thoth/deploy/setup/deploy.sh"
- aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*"
- bash ci-cd-commons/telegram_alert.sh "✅ Deploy DEV de proyectosacc completado exitosamente"
main:
- step:
name: 01_image-setup
script:
- set -euo pipefail
- apt-get update -y && apt-get install -y openssh-client openjdk-21-jdk awscli
- mkdir -p ~/.ssh
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- ssh-keyscan -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" "${PROD_SERVER_IP_PROYECTOSACC}" >> ~/.ssh/known_hosts 2>/dev/null || true
- export TELEGRAM_BOT_TOKEN="${PROD_TELEGRAM_BOT_TOKEN}"
- export TELEGRAM_CHAT_ID="${PROD_TELEGRAM_CHAT_ID}"
- bash ci-cd-commons/telegram_alert.sh "🚀 Iniciando pipeline PROD de proyectosacc"
- step:
name: 02_repo-config
script:
- set -euo pipefail
- git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-commons.git" ci-cd-commons
- git clone "https://x-token-auth:${BITBUCKET_PASSWORD}@bitbucket.org/ccsoft1/ci-cd-saac4.git" ci-cd-saac4
- step:
name: 03_dependencies
script:
- set -euo pipefail
- npm ci
- ./gradlew dependencies
- step:
name: 04_build
script:
- set -euo pipefail
- npm ci
- npm run build
- ./gradlew clean bootJar
artifacts:
- build/**
- build/libs/*.jar
- step:
name: 05_publish
script:
- set -euo pipefail
- aws s3 sync build/ "s3://${S3_FRONTEND_BUCKET}/" --delete
- aws s3 cp build/libs/*.jar "s3://${S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar"
- step:
name: 06_install
script:
- set -euo pipefail
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- |
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
"bash -c 'mkdir -p /home/thoth/deploy/artifacts/current && aws s3 cp s3://${S3_ARTIFACTS_BUCKET}/main/proyectosacc-app.jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar && chown osiris:osiris /home/thoth/deploy/artifacts/current/proyectosacc-app.jar'"
- step:
name: 07_deploy
script:
- set -euo pipefail
- echo "${PROD_SSH_PRIVATE_KEY_THOTH_PROYECTOSACC}" | base64 -d > ~/.ssh/sacc4_key
- chmod 600 ~/.ssh/sacc4_key
- |
ssh -p "${PROD_SSH_PORT_PROYECTOSACC:-22}" \
-i ~/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
"${PROD_SERVER_USER_PROYECTOSACC:-thoth}@${PROD_SERVER_IP_PROYECTOSACC}" \
"bash /home/thoth/deploy/setup/deploy.sh"
- aws cloudfront create-invalidation --distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" --paths "/*"
- bash ci-cd-commons/telegram_alert.sh "✅ Deploy PROD de proyectosacc completado exitosamente"
+56
View File
@@ -0,0 +1,56 @@
# 01 - ¿Qué es proyectosacc?
> Guía básica para entender el proyecto `proyectosacc` de Cómputo Contable Soft.
---
## 1. ¿Qué es este proyecto?
**proyectosacc** es el sistema de despliegue automático para la aplicación **SACC** (Sistema de Administración Contable y Comercial) de Cómputo Contable Soft.
En palabras simples: es el "puente" que lleva el código de los programadores desde **Bitbucket** hasta el servidor de AWS donde los usuarios pueden usarlo.
El proyecto tiene tres partes principales:
1. **Infraestructura en AWS**: el servidor de la API, la base de datos, el bucket S3 para el frontend, CloudFront y el dominio.
2. **Pipeline de Bitbucket**: el proceso automático que compila y publica tanto el frontend React como la API backend.
3. **Scripts de despliegue**: los archivos que instalan la API en el servidor y suben el frontend a S3.
---
## 2. ¿Qué problema resuelve?
Antes de `proyectosacc`, desplegar la aplicación SACC era un proceso manual y lento:
- Los desarrolladores tenían que copiar archivos a mano.
- Había errores porque olvidaban pasos.
- Era difícil saber qué versión estaba corriendo en producción.
**proyectosacc** resuelve esto automatizando todo:
- Cada vez que un programador sube código a Bitbucket, el pipeline se encarga de compilar, probar y desplegar.
- Si algo falla, se envía una alerta por Telegram.
- La infraestructura se crea con **Terraform**, así que siempre es la misma y no hay "magia" escondida.
---
## 3. ¿Quién lo usa?
| Rol | Cómo interactúa con el proyecto |
|-----|--------------------------------|
| **Desarrolladores** | Suben código a Bitbucket. El pipeline hace el resto. |
| **DevOps / SysAdmin** | Configuran el pipeline, revisan logs y arreglan problemas de infraestructura. |
| **Usuarios finales** | Acceden a `https://sacc.ccsoft.mx` para usar la aplicación. No ven el pipeline, pero se benefician de que siempre hay una versión estable. |
---
## 4. Dato clave
El dominio oficial de la aplicación es:
```
https://sacc.ccsoft.mx
```
Esto significa que cuando el despliegue termina exitosamente, la aplicación está disponible en esa dirección.
---
*Siguiente: [`02-arquitectura-general.md`](02-arquitectura-general.md)*
+151
View File
@@ -0,0 +1,151 @@
# 02 - Arquitectura General
> Cómo se conectan todas las piezas de `proyectosacc`.
---
## 1. Diagrama general
Aquí tienes un diagrama en texto que muestra el flujo completo:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET (usuarios) │
│ ▼ │
│ https://sacc.ccsoft.mx │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ AWS Route 53 │
│ (traduce el dominio al destino correcto) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ AWS CloudFront + ACM (SSL) │
│ (distribuye el frontend globalmente y pone el candadito verde HTTPS) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ S3 Static Website Hosting │
│ (almacena y sirve el frontend React compilado) │
└─────────────────────────────────────────────────────────────────────────┘
FLUJO DE LA API (desde el navegador o la app)
=============================================
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Navegador/app │─────▶│ EC2 T3.small │─────▶│ RDS MariaDB │
│ (llama a /api) │ │ (API backend) │ │ (base de datos)│
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ Nginx Proxy (solo API, puerto 8080)
Servicio systemd
(usuario osiris)
OTRO FLUJO: El pipeline de despliegue
=====================================
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Bitbucket │────▶│ Pipeline │────▶│ S3 Buckets │────▶│ EC2 T3 │
│ (código) │ │ (7 pasos) │ │ (frontend + │ │ (API) │
│ │ │ │ │ artefactos) │ │ │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ ▲
│ │
▼ │
CloudFront invalidation │
(actualiza cache global) │
```
---
## 2. Explicación de cada pieza
> 💡 **Dato clave**: aunque CloudFront sirve principalmente el frontend desde S3, también tiene una regla especial (`/api/*`) que envía las peticiones de la API directamente a la EC2. Así todo el tráfico de `sacc.ccsoft.mx` pasa por un solo punto.
### 🔷 Bitbucket
Es el "banco" donde se guarda el código fuente de SACC.
- Los desarrolladores suben sus cambios aquí.
- Cuando hay un cambio nuevo, Bitbucket "despierta" el pipeline.
### 🔷 Pipeline de Bitbucket (7 pasos)
Es el robot que hace todo el trabajo:
1. Prepara la imagen de compilación.
2. Descarga los repositorios.
3. Instala dependencias.
4. **Compila el frontend React (`npm run build`) y la API backend (`./gradlew bootJar`).**
5. **Sube el frontend al bucket S3 y el artefacto de la API a S3.**
6. Se conecta por SSH al servidor EC2 para desplegar la API.
7. **Invalida la caché de CloudFront, reinicia la API y verifica que todo esté saludable.**
### 🔷 S3 Bucket
Es un "almacén" de AWS con dos trabajos en `proyectosacc`:
1. **Sirve el frontend React** como un sitio web estático.
2. **Guarda los artefactos** de la API generados por el pipeline.
- También sirve como respaldo de versiones anteriores.
### 🔷 CloudFront
Es la red de distribución de contenido (CDN) de AWS.
- **Rol**: Toma los archivos del frontend desde S3 y los distribuye por todo el mundo de forma rápida y segura.
- También maneja el HTTPS con el certificado de ACM.
### 🔷 EC2 T3.small
Es el servidor virtual de AWS donde **solo vive la API/backend**.
- **Tipo**: `t3.small` (2 vCPU, 2 GB RAM).
- **Sistema operativo**: Ubuntu 22.04 LTS.
- **Rol**: Ejecuta el servicio de la API (Java) en el puerto `8080`.
- Nginx en la EC2 solo actúa como proxy hacia la API, **no sirve el frontend React**.
### 🔷 RDS MariaDB
Es la base de datos gestionada por AWS.
- **Tipo**: `db.t3.micro`.
- **Rol**: Guarda toda la información de la aplicación (clientes, facturas, usuarios, etc.).
- La EC2 se conecta a ella por la red interna de AWS.
### 🔷 Route 53
Es el servicio de DNS de AWS.
- **Rol**: Cuando un usuario escribe `sacc.ccsoft.mx`, Route 53 le dice al navegador que debe ir a **CloudFront** (no directamente a la EC2).
- Es como la "guía telefónica" de internet.
### 🔷 ACM (AWS Certificate Manager)
Es el servicio que gestiona los certificados SSL.
- **Rol**: Permite que `https://sacc.ccsoft.mx` funcione con el candadito verde.
- El certificado se adjunta a **CloudFront**, no a Nginx en la EC2.
---
## 3. Integración con proyectos CI/CD compartidos
`proyectosacc` se conecta con otros repositorios de CI/CD de Cómputo Contable Soft que le prestan "herramientas y ladrillos" ya hechos:
- **`ci-cd-commons`**: scripts base, utilerías como `telegram_alert.sh` y `logger_bash.sh`, y buenas prácticas de pipeline.
- **`ci-cd-saac4`**: módulos de Terraform, patrones de infraestructura AWS y scripts de despliegue adaptados para la familia de aplicaciones SACC.
Esto evita reinventar la rueda en cada proyecto y mantiene todo alineado con los estándares de la empresa.
---
## 4. ¿Por qué cada pieza existe?
| Pieza | ¿Por qué la necesitamos? |
|-------|--------------------------|
| Bitbucket | Para guardar el código y activar el pipeline automáticamente. |
| Pipeline | Para que nadie tenga que copiar archivos a mano. |
| S3 | Para servir el frontend React y guardar los artefactos de la API de forma segura. |
| CloudFront | Para distribuir el frontend rápidamente por todo el mundo con HTTPS. |
| EC2 T3 | Es el servidor donde corre la API backend. Sin él, no hay dónde procesar las peticiones de datos. |
| RDS | Es mejor que instalar la base de datos en la misma EC2 porque AWS la respalda, actualiza y monitorea automáticamente. |
| Route 53 | Para que los usuarios no tengan que memorizar una IP numérica. |
| ACM | Para que la conexión sea segura (HTTPS) y proteger los datos de los usuarios. |
| Nginx | Para recibir las peticiones a la API y pasarlas al servicio backend en el puerto 8080. |
---
*Anterior: [`01-que-es-proyectosacc.md`](01-que-es-proyectosacc.md)*
*Siguiente: [`03-infraestructura-aws.md`](03-infraestructura-aws.md)*
+167
View File
@@ -0,0 +1,167 @@
# 03 - Infraestructura AWS
> Descripción detallada de cada recurso de AWS que usa `proyectosacc`.
---
## 1. EC2 T3.small
La **EC2** (Elastic Compute Cloud) es el servidor virtual donde corre la aplicación SACC.
### Especificaciones
- **Tipo de instancia**: `t3.small`
- **vCPU**: 2
- **RAM**: 2 GB
- **Sistema operativo**: Ubuntu 22.04 LTS
- **Disco raíz**: 20 GB SSD (`gp3`), con cifrado obligatorio
### ¿Qué hace?
- **Ejecuta la API/backend de SACC** como un servicio de `systemd` (puerto `8080`).
- Corre **Nginx Proxy** solo para redirigir las peticiones de la API hacia el backend.
- **NO sirve el frontend React** (eso lo hace S3 + CloudFront).
- Se conecta a la base de datos RDS para leer y guardar datos.
- **Solo acepta conexiones SSH con llaves dedicadas**: el pipeline y los administradores usan un par de llaves SSH generado específicamente para `proyectosacc`. No se permite acceso con contraseña.
### Dato importante
En Cómputo Contable Soft hay una regla obligatoria: **todas las instancias EC2 deben ser de la familia T3**. Esto garantiza el mejor balance entre costo y rendimiento.
---
## 2. RDS MariaDB
La **RDS** (Relational Database Service) es la base de datos gestionada por AWS.
### Especificaciones
- **Motor**: MariaDB
- **Tipo de instancia**: `db.t3.micro`
- **vCPU**: 2
- **RAM**: 1 GB
- **Uso**: Base de datos de la aplicación SACC
### ¿Qué hace?
- Guarda todos los datos de la aplicación: usuarios, clientes, productos, facturas, etc.
- AWS se encarga de hacer respaldos automáticos, aplicar parches de seguridad y monitorear el rendimiento.
### Ventaja principal
No tenemos que instalar ni mantener MariaDB nosotros mismos en el servidor. AWS lo hace por nosotros.
---
## 3. S3 Bucket
El **S3** (Simple Storage Service) es el almacén de archivos de AWS.
### ¿Qué guardamos aquí?
1. **El frontend React compilado** (`build/`) como un sitio web estático. CloudFront lee estos archivos para servirlos a los usuarios.
2. Los archivos compilados (artefactos `.jar`) de la API backend.
3. Versiones anteriores por si necesitamos hacer un *rollback*.
4. Logs y respaldos temporales.
### Ejemplo de rutas dentro del bucket
```
s3://ccsoft-proyectosacc-frontend/index.html
s3://ccsoft-artifacts-sacc4/develop/proyectosacc-app-1.0.0.jar
```
### ¿Por qué S3?
- Es muy económico.
- Es duradero (AWS garantiza que no se pierdan los archivos).
- El pipeline puede subir y descargar archivos fácilmente.
---
## 4. CloudFront
**CloudFront** es la red de distribución de contenido (CDN) de AWS.
### ¿Qué hace?
- Distribuye el frontend React desde el bucket S3 a los usuarios de todo el mundo.
- Reduce la latencia porque los archivos se sirven desde el "edge location" más cercano al usuario.
- Termina el cifrado SSL/TLS (HTTPS) usando el certificado de ACM.
- Se conecta a S3 mediante **Origin Access Control (OAC)** para que el bucket no sea público directamente.
### Relación con S3 y la API
CloudFront lee los archivos estáticos (`index.html`, CSS, JS) desde S3. Además, tiene un behavior `/api/*` que redirige las peticiones de la API hacia la EC2. De esta forma, tanto el frontend como el backend comparten el mismo dominio `https://sacc.ccsoft.mx`, pero los usuarios nunca acceden directamente al bucket S3.
---
## 5. Route 53
**Route 53** es el servicio de DNS de AWS.
### Registro configurado
- **Nombre**: `sacc.ccsoft.mx`
- **Tipo**: A (Alias)
- **Destino**: **CloudFront Distribution** (no la IP de la EC2)
### ¿Qué hace?
Cuando un usuario escribe `https://sacc.ccsoft.mx` en su navegador, Route 53 le dice: "ve a CloudFront". Sin Route 53, los usuarios tendrían que escribir una URL larga de CloudFront.
---
## 6. ACM (AWS Certificate Manager)
**ACM** es el servicio que gestiona certificados SSL/TLS.
### ¿Qué es un certificado SSL?
Es un documento digital que permite que la conexión entre el usuario y el servidor esté cifrada. Se ve como el candadito verde 🔒 al lado de la dirección web.
### ¿Cómo lo usamos?
1. Solicitamos un certificado en ACM para el dominio `sacc.ccsoft.mx`.
2. ACM valida que el dominio nos pertenece.
3. El certificado se adjunta a la **distribución de CloudFront**.
4. Los usuarios acceden por **HTTPS** de forma segura. El certificado ya no vive en Nginx de la EC2.
---
## 7. Security Groups
Un **Security Group** es como un "guardia de seguridad" virtual que controla quién puede entrar y salir de la EC2.
### Reglas de entrada (inbound)
| Puerto | Origen | ¿Para qué? |
|--------|--------|-----------|
| `22` | IP del pipeline / VPN | Conexión SSH para despliegues de la API |
| `80` | `0.0.0.0/0` (todo internet) | Tráfico HTTP hacia la API (puede redirigir a HTTPS) |
| `443` | `0.0.0.0/0` (todo internet) | Tráfico HTTPS hacia la API |
| `8080` | IPs de la VPC / CloudFront | Acceso directo a la API backend |
> 💡 **Nota**: los puertos `80` y `443` en la EC2 ya no sirven el frontend React. Solo reciben peticiones a la API. El frontend se sirve desde CloudFront.
### Reglas de salida (outbound)
| Puerto | Destino | ¿Para qué? |
|--------|---------|-----------|
| Todo (`0-65535`) | `0.0.0.0/0` | La EC2 puede salir a internet (descargar paquetes, consultar APIs, etc.) |
### Regla de seguridad importante
El puerto `22` (SSH) **solo debe abrirse desde IPs confiables**. Nunca dejarlo abierto a todo el mundo (`0.0.0.0/0`).
---
## 8. Nginx Proxy en la EC2
**Nginx** es un servidor web que en este proyecto actúa como **proxy inverso exclusivo para la API**.
### ¿Qué hace Nginx?
1. Escucha en los puertos `80` y/o `443`.
2. Recibe las peticiones dirigidas a la API (por ejemplo, `sacc.ccsoft.mx/api`).
3. Redirige el tráfico al backend que corre en el puerto interno `8080`.
4. **NO sirve archivos estáticos del frontend React** (eso es trabajo de S3 + CloudFront).
### Ejemplo mental simple
```
Navegador/app ──▶ Nginx (443) ──▶ API SACC (8080)
```
### ¿Por qué Nginx y no Apache?
En `proyectosacc` se decidió usar **Nginx** porque:
- Consume menos memoria RAM.
- Es más rápido manejando muchas conexiones simultáneas.
- La configuración de proxy inverso es más sencilla.
---
*Anterior: [`02-arquitectura-general.md`](02-arquitectura-general.md)*
*Siguiente: [`04-usuarios-y-permisos.md`](04-usuarios-y-permisos.md)*
+134
View File
@@ -0,0 +1,134 @@
# 04 - Usuarios y Permisos
> Quién puede hacer qué en el servidor de `proyectosacc`.
---
## 1. Usuarios del sistema
En el servidor EC2 de `proyectosacc` existen tres usuarios principales. Cada uno tiene un trabajo específico y **no se mezclan los roles**.
---
### 👤 `ubuntu`
Este es el usuario administrador por defecto de Ubuntu en AWS.
#### ¿Qué puede hacer?
- Todo. Es el usuario con permisos de `sudo` (superusuario).
- Se usa para configurar el servidor por primera vez.
- Puede instalar programas, crear otros usuarios y revisar logs del sistema.
#### ¿Cuándo se usa?
- Durante el setup inicial del servidor.
- Para tareas de mantenimiento que requieren privilegios elevados.
- **No se usa para despliegues automáticos ni para ejecutar la aplicación.**
---
### 👤 `thoth`
Este es el usuario de **CI/CD** (Integración Continua / Despliegue Continuo).
#### ¿Qué puede hacer?
- ✅ Descargar actualizaciones de los repositorios de código (solo lectura).
- ✅ Subir el frontend compilado al bucket S3 (`aws s3 sync`).
- ✅ Mover archivos compilados de la API (binarios, JARs, etc.) a los directorios correctos en la EC2.
- ✅ Hacer copias de seguridad (backups) de los binarios antes de actualizarlos.
- ✅ Ejecutar los scripts de despliegue de la API.
#### ¿Qué NO puede hacer?
- ❌ Ejecutar la aplicación final en producción. Ese trabajo es de `osiris`.
#### ¿Por qué existe?
Para que el pipeline de Bitbucket se conecte por SSH al servidor y haga el despliegue sin necesidad de usar el usuario administrador.
---
### 👤 `osiris`
Este es el usuario de **ejecución** de la aplicación.
#### ¿Qué puede hacer?
- ✅ Iniciar y detener la aplicación SACC.
- ✅ Ejecutar los binarios finales (por ejemplo, archivos `.jar` de Java).
- ✅ Escribir logs en los directorios asignados.
#### ¿Qué NO puede hacer?
- ❌ Realizar despliegues.
- ❌ Mover archivos de instalación.
- ❌ Acceder a repositorios de código.
#### ¿Por qué existe?
Por seguridad. Si alguien compromete la aplicación, el atacante solo tendría los permisos de `osiris`, que son muy limitados.
---
## 2. Separación de privilegios
Este modelo se llama **SoD** (Segregation of Duties) o "Separación de Funciones".
La idea es simple: **una sola persona (o usuario) no debe poder hacer todo**.
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ thoth │ │ osiris │ │ ubuntu │
│ (CI/CD) │ │ (ejecución) │ │ (admin) │
│ │ │ │ │ │
│ Despliega │ ──▶ │ Ejecuta │ ◀── │ Administra │
│ la app │ │ la app │ │ el server │
└─────────────┘ └─────────────┘ └─────────────┘
```
---
## 3. Llaves SSH
Las **llaves SSH** son como un "carnet de identidad digital" que permite conectarse al servidor sin escribir una contraseña cada vez.
En `proyectosacc` usamos un **par de llaves SSH dedicado**, generado específicamente para este proyecto. No se usa contraseña ni herramientas como `sshpass`.
### ¿Cómo funcionan?
Una llave SSH siempre viene en **par**:
1. **Llave privada**: es secreta. Solo la tiene quien se va a conectar.
2. **Llave pública**: se puede compartir. Se coloca en el servidor para decirle "confía en quien tenga la llave privada que corresponde a esta pública".
### Analogía simple
Imagina un candado con dos llaves:
- La **pública** es el candado mismo. Lo pones en la puerta del servidor.
- La **privada** es la llave que abre el candado. Solo tú la tienes.
---
## 4. Dónde viven las llaves
### Llave privada
- **Vive en**: las variables seguras de **Bitbucket Pipelines**.
- **Nombre de variable**: `PROYECTOSACC_SSH_KEY`
- **Formato**: codificada en **base64** (esto evita que Bitbucket modifique el formato).
- **Quién la usa**: el pipeline de Bitbucket cuando se conecta por SSH al servidor.
### Llave pública
- **Vive en**: el archivo `~/.ssh/authorized_keys` del usuario `thoth` en la EC2.
- **Ruta completa**: `/home/thoth/.ssh/authorized_keys`
- **Función**: permite que el pipeline se conecte sin contraseña.
### Llaves del servidor hacia Bitbucket
- El usuario `thoth` también tiene sus propias llaves SSH para clonar repositorios desde Bitbucket.
- **Ubicación**: `/home/thoth/.ssh/`
- **Ejemplo de nombre**: `proyectosacc_server_thoth_develop_TO_bitbucket_pipeline_ci_cd`
---
## 5. Resumen rápido
| Usuario | Rol principal | Llave SSH asociada |
|---------|---------------|-------------------|
| `ubuntu` | Administrador del servidor | Llave de acceso inicial a la EC2 (creada al lanzarla en AWS) |
| `thoth` | Despliegue automático (CI/CD) | `PROYECTOSACC_SSH_KEY` en Bitbucket Variables |
| `osiris` | Ejecutar la aplicación | No necesita llave SSH para conexiones externas |
---
*Anterior: [`03-infraestructura-aws.md`](03-infraestructura-aws.md)*
*Siguiente: [`05-pipeline-bitbucket.md`](05-pipeline-bitbucket.md)*
+199
View File
@@ -0,0 +1,199 @@
# 05 - Pipeline Bitbucket
> Cómo funciona el pipeline de 7 pasos que despliega `proyectosacc`.
---
## 1. ¿Qué es un pipeline?
Un **pipeline** es una serie de pasos automáticos que se ejecutan cada vez que alguien sube código al repositorio.
En Cómputo Contable Soft, todos los pipelines web usan **7 pasos** estandarizados. Esto significa que, sin importar el proyecto, siempre hay los mismos pasos con los mismos nombres.
---
## 2. Los 7 pasos del pipeline
### Paso 1: `01_image-setup`
**Prepara la computadora virtual donde se va a compilar todo.**
Qué hace:
1. Actualiza los paquetes del sistema.
2. Instala herramientas necesarias: `openssh-client`, `openjdk-21-jdk`, `aws-cli`.
3. Configura las llaves SSH y los `known_hosts`.
4. Carga los scripts de utilería (`logger_bash.sh`, `telegram_alert.sh`).
> 💡 **En palabras simples**: como cuando llegas a una cocina nueva y primero lavas los trastes, prendes la estufa y sacas los utensilios.
---
### Paso 2: `02_repo-config`
**Descarga todos los repositorios que necesita la aplicación.**
Qué hace:
1. Clona el repositorio principal de la aplicación.
2. Clona los repositorios de componentes compartidos (por ejemplo, `cmp-commons`, `cmp-security`).
3. Clona `ci-cd-commons` (scripts base de CCsoft).
> 💡 **En palabras simples**: bajas todos los ingredientes de la receta antes de empezar a cocinar.
---
### Paso 3: `03_dependencies`
**Compila o descarga todo lo que la aplicación necesita para funcionar.**
Qué hace:
1. Compila las librerías compartidas.
2. Descarga dependencias de Maven, Gradle, npm, etc.
3. Publica las librerías locales si es necesario.
> 💡 **En palabras simples**: preparas la masa, cortas las verduras y sacas los condimentos antes de armar el platillo final.
---
### Paso 4: `04_build`
**Compila el frontend React y la API backend.**
Qué hace:
1. **Frontend**: ejecuta `npm run build` (o `npm ci && npm run build`) para generar la carpeta `build/` con el React estático.
2. **Backend**: ejecuta el comando de build de la API (por ejemplo, `./gradlew clean bootJar` para Java).
3. Genera los archivos finales: la carpeta `build/` para el frontend y el `.jar` para la API.
> 💡 **En palabras simples**: aquí cocinamos dos platillos: la entrada (frontend) y el plato fuerte (API).
---
### Paso 5: `05_publish`
**Sube el frontend a S3 y el artefacto de la API a Amazon S3.**
Qué hace:
1. **Frontend**: sincroniza la carpeta `build/` al bucket S3 designado para el sitio estático (`aws s3 sync build/ s3://bucket-name`).
2. **Backend**: sube el `.jar` (o artefacto equivalente) al bucket S3 para que la EC2 lo descargue después.
3. Esto evita transferir archivos grandes directamente por SSH y permite recuperar versiones anteriores fácilmente.
> 💡 **En palabras simples**: pones la entrada en el mostrador (S3) y el plato fuerte en la bandeja de la cocina (S3) para servirlo después.
---
### Paso 6: `06_install`
**Descarga el artefacto de la API en el servidor EC2.**
Qué hace:
1. Se conecta por SSH al servidor EC2 como `thoth`.
2. Ejecuta un script de preparación que descarga el `.jar` desde **S3** y lo coloca en `/home/thoth/deploy/artifacts/current/`.
3. También hace una copia de seguridad de la versión anterior.
4. **Aún no reinicia el servicio**: eso ocurre en el paso 7.
> 💡 **En palabras simples**: pones el plato fuerte en el plato y lo dejas listo, pero aún no lo sirves.
---
### Paso 7: `07_deploy`
**Invalida la caché de CloudFront, despliega la API y verifica que todo esté saludable.**
Qué hace:
1. **Invalida la caché de CloudFront** (`aws cloudfront create-invalidation`) para que los usuarios vean la nueva versión del frontend inmediatamente.
2. Se conecta por SSH al servidor EC2 como `thoth` y ejecuta `bash /home/thoth/deploy/setup/deploy.sh`.
3. El script `deploy.sh` crea o actualiza el servicio de `systemd`, detiene la versión anterior e inicia la nueva con el usuario `osiris`.
4. Ejecuta un **health check** en la API para verificar que responde correctamente.
5. Envía una notificación por Telegram con el resultado.
> 💡 **En palabras simples**: actualizas el menú en todos los restaurantes (CloudFront) y sirves el plato fuerte calentito desde la cocina (EC2).
---
## 3. Diagrama del pipeline
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ BITBUCKET PIPELINE │
│ │
│ 1. image-setup ▶ Instala herramientas │
│ │ │
│ ▼ │
│ 2. repo-config ▶ Descarga repositorios │
│ │ │
│ ▼ │
│ 3. dependencies ▶ Compila librerías y dependencias │
│ │ │
│ ▼ │
│ 4. build ▶ Compila React frontend + API backend (.jar) │
│ │ │
│ ▼ │
│ 5. publish ▶ Sube frontend a S3 web + API .jar a S3 artifacts │
│ │ │
│ ▼ │
│ 6. install ▶ Prepara la API en el servidor EC2 │
│ │ │
│ ▼ │
│ 7. deploy ▶ Invalida CloudFront, reinicia API y health check │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 4. ¿Cómo se conecta el pipeline a la EC2?
La conexión se hace por **SSH** (Secure Shell).
1. El pipeline tiene guardada la **llave privada** de `thoth` en una variable segura de Bitbucket.
2. Al llegar al paso `05_publish` o `06_install`, el pipeline decodifica la llave y la guarda en `/root/.ssh/sacc4_key`.
3. Ejecuta un comando `ssh` usando esa llave para ejecutar scripts remotos.
4. La **llave pública** correspondiente ya está en el archivo `~/.ssh/authorized_keys` del usuario `thoth` en la EC2.
5. ¡La puerta se abre y el pipeline puede ejecutar comandos en el servidor!
### Ejemplo del comando que usa el pipeline
```bash
ssh -p 22 \
-i /root/.ssh/sacc4_key \
-o StrictHostKeyChecking=no \
thoth@<IP_DE_LA_EC2> \
"bash /home/thoth/deploy/setup/deploy.sh"
```
---
## 5. ¿De dónde salen los secrets?
Los **secrets** (contraseñas, llaves, tokens) viven en las **variables de repositorio** de Bitbucket.
### Cómo se configuran
1. Entra al repositorio en Bitbucket.
2. Ve a **Repository settings > Pipelines > Repository variables**.
3. Agrega cada variable y márcala como **Secured** 🔒.
4. Las variables marcadas como secured se ofuscan en los logs (aparecen como `***`).
### Variables principales
| Variable | Descripción |
|----------|-------------|
| `PROYECTOSACC_SSH_KEY` | Llave privada SSH (en base64) dedicada para conectar al servidor |
| `PROYECTOSACC_SERVER_IP` | IP pública de la EC2 |
| `PROYECTOSACC_SSH_PORT` | Puerto SSH (normalmente `22`) |
| `PROYECTOSACC_SERVER_USER` | Usuario SSH para despliegue (`thoth`) |
| `PROYECTOSACC_TELEGRAM_BOT_TOKEN` | Token del bot de Telegram para alertas |
| `PROYECTOSACC_TELEGRAM_CHAT_ID` | ID del chat donde llegan las alertas |
| `AWS_ACCESS_KEY_ID` | Credencial de AWS para subir a S3 |
| `AWS_SECRET_ACCESS_KEY` | Credencial secreta de AWS |
| `CLOUDFRONT_DISTRIBUTION_ID` | ID de la distribución de CloudFront para invalidaciones |
| `S3_FRONTEND_BUCKET` | Nombre del bucket S3 donde se publica el frontend |
### ⚠️ Regla de oro
**NUNCA** escribas secrets directamente en el código del pipeline (`bitbucket-pipelines.yml`). Siempre usa variables de entorno.
---
## 6. Integración con proyectos CI/CD compartidos
`proyectosacc` no trabaja solo. Se conecta con otros proyectos de CI/CD de la empresa que proveen piezas reutilizables:
- **`ci-cd-commons`**: scripts base compartidos como `telegram_alert.sh` y `logger_bash.sh`.
- **`ci-cd-saac4`**: módulos de Terraform, patrones de infraestructura y scripts de despliegue específicos para la familia SACC.
Esto significa que parte del pipeline y de los scripts de despliegue vienen de esos repositorios, no de este. Es como armar una casa con ladrillos que ya están hechos en otra fábrica.
---
*Anterior: [`04-usuarios-y-permisos.md`](04-usuarios-y-permisos.md)*
*Siguiente: [`06-scripts-de-despliegue.md`](06-scripts-de-despliegue.md)*
+197
View File
@@ -0,0 +1,197 @@
# 06 - Scripts de Despliegue
> Qué hace cada script y cuándo se ejecuta.
---
## 1. ¿Qué son los scripts de despliegue?
Los **scripts** son archivos de texto con instrucciones que la computadora puede ejecutar automáticamente.
En `proyectosacc` usamos scripts escritos en **Bash** (el lenguaje de comandos de Linux). Todos comienzan con esta línea mágica:
```bash
set -euo pipefail
```
Esto significa tres cosas:
1. **`set -e`**: Si un comando falla, el script se detiene inmediatamente.
2. **`set -u`**: Si usas una variable que no existe, el script se detiene.
3. **`set -o pipefail`**: Si un comando dentro de una "tubería" (`|`) falla, el script también se detiene.
> 💡 **En palabras simples**: es como poner el cinturón de seguridad antes de manejar.
---
### 1.1. Scripts provenientes de otros proyectos CI/CD
`proyectosacc` reutiliza scripts y utilerías que viven en repositorios compartidos:
- **`ci-cd-commons`**: contiene `telegram_alert.sh`, `logger_bash.sh` y otras utilerías base que usan todos los pipelines de la empresa.
- **`ci-cd-saac4`**: provee módulos de Terraform y scripts de despliegue adaptados para la familia SACC.
Estos repositorios se clonan dentro de la carpeta `/home/thoth/deploy/` (por ejemplo, `/home/thoth/deploy/ci-cd-commons/`) para que los scripts locales puedan usarlos.
### 1.2. Nuevo script: `deploy-frontend-s3.sh`
Este script se ejecuta **desde el pipeline de Bitbucket** (no dentro de la EC2). Su trabajo es subir el frontend React a S3.
Qué hace:
1. Sincroniza la carpeta `build/` con el bucket S3 (`aws s3 sync build/ s3://bucket-name --delete`).
2. Espera a que la sincronización termine.
> 💡 **Nota**: la invalidación de la caché de CloudFront se hace en el **paso 7** del pipeline (`07_deploy`), después de confirmar que la API está saludable. Así evitamos refrescar el frontend antes de que el backend esté listo.
Ejemplo de comando:
```bash
bash /home/thoth/deploy/scripts/deploy-frontend-s3.sh
```
---
## 2. `deploy.sh`
### ¿Qué hace?
Este es el script principal de despliegue de la **API backend**. Se ejecuta **dentro del servidor EC2** y se encarga de instalar la nueva versión del servicio.
### Pasos que sigue
1. **Crear directorios**: verifica que existan las carpetas necesarias (`backup`, `current`, `pids`, `logs`).
2. **Detener servicio anterior**: si la aplicación ya está corriendo, la detiene con `systemctl`.
3. **Configurar systemd**: crea o actualiza el archivo de servicio para que la app inicie automáticamente.
4. **Recargar systemd**: ejecuta `sudo systemctl daemon-reload`.
5. **Iniciar servicio**: ejecuta `sudo systemctl start <nombre-del-servicio>`.
6. **Health check**: hace peticiones a `http://localhost:8080/actuator/health` para verificar que la app responde.
7. **Guardar PID**: anota el ID del proceso para poder monitorearlo después.
### ¿Cuándo se llama?
- En el **paso 7** del pipeline (`07_deploy`).
- También puede ejecutarse manualmente si necesitas hacer un despliegue fuera del pipeline.
### Ejemplo de comando
```bash
bash /home/thoth/deploy/setup/deploy.sh
```
---
## 3. `health-check.sh`
### ¿Qué hace?
Verifica que la aplicación esté viva y respondiendo correctamente.
### Cómo funciona
1. Intenta conectarse a la URL de salud de la aplicación:
```
http://localhost:8080/actuator/health
```
2. Si la respuesta contiene `"status":"UP"`, todo está bien.
3. Si no responde después de varios intentos, reporta un error.
### ¿Cuándo se llama?
- **Dentro de `deploy.sh`**, inmediatamente después de iniciar el servicio.
- También puede usarse manualmente para revisar si la app sigue funcionando horas después del despliegue.
### Ejemplo de comando manual
```bash
bash /home/thoth/deploy/scripts/health-check.sh
```
---
## 4. `rollback.sh`
### ¿Qué hace?
Regresa la API backend a la versión anterior si el nuevo despliegue falló. También puede restaurar el frontend en S3 si se habilitó **versionamiento de objetos** en el bucket.
### Pasos que sigue (API en EC2)
1. Busca el último backup en la carpeta `/home/thoth/deploy/artifacts/backup/`.
2. Detiene el servicio actual.
3. Copia el backup a la carpeta `current`.
4. Reinicia el servicio con la versión anterior.
5. Ejecuta un health check para confirmar que la versión anterior todavía funciona.
### Pasos que sigue (frontend en S3)
Si el frontend también necesita rollback:
1. Identifica la versión anterior del archivo en S3 usando el versionamiento del bucket.
2. Restaura la versión anterior de `index.html` y los assets estáticos.
3. Invalida la caché de CloudFront para que los usuarios vean la versión anterior.
### ¿Cuándo se llama?
- Cuando el `deploy.sh` o el `health-check.sh` reportan un fallo en la API.
- Manualmente, si descubres que la nueva versión del frontend o la API tiene un bug grave.
### Ejemplo de comando
```bash
bash /home/thoth/deploy/scripts/rollback.sh
```
---
## 5. `notify-telegram.sh`
### ¿Qué hace?
Envía mensajes a un grupo o chat de Telegram para informar sobre el estado del pipeline.
### Qué tipo de mensajes envía
- 🟢 **Éxito**: "El despliegue de proyectosacc terminó correctamente."
- 🔴 **Error**: "El pipeline de proyectosacc falló en el paso 4. Revisar logs."
- 🟡 **Advertencia**: "Health check falló. Iniciando rollback automático."
### ¿Cuándo se llama?
- Al **inicio** del pipeline.
- Al **final** del pipeline (éxito o fracaso).
- Cuando ocurre un error en cualquier paso.
### Variables necesarias
```bash
TELEGRAM_BOT_TOKEN=<token-del-bot>
TELEGRAM_CHAT_ID=<id-del-chat>
```
### Ejemplo de comando
```bash
bash /home/thoth/deploy/ci-cd-commons/telegram_alert.sh "Despliegue exitoso"
```
---
## 6. Resumen de cuándo usar cada script
| Script | ¿Cuándo se ejecuta? | ¿Dónde corre? |
|--------|---------------------|---------------|
| `deploy-frontend-s3.sh` | Paso 5 del pipeline (subir React a S3) | Pipeline (Bitbucket) |
| `deploy.sh` | Paso 7 del pipeline (desplegar API en la EC2), o manualmente | Dentro de la EC2 |
| `health-check.sh` | Después de `deploy.sh`, o manualmente | Dentro de la EC2 |
| `rollback.sh` | Cuando `deploy.sh` o `health-check.sh` fallan, o manualmente | Pipeline o EC2 |
| `notify-telegram.sh` | Inicio, fin o error del pipeline | Pipeline (Bitbucket) o EC2 |
---
## 7. Dato extra: ¿dónde viven los scripts?
En el servidor EC2, los scripts se organizan así:
```
/home/thoth/deploy/
├── artifacts/
│ ├── backup/ ← versiones anteriores de la API
│ ├── current/ ← versión activa de la API
│ ├── logs/ ← logs del pipeline
│ └── pids/ ← archivos con el ID de proceso
├── ci-cd-commons/
│ ├── telegram_alert.sh
│ └── logger_bash.sh
├── scripts/
│ ├── deploy-frontend-s3.sh ← sube React a S3
│ ├── health-check.sh
│ └── rollback.sh
└── setup/
└── deploy.sh ← script principal del pipeline para la API (llamado vía SSH)
```
> 💡 **Nota**: el artefacto de la API que termina en `artifacts/current/` se descarga desde **Amazon S3** durante el paso de instalación del pipeline. No se transfiere directamente por SSH. El frontend React se sube directamente a S3 desde el pipeline.
---
*Anterior: [`05-pipeline-bitbucket.md`](05-pipeline-bitbucket.md)*
*Siguiente: [`07-terraform-iac.md`](07-terraform-iac.md)*
+322
View File
@@ -0,0 +1,322 @@
# 07 - Terraform (IaC)
> Cómo creamos la infraestructura de `proyectosacc` con código.
---
## 1. ¿Qué es Terraform?
**Terraform** es una herramienta que permite crear infraestructura en la nube escribiendo código en lugar de hacer clic en la consola de AWS.
A esto se le llama **IaC** (Infrastructure as Code) o "Infraestructura como Código".
### Ventajas de usar Terraform
-**Repetible**: puedes crear la misma infraestructura 10 veces y siempre será idéntica.
-**Versionable**: el código se guarda en Git, así que sabes quién hizo qué cambio y cuándo.
-**Rápido**: un solo comando crea servidor, base de datos, DNS y seguridad.
-**Sin sorpresas**: todo está documentado en archivos de texto.
---
## 2. ¿Qué crea Terraform en proyectosacc?
Terraform se encarga de crear todos estos recursos en AWS:
1. **VPC y subredes**: la red virtual donde vive todo.
2. **Security Groups**: las reglas de firewall.
3. **EC2 T3.small**: el servidor de la **API backend**.
4. **RDS MariaDB**: la base de datos gestionada.
5. **S3 Bucket (artefactos)**: el almacén de archivos de la API.
6. **S3 Bucket (frontend)**: el sitio web estático que sirve el React app.
7. **CloudFront Distribution**: la CDN que distribuye el frontend globalmente con HTTPS.
8. **Route 53 Record**: el dominio `sacc.ccsoft.mx` apunta a CloudFront.
9. **ACM Certificate**: certificado SSL para CloudFront.
10. **IAM Role**: permisos para que la EC2 y CloudFront accedan a S3.
---
## 3. Archivos principales de Terraform
### `main.tf`
Este es el archivo central. Aquí se definen los recursos que Terraform va a crear.
Ejemplo de la EC2 para la API:
```hcl
resource "aws_instance" "sacc_api" {
ami = "ami-0c02fb55956c7d316" # Ubuntu 22.04
instance_type = "t3.small"
vpc_security_group_ids = [aws_security_group.sacc_sg.id]
user_data = file("user-data.sh")
tags = {
Name = "proyectosacc-api"
Environment = "develop"
Project = "proyectosacc"
}
}
```
Ejemplo del bucket S3 para el frontend React:
```hcl
resource "aws_s3_bucket" "sacc_frontend" {
bucket = "ccsoft-proyectosacc-frontend"
}
resource "aws_s3_bucket_website_configuration" "sacc_frontend" {
bucket = aws_s3_bucket.sacc_frontend.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
}
```
Ejemplo de CloudFront:
```hcl
resource "aws_cloudfront_distribution" "sacc_cdn" {
enabled = true
default_root_object = "index.html"
aliases = ["sacc.ccsoft.mx"]
# Origin 1: S3 bucket para el frontend React
origin {
domain_name = aws_s3_bucket.sacc_frontend.bucket_regional_domain_name
origin_id = "saccS3Origin"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.sacc_oai.cloudfront_access_identity_path
}
}
# Origin 2: EC2 para la API backend
origin {
domain_name = aws_instance.sacc_api.public_dns
origin_id = "saccApiOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "saccS3Origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
}
# API requests go to the EC2 origin
ordered_cache_behavior {
path_pattern = "/api/*"
target_origin_id = "saccApiOrigin"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = true
headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"]
cookies {
forward = "all"
}
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.sacc_cert.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
```
> 💡 **¿Por qué dos origins?** CloudFront sirve el frontend desde S3 por defecto. Pero cuando el navegador pide algo bajo `/api/*`, CloudFront lo envía a la EC2 donde Nginx lo redirige al backend en el puerto 8080. Así todo comparte el mismo dominio `sacc.ccsoft.mx`.
---
### `variables.tf`
Aquí se definen las variables que personalizan la infraestructura.
Ejemplo:
```hcl
variable "region" {
description = "Región de AWS"
type = string
default = "us-east-1"
}
variable "instance_type" {
description = "Tipo de instancia EC2"
type = string
default = "t3.small"
}
variable "db_password" {
description = "Contraseña de la base de datos"
type = string
sensitive = true
}
```
> ⚠️ La variable `db_password` tiene `sensitive = true` para que no aparezca en los logs.
---
### `user-data.sh`
Este archivo contiene los comandos que la EC2 ejecuta la **primera vez** que se enciende.
Ejemplo de lo que hace:
1. Actualiza el sistema operativo.
2. Instala Nginx.
3. Instala Java (para correr la aplicación SACC).
4. Instala AWS CLI.
5. Crea los usuarios `thoth` y `osiris`.
6. Inyecta la **llave pública SSH dedicada** para el pipeline en `~/.ssh/authorized_keys` del usuario `thoth`.
7. Crea los directorios necesarios para el despliegue.
```bash
#!/bin/bash
set -euo pipefail
# Actualizar sistema
apt-get update -y
apt-get install -y nginx openjdk-21-jdk awscli
# Crear usuarios
useradd -m -s /bin/bash thoth || true
useradd -m -s /bin/bash osiris || true
# Crear directorios de despliegue
mkdir -p /home/thoth/deploy/artifacts/{backup,current,logs,pids}
chown -R thoth:thoth /home/thoth/deploy
# Configurar Nginx como proxy SOLO para la API (no para el frontend)
cat > /etc/nginx/sites-available/proyectosacc-api <<'EOF'
server {
listen 80;
server_name _;
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
ln -sf /etc/nginx/sites-available/proyectosacc-api /etc/nginx/sites-enabled/default
# Iniciar Nginx
systemctl enable nginx
systemctl start nginx
```
---
### `outputs.tf`
Aquí definimos qué información queremos que Terraform nos muestre al final.
Ejemplo:
```hcl
output "ec2_public_ip" {
description = "IP pública del servidor de la API"
value = aws_instance.sacc_api.public_ip
}
output "rds_endpoint" {
description = "Dirección de conexión a la base de datos"
value = aws_db_instance.sacc_db.endpoint
}
output "s3_artifacts_bucket" {
description = "Nombre del bucket de artefactos de la API"
value = aws_s3_bucket.artifacts.bucket
}
output "s3_frontend_bucket" {
description = "Nombre del bucket del frontend React"
value = aws_s3_bucket.sacc_frontend.bucket
}
output "cloudfront_domain" {
description = "Dominio de CloudFront"
value = aws_cloudfront_distribution.sacc_cdn.domain_name
}
```
Después de ejecutar `terraform apply`, verás estos valores en la pantalla.
---
## 4. Cómo ejecutar Terraform paso a paso
### Paso 1: Ir a la carpeta de Terraform
```bash
cd /home/evert/Servidores/Nuve/AWS/proyectosacc/terraform
```
### Paso 2: Inicializar Terraform
```bash
terraform init
```
Esto descarga los plugins necesarios para hablar con AWS.
### Paso 3: Revisar el plan
```bash
terraform plan
```
Terraform te muestra **todo lo que va a crear, modificar o destruir**. Revísalo con cuidado.
### Paso 4: Aplicar los cambios
```bash
terraform apply
```
Terraform te pedirá que escribas `yes` para confirmar. Luego empezará a crear los recursos en AWS.
### Paso 5: Ver los outputs
Al finalizar, Terraform mostrará los valores definidos en `outputs.tf`:
- IP pública de la EC2 (API backend)
- Endpoint de la base de datos
- Nombre del bucket S3 de artefactos
- Nombre del bucket S3 del frontend
- Dominio de CloudFront
---
## 5. Comandos útiles adicionales
| Comando | Para qué sirve |
|---------|----------------|
| `terraform validate` | Revisa que la sintaxis de los archivos esté correcta. |
| `terraform fmt` | Ordena y da formato a los archivos `.tf`. |
| `terraform show` | Muestra el estado actual de la infraestructura. |
| `terraform destroy` | **Elimina** todos los recursos creados. ¡Úsalo con cuidado! |
---
## 6. Checklist antes de ejecutar Terraform
- [ ] Tienes configuradas las credenciales de AWS (`aws configure`).
- [ ] La variable `db_password` no está escrita en el código, viene de un archivo seguro o variable de entorno.
- [ ] Revisaste el `terraform plan` antes de aplicar.
- [ ] Sabes en qué región de AWS se va a crear todo (`us-east-1`, `us-west-2`, etc.).
- [ ] El bucket S3 para el estado remoto ya existe (si usas backend remoto).
- [ ] El `user_data.sh` incluye la llave pública SSH dedicada para el pipeline.
- [ ] El bucket S3 del frontend tiene habilitado el versionamiento de objetos (para rollback del frontend).
- [ ] CloudFront tiene configurado el Origin Access Identity (OAI) u Origin Access Control (OAC) para leer de S3.
- [ ] Route 53 apunta a CloudFront, no a la IP de la EC2.
- [ ] CloudFront tiene un behavior `/api/*` que apunta a la EC2.
- [ ] La EC2 tiene el Security Group configurado para recibir tráfico HTTP desde CloudFront.
---
*Anterior: [`06-scripts-de-despliegue.md`](06-scripts-de-despliegue.md)*
*Siguiente: [`08-seguridad-y-secretos.md`](08-seguridad-y-secretos.md)*
+205
View File
@@ -0,0 +1,205 @@
# 08 - Seguridad y Secretos
> Cómo protegemos las contraseñas, llaves y datos sensibles de `proyectosacc`.
---
## 1. ¿Qué es un "secreto"?
Un **secreto** es cualquier información que no debe ser pública. En `proyectosacc`, los secrets incluyen:
- Contraseñas de bases de datos.
- Llaves SSH privadas.
- Tokens de Telegram.
- Credenciales de AWS (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`).
> 💡 **Regla de oro**: si perder esa información causaría un problema de seguridad, es un secreto.
---
## 2. ¿Dónde viven los secrets?
### Opción 1: Bitbucket Repository Variables (la más usada)
En Bitbucket Pipelines podemos guardar variables de entorno que los scripts usan sin exponer su valor.
**Cómo configurarlas:**
1. Ve al repositorio en Bitbucket.
2. Entra a **Repository settings > Pipelines > Repository variables**.
3. Agrega el nombre y el valor.
4. **Marca la casilla "Secured"** 🔒.
**Ventaja**: cuando el pipeline corre, las variables secured aparecen como `***` en los logs. Nadie puede leerlas.
### Opción 2: AWS Secrets Manager
AWS tiene un servicio llamado **Secrets Manager** que guarda secrets de forma segura y permite rotarlos automáticamente.
**Ejemplo de uso en un pipeline de CodeBuild:**
```yaml
env:
secrets-manager:
DB_PASSWORD: "prod/database/password"
```
### Opción 3: AWS Systems Manager Parameter Store
Similar a Secrets Manager, pero más simple y económico. Se usa para guardar configuraciones sensibles.
```yaml
env:
parameter-store:
DATABASE_URL: "/app/database/url"
```
### Opción 4: Archivo `.env` (solo local)
En tu computadora personal puedes tener un archivo `.env` con variables locales. **Este archivo NUNCA debe subirse al repositorio.**
El archivo `.gitignore` del proyecto ya debería incluir `.env` para evitar accidentes.
---
## 3. ¿Qué NUNCA poner en el repositorio?
### ❌ NUNCA hagas esto
```yaml
# Mal ejemplo en bitbucket-pipelines.yml
- step:
script:
- export DB_PASSWORD="supersecreta123"
- export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
```
```hcl
# Mal ejemplo en Terraform
resource "aws_db_instance" "example" {
password = "mi-contrasena-123"
}
```
```bash
# Mal ejemplo en un script
API_KEY="sk-1234567890abcdef"
```
### ✅ SIEMPRE haz esto
```yaml
# Bien: usa variables de entorno
- step:
script:
- export DB_PASSWORD="$DB_PASSWORD"
```
```hcl
# Bien: usa variables de Terraform
resource "aws_db_instance" "example" {
password = var.db_password
}
```
```bash
# Bien: lee desde una variable de entorno
API_KEY="$API_KEY"
```
---
## 4. ¿Cómo rotar las llaves SSH?
**Rotar** una llave significa crear una nueva y dejar de usar la anterior. Es una buena práctica de seguridad, especialmente si alguien con acceso a la llave privada deja la empresa.
### Paso 1: Generar un nuevo par de llaves
```bash
ssh-keygen -t ed25519 -b 4096 \
-C "bitbucket.pipeline.ci.cd.proyectosacc.thoth.develop@computocontable.com" \
-f bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop
```
### Paso 2: Codificar la llave privada en base64
```bash
base64 -i bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop | tr -d '\n' > nueva_llave.b64
```
### Paso 3: Actualizar la variable en Bitbucket
1. Copia el contenido de `nueva_llave.b64`.
2. Ve a **Repository settings > Pipelines > Repository variables**.
3. Actualiza la variable `PROYECTOSACC_SSH_KEY` con el nuevo valor.
### Paso 4: Colocar la llave pública en el servidor
```bash
# Copia el contenido del archivo .pub
cat bitbucket_pipeline_ci_cd_TO_proyectosacc_server_thoth_develop.pub
# Conéctate al servidor y agrégala a authorized_keys
ssh ubuntu@<IP_DE_LA_EC2>
sudo su - thoth
nano ~/.ssh/authorized_keys
# Pega la nueva llave pública al final del archivo
> 💡 **Tip**: en un despliegue automatizado con Terraform, esta llave pública se inyecta directamente desde el archivo `user_data`. No hace falta editar `authorized_keys` a mano.
### Paso 5: Probar la conexión
Ejecuta un pipeline de prueba o conéctate manualmente con la nueva llave privada para confirmar que funciona.
### Paso 6: Eliminar la llave anterior
Una vez confirmado que la nueva llave funciona:
1. Borra la llave pública antigua de `~/.ssh/authorized_keys`.
2. Borra la llave privada antigua de tu computadora.
3. Actualiza cualquier otra copia que exista.
---
## 5. Seguridad del frontend en S3 + CloudFront
### Bucket policy para CloudFront (OAI)
El bucket S3 del frontend NO debe ser público. Solo CloudFront puede leer los objetos:
```json
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFrontOAI",
"Effect": "Allow",
"Principal": {
"CanonicalUser": "<OAI-canonical-user-id>"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::ccsoft-proyectosacc-frontend/*"
}]
}
```
### CORS en S3 (si el frontend llama a la API)
Como el frontend React y la API backend comparten el mismo dominio `https://sacc.ccsoft.mx` (gracias al behavior `/api/*` de CloudFront), **no hay problema de CORS**. Si en el futuro decides mover la API a un subdominio diferente (por ejemplo, `api.sacc.ccsoft.mx`), ahí sí necesitarás configurar CORS en el backend.
### Versionamiento de objetos en S3
Habilita el versionamiento en el bucket del frontend. Así puedes restaurar una versión anterior del sitio si un deploy falla:
```bash
aws s3api put-bucket-versioning \
--bucket ccsoft-proyectosacc-frontend \
--versioning-configuration Status=Enabled
```
---
## 6. Checklist de seguridad básica
- [ ] El archivo `.env` está en `.gitignore`.
- [ ] Ningún archivo del repositorio contiene contraseñas escritas directamente.
- [ ] Las variables de Bitbucket están marcadas como **Secured**.
- [ ] La llave privada SSH dedicada para `proyectosacc` está en formato **base64** en Bitbucket.
- [ ] No se usa acceso SSH por contraseña ni herramientas como `sshpass`.
- [ ] El puerto SSH (22) de la EC2 solo está abierto a IPs conocidas.
- [ ] La base de datos RDS no es accesible desde internet (solo desde la VPC).
- [ ] Los certificados SSL están configurados correctamente en **CloudFront** (HTTPS obligatorio).
- [ ] El bucket S3 del frontend NO es público; solo CloudFront tiene acceso mediante OAI/OAC.
- [ ] El versionamiento de objetos está habilitado en el bucket S3 del frontend.
- [ ] Se ha programado una rotación de llaves SSH al menos una vez al año.
---
*Anterior: [`07-terraform-iac.md`](07-terraform-iac.md)*
*Siguiente: [`09-runbook-deploy.md`](09-runbook-deploy.md)*
+269
View File
@@ -0,0 +1,269 @@
# 09 - Runbook: Deploy Manual
> Qué hacer si el pipeline automático falla y necesitas desplegar a mano.
---
## 1. ¿Cuándo hacer un deploy manual?
Un **deploy manual** significa que tú, persona humana, ejecutas los pasos que normalmente hace el pipeline automático.
Haz un deploy manual cuando:
- 🔴 El pipeline de Bitbucket falló y no se puede reintentar.
- 🟡 Necesitas desplegar una corrección urgente fuera de horario.
- 🟠 El servidor EC2 fue recreado y el pipeline no está configurado todavía.
---
## 2. Paso a paso: deploy manual
Un deploy manual ahora tiene **dos partes**: subir el frontend a S3 + CloudFront, y desplegar la API en la EC2.
---
### PARTE A: Subir el frontend React a S3 e invalidar CloudFront
Esta parte puedes hacerla desde tu computadora local o desde cualquier máquina con AWS CLI configurada.
#### Paso A1: Ve a la carpeta del frontend compilado
```bash
cd /ruta/al/proyecto/frontend/build
```
Si no has compilado el frontend aún:
```bash
npm ci
npm run build
```
#### Paso A2: Sincroniza la carpeta `build/` con S3
```bash
aws s3 sync . s3://ccsoft-proyectosacc-frontend --delete
```
#### Paso A3: Invalida la caché de CloudFront
```bash
aws cloudfront create-invalidation \
--distribution-id <DISTRIBUTION_ID> \
--paths "/*"
```
> 💡 **Nota**: el `DISTRIBUTION_ID` lo encuentras en la consola de AWS o en los outputs de Terraform.
#### Paso A4: Verifica el frontend
Abre tu navegador y visita:
```
https://sacc.ccsoft.mx
```
Si ves la interfaz de SACC, la parte A fue exitosa.
---
### PARTE B: Desplegar la API backend en la EC2
#### Paso B1: Conéctate al servidor EC2
Necesitas la IP pública de la instancia y la **llave SSH dedicada** generada para `proyectosacc`.
```bash
ssh -i ~/.ssh/tu_llave.pem ubuntu@<IP_DE_LA_EC2>
```
O si ya estás dentro de la red de la empresa:
```bash
ssh thoth@<IP_DE_LA_EC2>
```
---
#### Paso B2: Conviértete en el usuario thoth
Si entraste como `ubuntu`, cambia al usuario de despliegue:
```bash
sudo su - thoth
```
---
#### Paso B3: Ve al directorio de despliegue
```bash
cd /home/thoth/deploy
```
---
#### Paso B4: Descarga el artefacto compilado de la API
Si el artefacto está en S3, descárgalo:
```bash
aws s3 cp s3://ccsoft-artifacts-sacc4/develop/proyectosacc-app-1.0.0.jar \
/home/thoth/deploy/artifacts/current/
```
Si no tienes acceso a S3, pide el archivo `.jar` a un compañero y súbelo con `scp`.
---
#### Paso B5: Ejecuta el script de deploy de la API
```bash
bash /home/thoth/deploy/setup/deploy.sh
```
Este script hará lo siguiente automáticamente:
1. Hará backup de la versión anterior.
2. Detendrá el servicio actual.
3. Copiará el nuevo archivo a su lugar.
4. Configurará y recargará `systemd`.
5. Iniciará el nuevo servicio.
6. Hará un health check.
---
#### Paso B6: Verifica que el servicio esté corriendo
```bash
sudo systemctl status proyectosacc-app.service
```
Deberías ver algo como:
```
● proyectosacc-app.service - Proyecto SACC App Service
Loaded: loaded (/etc/systemd/system/proyectosacc-app.service; enabled)
Active: active (running) since Mon 2026-04-14 10:30:00 UTC
```
Si dice `active (running)`, ¡excelente!
---
#### Paso B7: Verifica el health check de la API
```bash
curl http://localhost:8080/actuator/health
```
La respuesta debe incluir:
```json
{"status":"UP"}
```
---
#### Paso B8: Verifica la API desde fuera
Desde tu navegador o con `curl`, prueba un endpoint de la API:
```bash
curl https://sacc.ccsoft.mx/api/actuator/health
```
Si responde correctamente, el deploy manual completo fue exitoso.
---
## 3. Cómo revisar si el deploy funcionó
### Frontend (S3 + CloudFront)
- Abre `https://sacc.ccsoft.mx` en el navegador.
- Revisa la consola de AWS > S3 para ver que los archivos nuevos estén en el bucket.
- Verifica el estado de la invalidación de CloudFront en la consola de AWS.
### API (EC2)
#### Revisar los logs del servicio
```bash
sudo journalctl -u proyectosacc-app.service -n 50 --no-pager
```
#### Revisar el archivo de log de la aplicación
```bash
tail -n 50 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log
```
#### Revisar que Nginx esté funcionando
```bash
sudo systemctl status nginx
```
#### Revisar que la API responda en el puerto correcto
```bash
ss -tlnp | grep 8080
```
---
## 4. Errores comunes y cómo arreglarlos
### Error 1: "Permission denied" al conectarse por SSH
**Causa**: la llave SSH no tiene los permisos correctos o no está en `authorized_keys`.
**Solución**:
```bash
# En tu computadora local
chmod 600 ~/.ssh/tu_llave.pem
# En el servidor, como thoth
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
```
---
### Error 2: "No such file or directory" al ejecutar deploy.sh
**Causa**: el script no está donde esperas o la ruta es incorrecta.
**Solución**:
```bash
# Busca el script
find /home/thoth -name "deploy.sh"
# Luego ejecútalo con la ruta completa
bash /home/thoth/deploy/setup/deploy.sh
```
---
### Error 3: El servicio no inicia (failed)
**Causa**: puede ser que falta Java, que el puerto 8080 ya está ocupado, o que hay un error en el archivo `.jar`.
**Solución**:
```bash
# Ver qué dice el log
sudo journalctl -u proyectosacc-app.service -n 100 --no-pager
# Ver si el puerto 8080 está ocupado
sudo lsof -i :8080
# Verificar que Java esté instalado
java -version
```
---
### Error 4: Health check falla después del deploy
**Causa**: la aplicación inició pero no responde correctamente, o tarda más de lo esperado.
**Solución**:
```bash
# Espera unos segundos más y vuelve a intentar
curl -v http://localhost:8080/actuator/health
# Revisa si hay errores en el log de la app
tail -n 100 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log
```
Si sigue fallando, considera ejecutar el **rollback** (ver [`10-runbook-rollback.md`](10-runbook-rollback.md)).
---
*Anterior: [`08-seguridad-y-secretos.md`](08-seguridad-y-secretos.md)*
*Siguiente: [`10-runbook-rollback.md`](10-runbook-rollback.md)*
+235
View File
@@ -0,0 +1,235 @@
# 10 - Runbook: Rollback
> Cómo regresar a la versión anterior de la aplicación cuando algo sale mal.
---
## 1. ¿Qué es un rollback?
Un **rollback** es el proceso de "deshacer" un despliegue. Si la nueva versión de la aplicación tiene errores, el rollback vuelve a poner la versión anterior para que los usuarios no se vean afectados.
> 💡 **Analogía simple**: es como cuando instalas una actualización en tu celular y algo deja de funcionar, así que regresas a la versión anterior del sistema.
---
## 2. ¿Cuándo hacer un rollback?
Haz rollback **inmediatamente** si ves alguna de estas situaciones:
- 🔴 El **health check** falla después del despliegue.
- 🔴 La aplicación se cae al poco tiempo de iniciar.
- 🟡 Los usuarios reportan errores graves que no existían antes.
- 🟡 El pipeline detectó un fallo en el paso de deploy.
- 🟠 El servicio de `systemd` no puede arrancar.
> ⚠️ **Regla de oro**: si dudas, haz rollback. Es mejor tener la versión anterior estable mientras investigas el problema.
---
## 3. Paso a paso: cómo hacer rollback
El rollback ahora tiene **dos partes**: el frontend en S3 y la API en la EC2.
---
### PARTE A: Rollback del frontend en S3
Si el problema es visual (interfaz rota, pantalla en blanco, etc.), restaura el frontend anterior.
#### Paso A1: Lista las versiones anteriores del archivo `index.html`
```bash
aws s3api list-object-versions \
--bucket ccsoft-proyectosacc-frontend \
--prefix index.html \
--query 'Versions[?IsLatest==`false`].[VersionId,LastModified]' \
--output table
```
#### Paso A2: Restaura la versión anterior
Copia el `VersionId` de la versión anterior y restáurala:
```bash
aws s3api copy-object \
--bucket ccsoft-proyectosacc-frontend \
--copy-source ccsoft-proyectosacc-frontend/index.html?versionId=<VERSION_ID> \
--key index.html
```
#### Paso A3: Invalida la caché de CloudFront
```bash
aws cloudfront create-invalidation \
--distribution-id <DISTRIBUTION_ID> \
--paths "/*"
```
#### Paso A4: Verifica el frontend
Abre `https://sacc.ccsoft.mx` y confirma que la interfaz anterior ha vuelto.
---
### PARTE B: Rollback de la API en la EC2
Si el problema es del backend (errores 500, API caída, etc.), restaura la API.
#### Paso B1: Conéctate al servidor EC2
```bash
ssh -i ~/.ssh/tu_llave.pem ubuntu@<IP_DE_LA_EC2>
```
#### Paso B2: Cambia al usuario thoth
```bash
sudo su - thoth
```
#### Paso B3: Ve al directorio de artefactos
```bash
cd /home/thoth/deploy/artifacts
```
#### Paso B4: Revisa los backups disponibles
```bash
ls -lah backup/
```
Verás algo como:
```
proyectosacc-app-20260414_103000.jar
proyectosacc-app-20260413_154500.jar
```
Cada archivo tiene una fecha y hora en su nombre.
#### Paso B5: Identifica cuál es la versión anterior estable
Normalmente es el backup **más reciente antes del deploy fallido**.
```bash
# Ordena por fecha para ver el más reciente
ls -lt backup/ | head -5
```
#### Paso B6: Detén el servicio actual
```bash
sudo systemctl stop proyectosacc-app.service
```
#### Paso B7: Reemplaza el archivo actual con el backup
```bash
# Guarda una copia del archivo fallido (por si necesitas investigarlo después)
mv current/proyectosacc-app.jar current/proyectosacc-app.jar.fallo-$(date +%Y%m%d_%H%M%S)
# Copia el backup al directorio current
cp backup/proyectosacc-app-20260414_103000.jar current/proyectosacc-app.jar
```
> 💡 **Tip**: reemplaza `20260414_103000` con la fecha real del backup que quieres restaurar.
#### Paso B8: Recarga systemd y reinicia el servicio
```bash
sudo systemctl daemon-reload
sudo systemctl start proyectosacc-app.service
```
#### Paso B9: Verifica que el servicio esté corriendo
```bash
sudo systemctl status proyectosacc-app.service
```
Debe decir `active (running)`.
#### Paso B10: Ejecuta el health check
```bash
curl http://localhost:8080/actuator/health
```
Debe responder:
```json
{"status":"UP"}
```
#### Paso B11: Verifica la API desde fuera
```bash
curl https://sacc.ccsoft.mx/api/actuator/health
```
Si todo funciona, el rollback fue exitoso.
---
## 4. Cómo verificar que el rollback funcionó
### Checklist de verificación (API)
- [ ] El servicio `systemd` está en estado `active (running)`.
- [ ] El health check responde `"status":"UP"`.
- [ ] El endpoint `https://sacc.ccsoft.mx/api/actuator/health` responde sin errores.
- [ ] Los usuarios pueden iniciar sesión y usar la aplicación.
- [ ] No hay errores críticos recientes en los logs.
### Checklist de verificación (Frontend)
- [ ] La página `https://sacc.ccsoft.mx` carga sin errores.
- [ ] La invalidación de CloudFront se completó.
- [ ] No hay errores de consola del navegador relacionados con assets faltantes.
### Comandos útiles para verificar
```bash
# Estado del servicio API
sudo systemctl status proyectosacc-app.service
# Health check local
curl -s http://localhost:8080/actuator/health | grep '"status"'
# Health check remoto
curl -s https://sacc.ccsoft.mx/api/actuator/health | grep '"status"'
# Últimos logs
tail -n 30 /var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log
# Verificar que Nginx sigue redirigiendo bien la API
sudo systemctl status nginx
# Verificar invalidación de CloudFront
aws cloudfront list-invalidations --distribution-id <DISTRIBUTION_ID>
```
---
## 5. Después del rollback
Una vez que la versión anterior esté estable:
1. **Investiga qué falló** en la nueva versión.
2. **Revisa los logs** del intento fallido.
3. **Notifica al equipo** por Telegram o el canal de comunicación que usen.
4. **Corrige el problema** en el código.
5. **Haz un nuevo deploy** solo cuando estés seguro de que el error está corregido.
### Mensaje de notificación sugerido
```
🚨 Rollback ejecutado en proyectosacc
Motivo: [describe brevemente el problema]
Estado actual: Aplicación estable con versión anterior
Próximo paso: Investigación del error en la nueva versión
```
---
*Anterior: [`09-runbook-deploy.md`](09-runbook-deploy.md)*
*Siguiente: [`11-checklist-primer-deploy.md`](11-checklist-primer-deploy.md)*
+146
View File
@@ -0,0 +1,146 @@
# 11 - Checklist: Primer Deploy
> Lista de verificación de todo lo que debe estar listo antes de hacer el primer despliegue de `proyectosacc`.
---
## 1. Infraestructura AWS
1. [ ] La instancia **EC2 T3.small** con **Ubuntu 22.04** está creada y encendida.
2. [ ] La **EC2** tiene un **Security Group** que permite:
- Puerto `22` (SSH) desde IPs confiables.
- Puerto `80` (HTTP) desde cualquier lugar (solo para la API).
- Puerto `443` (HTTPS) desde cualquier lugar (solo para la API).
3. [ ] La **RDS MariaDB** (`db.t3.micro`) está creada y accesible desde la VPC.
4. [ ] El **bucket S3** para artefactos de la API está creado y la EC2 tiene permisos para leer/escribir en él.
5. [ ] El **bucket S3 para el frontend React** está creado con **Static Website Hosting** habilitado.
6. [ ] El bucket S3 del frontend tiene **versionamiento de objetos** habilitado.
7. [ ] La **distribución CloudFront** está creada, apunta al bucket S3 del frontend, tiene HTTPS y un behavior `/api/*` que apunta a la EC2.
8. [ ] El registro **Route 53** `sacc.ccsoft.mx` apunta a **CloudFront** (no a la IP de la EC2).
9. [ ] El certificado **ACM** para `sacc.ccsoft.mx` está emitido, válido y adjunto a **CloudFront**.
10. [ ] El bucket S3 del frontend tiene un **bucket policy** que permite leer objetos solo a CloudFront (OAI/OAC).
---
## 2. Servidor EC2 (configuración interna)
11. [ ] El usuario `ubuntu` puede conectarse por SSH.
12. [ ] El usuario `thoth` está creado con su directorio `/home/thoth`.
13. [ ] El usuario `osiris` está creado con su directorio `/home/osiris`.
14. [ ] Los directorios de despliegue existen:
```
/home/thoth/deploy/artifacts/backup
/home/thoth/deploy/artifacts/current
/home/thoth/deploy/artifacts/logs
/home/thoth/deploy/artifacts/pids
```
15. [ ] **Nginx** está instalado, configurado como proxy inverso **solo para la API** (`/api` → `localhost:8080`) y corriendo.
16. [ ] **Java** (`openjdk-21-jdk`) está instalado.
17. [ ] **AWS CLI** está instalado y configurado.
18. [ ] Los permisos de carpetas están correctos (`thoth` y `osiris` pueden acceder donde deben).
---
## 3. Llaves SSH
15. [ ] Se generó el **par de llaves SSH dedicado** para `proyectosacc` (no se reutilizan llaves de otros proyectos).
16. [ ] La **llave privada** está codificada en base64 y guardada en **Bitbucket Repository Variables** (`PROYECTOSACC_SSH_KEY`).
17. [ ] La **llave pública** está en `/home/thoth/.ssh/authorized_keys` (inyectada vía Terraform `user_data`).
18. [ ] Se generó el par de llaves SSH para que `thoth` clone repositorios desde Bitbucket.
19. [ ] Las **Access Keys** correspondientes están configuradas en los repositorios de Bitbucket (`ci-cd-commons`, `ci-cd-saac4`).
20. [ ] Se probó la conexión SSH desde el pipeline (o manualmente) y funciona.
---
## 4. Base de datos
21. [ ] La base de datos RDS está accesible desde la EC2.
22. [ ] Se creó el esquema o las tablas necesarias para la aplicación.
23. [ ] La aplicación tiene configurada la URL de conexión a la base de datos.
24. [ ] Las credenciales de la base de datos están guardadas de forma segura (no en el código fuente).
---
## 5. Pipeline de Bitbucket
30. [ ] El archivo `bitbucket-pipelines.yml` está en la raíz del repositorio.
31. [ ] Los 7 pasos del pipeline están configurados correctamente:
- Paso 4 compila tanto el frontend React (`npm run build`) como la API backend.
- Paso 5 sube el frontend a S3 y el `.jar` de la API a S3.
- Paso 7 invalida la caché de CloudFront y reinicia la API.
32. [ ] Las **variables de entorno** están creadas en Bitbucket:
- `PROYECTOSACC_SSH_KEY`
- `PROYECTOSACC_SERVER_IP`
- `PROYECTOSACC_SSH_PORT`
- `PROYECTOSACC_SERVER_USER`
- `PROYECTOSACC_TELEGRAM_BOT_TOKEN`
- `PROYECTOSACC_TELEGRAM_CHAT_ID`
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `CLOUDFRONT_DISTRIBUTION_ID`
- `S3_FRONTEND_BUCKET`
33. [ ] Todas las variables sensibles están marcadas como **Secured** 🔒.
34. [ ] El pipeline puede clonar los repositorios necesarios (`ci-cd-commons`, `ci-cd-saac4`, etc.).
---
## 6. Scripts de despliegue
35. [ ] El script `deploy-frontend-s3.sh` existe y puede subir archivos a S3 + invalidar CloudFront.
36. [ ] El script `setup/deploy.sh` existe en el servidor y tiene permisos de ejecución (solo para la API).
37. [ ] El script `health-check.sh` existe y funciona correctamente.
38. [ ] El script `rollback.sh` existe y tiene al menos un backup de prueba de la API.
39. [ ] El script `notify-telegram.sh` envía mensajes de prueba exitosamente.
---
## 7. Aplicación SACC
40. [ ] El código fuente del **frontend React** compila sin errores (`npm run build`).
41. [ ] El código fuente de la **API backend** compila sin errores localmente.
42. [ ] El pipeline puede generar el artefacto final del frontend (`build/`) y de la API (`.jar`).
43. [ ] El artefacto de la API se puede ejecutar manualmente en la EC2.
44. [ ] La API responde en `http://localhost:8080/actuator/health` cuando corre localmente en la EC2.
---
## 8. Dominio y SSL
45. [ ] El dominio `sacc.ccsoft.mx` resuelve a **CloudFront** (verifica con `dig` o `nslookup`).
46. [ ] **CloudFront** sirve el frontend React por HTTPS sin errores de certificado.
47. [ ] **Nginx** en la EC2 redirige las peticiones de la API correctamente (`/api` → `localhost:8080`).
48. [ ] El endpoint `https://sacc.ccsoft.mx/api/actuator/health` responde correctamente.
49. [ ] Al entrar a `https://sacc.ccsoft.mx` se ve la interfaz de SACC (o al menos una página de prueba del frontend).
---
## 9. Seguridad
50. [ ] El archivo `.env` (si existe) está en `.gitignore`.
51. [ ] No hay contraseñas, tokens ni llaves privadas en el código del repositorio.
52. [ ] No se usa `sshpass` ni acceso SSH por contraseña.
53. [ ] El puerto SSH (22) del Security Group no está abierto a `0.0.0.0/0`.
54. [ ] La base de datos RDS no es pública (solo accesible desde la VPC).
55. [ ] El bucket S3 del frontend NO es público directamente; solo CloudFront accede a él (OAI/OAC).
56. [ ] Los logs sensibles no se imprimen en los archivos de configuración del pipeline.
---
## 10. Comunicación y documentación
57. [ ] El equipo de desarrollo sabe que el primer deploy se va a realizar.
58. [ ] Hay un canal de comunicación activo (Telegram, Slack, etc.) para alertas.
59. [ ] Este checklist ha sido revisado por al menos una persona más.
60. [ ] Se tiene planificado un horario de bajo tráfico para el primer deploy.
---
## ¿Listo?
Si marcaste **todas** las 60 casillas, estás listo para hacer el primer deploy. 🚀
Si te falta alguna, resuélvela antes de continuar. Es mejor tardar un poco más y hacerlo bien, que apurarse y dejar el servicio caído.
---
*Anterior: [`10-runbook-rollback.md`](10-runbook-rollback.md)*
+49
View File
@@ -0,0 +1,49 @@
# ===============================================================================================================
# proyectosacc-api.conf - Configuración de Nginx para API de proyectosacc
# Descripción:
# Proxy inverso EXCLUSIVO para la API backend. Nginx escucha en
# puerto 80 y redirige /api/ hacia localhost:8080.
# NO sirve el frontend React (eso es S3 + CloudFront).
#
# Uso:
# Copiar a /etc/nginx/sites-available/proyectosacc-api y crear
# enlace simbólico en sites-enabled.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Proxy inverso hacia la API backend en localhost:8080
location /api/ {
proxy_pass http://localhost:8080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering off;
}
# Cualquier otra ruta en la EC2 debe devolver 404
location / {
return 404;
}
# Página de error genérica
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# ===============================================================================================================
# deploy-frontend-s3.sh - Sincroniza el frontend React con S3
# Descripción:
# Sube la carpeta build/ al bucket S3 designado para el sitio estático.
# La invalidación de CloudFront se realiza en el pipeline (paso 7).
#
# Uso:
# S3_FRONTEND_BUCKET=ccsoft-proyectosacc-frontend bash deploy-frontend-s3.sh
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Validación de variables
# -------------------------------------------------------------------------------
S3_FRONTEND_BUCKET="${S3_FRONTEND_BUCKET:-}"
BUILD_DIR="${BUILD_DIR:-build}"
if [[ -z "$S3_FRONTEND_BUCKET" ]]; then
echo "[ERROR] Variable S3_FRONTEND_BUCKET no definida" >&2
exit 1
fi
if [[ ! -d "$BUILD_DIR" ]]; then
echo "[ERROR] Directorio ${BUILD_DIR} no encontrado" >&2
exit 1
fi
# -------------------------------------------------------------------------------
# Sincronización a S3
# -------------------------------------------------------------------------------
echo "[INFO] Sincronizando ${BUILD_DIR}/ a s3://${S3_FRONTEND_BUCKET}/ ..."
aws s3 sync "${BUILD_DIR}/" "s3://${S3_FRONTEND_BUCKET}/" --delete
echo "[INFO] Sincronización completada exitosamente"
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env bash
# ===============================================================================================================
# deploy.sh - Script de despliegue de la API backend en EC2
# Descripción:
# Despliega el artefacto .jar de la API en la EC2, gestiona backups,
# configura el servicio systemd y ejecuta health check.
#
# Uso:
# bash /home/thoth/deploy/setup/deploy.sh
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Configuración
# -------------------------------------------------------------------------------
readonly APP_NAME="proyectosacc-app"
readonly ARTIFACTS_DIR="/home/thoth/deploy/artifacts"
readonly CURRENT_DIR="${ARTIFACTS_DIR}/current"
readonly BACKUP_DIR="${ARTIFACTS_DIR}/backup"
readonly LOGS_DIR="/var/log/proyectosacc/${APP_NAME}"
readonly SYSTEMD_SERVICE="${APP_NAME}.service"
readonly HEALTH_ENDPOINT="http://localhost:8080/actuator/health"
readonly MAX_RETRIES=30
readonly RETRY_INTERVAL=2
# -------------------------------------------------------------------------------
# Colores y logging
# -------------------------------------------------------------------------------
readonly CLR_RED='\033[0;31m'
readonly CLR_GREEN='\033[0;32m'
readonly CLR_YELLOW='\033[1;33m'
readonly CLR_BLUE='\033[0;34m'
readonly CLR_NC='\033[0m'
_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; }
_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; }
_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; }
_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; }
_fail() { _log_error "$*"; exit 1; }
# -------------------------------------------------------------------------------
# Funciones
# -------------------------------------------------------------------------------
ensure_directories() {
_log_step "Verificando directorios de despliegue"
mkdir -p "${CURRENT_DIR}" "${BACKUP_DIR}" "${ARTIFACTS_DIR}/pids" "${LOGS_DIR}"
chown -R osiris:osiris "${ARTIFACTS_DIR}"
chmod 750 "${ARTIFACTS_DIR}"
_log_info "Directorios listos"
}
backup_current_version() {
_log_step "Creando backup de la versión actual"
local current_jar="${CURRENT_DIR}/${APP_NAME}.jar"
if [[ -f "$current_jar" ]]; then
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
local backup_name="${BACKUP_DIR}/${APP_NAME}-${timestamp}.jar"
cp "$current_jar" "$backup_name"
_log_info "Backup creado: ${backup_name}"
else
_log_warn "No hay versión actual para hacer backup"
fi
}
stop_service() {
_log_step "Deteniendo servicio actual"
if systemctl is-active --quiet "${SYSTEMD_SERVICE}"; then
sudo systemctl stop "${SYSTEMD_SERVICE}" || true
_log_info "Servicio detenido"
else
_log_warn "El servicio no estaba activo"
fi
}
create_systemd_service() {
_log_step "Configurando servicio systemd"
sudo tee "/etc/systemd/system/${SYSTEMD_SERVICE}" > /dev/null <<EOF
[Unit]
Description=Proyecto SACC App Service
After=network.target
[Service]
Type=simple
User=osiris
Group=osiris
WorkingDirectory=${CURRENT_DIR}
ExecStart=/usr/bin/java -jar ${CURRENT_DIR}/${APP_NAME}.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
StandardOutput=append:${LOGS_DIR}/${APP_NAME}-service.log
StandardError=append:${LOGS_DIR}/${APP_NAME}-service.log
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
_log_info "Servicio systemd actualizado"
}
start_service() {
_log_step "Iniciando servicio"
sudo systemctl enable "${SYSTEMD_SERVICE}" || true
sudo systemctl start "${SYSTEMD_SERVICE}"
_log_info "Servicio iniciado"
}
health_check() {
_log_step "Ejecutando health check"
local attempt=1
while [[ $attempt -le $MAX_RETRIES ]]; do
_log_info "Intento ${attempt}/${MAX_RETRIES}..."
if curl -sf "${HEALTH_ENDPOINT}" | grep -q '"status":"UP"'; then
_log_info "Health check PASÓ"
return 0
fi
sleep "${RETRY_INTERVAL}"
((attempt++))
done
_fail "Health check FALLÓ después de ${MAX_RETRIES} intentos"
}
save_pid() {
local pid
pid=$(systemctl show -p MainPID --value "${SYSTEMD_SERVICE}")
echo "$pid" > "${ARTIFACTS_DIR}/pids/${APP_NAME}.pid"
_log_info "PID guardado: ${pid}"
}
# -------------------------------------------------------------------------------
# Main
# -------------------------------------------------------------------------------
main() {
_log_step "Iniciando despliegue de ${APP_NAME}"
ensure_directories
backup_current_version
stop_service
create_systemd_service
start_service
health_check
save_pid
_log_info "Despliegue de ${APP_NAME} completado exitosamente"
}
main "$@"
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# ===============================================================================================================
# health-check.sh - Verifica que la API backend esté saludable
# Descripción:
# Realiza peticiones al endpoint de salud de la API con reintentos.
# Puede ejecutarse desde la EC2 localmente o desde el pipeline.
#
# Uso:
# HEALTH_URL=http://localhost:8080/actuator/health bash health-check.sh
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Configuración
# -------------------------------------------------------------------------------
HEALTH_URL="${HEALTH_URL:-http://localhost:8080/actuator/health}"
MAX_RETRIES="${MAX_RETRIES:-30}"
RETRY_INTERVAL="${RETRY_INTERVAL:-2}"
readonly CLR_RED='\033[0;31m'
readonly CLR_GREEN='\033[0;32m'
readonly CLR_YELLOW='\033[1;33m'
readonly CLR_BLUE='\033[0;34m'
readonly CLR_NC='\033[0m'
_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; }
_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; }
_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; }
_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; }
# -------------------------------------------------------------------------------
# Health Check
# -------------------------------------------------------------------------------
main() {
_log_step "Health Check: ${HEALTH_URL}"
local attempt=1
while [[ $attempt -le $MAX_RETRIES ]]; do
_log_info "Intento ${attempt}/${MAX_RETRIES}..."
if curl -sf "${HEALTH_URL}" | grep -q '"status":"UP"'; then
_log_info "Health check PASÓ - API saludable"
return 0
fi
if [[ $attempt -lt $MAX_RETRIES ]]; then
_log_warn "API no responde aún, reintentando en ${RETRY_INTERVAL}s..."
sleep "${RETRY_INTERVAL}"
fi
((attempt++))
done
_log_error "Health check FALLÓ después de ${MAX_RETRIES} intentos"
return 1
}
main "$@"
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# ===============================================================================================================
# notify-telegram.sh - Envía notificaciones a Telegram
# Descripción:
# Wrapper simple para enviar mensajes a un chat de Telegram.
# Requiere TELEGRAM_BOT_TOKEN y TELEGRAM_CHAT_ID como variables
# de entorno.
#
# Uso:
# bash notify-telegram.sh "Mensaje de prueba"
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Validación
# -------------------------------------------------------------------------------
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
MESSAGE="${1:-}"
if [[ -z "$TELEGRAM_BOT_TOKEN" ]]; then
echo "[ERROR] TELEGRAM_BOT_TOKEN no definido" >&2
exit 1
fi
if [[ -z "$TELEGRAM_CHAT_ID" ]]; then
echo "[ERROR] TELEGRAM_CHAT_ID no definido" >&2
exit 1
fi
if [[ -z "$MESSAGE" ]]; then
echo "[ERROR] Debe proporcionar un mensaje como primer argumento" >&2
exit 1
fi
# -------------------------------------------------------------------------------
# Envío de mensaje
# -------------------------------------------------------------------------------
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=${MESSAGE}" \
-d "parse_mode=Markdown" > /dev/null
echo "[INFO] Mensaje enviado a Telegram"
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env bash
# ===============================================================================================================
# rollback.sh - Rollback de la API backend y/o frontend de proyectosacc
# Descripción:
# Restaura la versión anterior de la API desde backup local en la EC2.
# Opcionalmente puede restaurar el frontend en S3 usando versionamiento.
#
# Uso:
# bash /home/thoth/deploy/scripts/rollback.sh [api|frontend|both]
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Configuración
# -------------------------------------------------------------------------------
ROLLBACK_TARGET="${1:-api}"
readonly APP_NAME="proyectosacc-app"
readonly ARTIFACTS_DIR="/home/thoth/deploy/artifacts"
readonly CURRENT_DIR="${ARTIFACTS_DIR}/current"
readonly BACKUP_DIR="${ARTIFACTS_DIR}/backup"
readonly SYSTEMD_SERVICE="${APP_NAME}.service"
readonly CLR_RED='\033[0;31m'
readonly CLR_GREEN='\033[0;32m'
readonly CLR_YELLOW='\033[1;33m'
readonly CLR_BLUE='\033[0;34m'
readonly CLR_NC='\033[0m'
_log_info() { echo -e "${CLR_GREEN}[INFO]${CLR_NC} $*"; }
_log_warn() { echo -e "${CLR_YELLOW}[WARN]${CLR_NC} $*" >&2; }
_log_error() { echo -e "${CLR_RED}[ERROR]${CLR_NC} $*" >&2; }
_log_step() { echo ""; echo -e "${CLR_BLUE}==== $* ====${CLR_NC}"; }
_fail() { _log_error "$*"; exit 1; }
# -------------------------------------------------------------------------------
# Rollback API
# -------------------------------------------------------------------------------
rollback_api() {
_log_step "Iniciando rollback de la API"
if [[ ! -d "$BACKUP_DIR" ]]; then
_fail "Directorio de backups no encontrado: ${BACKUP_DIR}"
fi
local latest_backup
latest_backup=$(ls -t "${BACKUP_DIR}/${APP_NAME}"-*.jar 2>/dev/null | head -n 1 || true)
if [[ -z "$latest_backup" ]]; then
_fail "No se encontraron backups de la API en ${BACKUP_DIR}"
fi
_log_info "Backup seleccionado: ${latest_backup}"
# Detener servicio actual
if systemctl is-active --quiet "${SYSTEMD_SERVICE}"; then
sudo systemctl stop "${SYSTEMD_SERVICE}" || true
_log_info "Servicio detenido"
fi
# Guardar versión fallida
local current_jar="${CURRENT_DIR}/${APP_NAME}.jar"
if [[ -f "$current_jar" ]]; then
mv "$current_jar" "${current_jar}.fallo-$(date +%Y%m%d_%H%M%S)"
fi
# Restaurar backup
cp "$latest_backup" "$current_jar"
chown osiris:osiris "$current_jar"
_log_info "Backup restaurado a ${current_jar}"
# Reiniciar servicio
sudo systemctl daemon-reload
sudo systemctl start "${SYSTEMD_SERVICE}"
_log_info "Servicio reiniciado con versión anterior"
# Health check
local attempt=1
local max_retries=30
local retry_interval=2
while [[ $attempt -le $max_retries ]]; do
_log_info "Health check intento ${attempt}/${max_retries}..."
if curl -sf http://localhost:8080/actuator/health | grep -q '"status":"UP"'; then
_log_info "Rollback de API exitoso - API saludable"
return 0
fi
sleep "$retry_interval"
((attempt++))
done
_fail "Rollback de API falló - Health check no pasó"
}
# -------------------------------------------------------------------------------
# Rollback Frontend
# -------------------------------------------------------------------------------
rollback_frontend() {
_log_step "Iniciando rollback del frontend en S3"
local bucket="${S3_FRONTEND_BUCKET:-}"
local distribution_id="${CLOUDFRONT_DISTRIBUTION_ID:-}"
if [[ -z "$bucket" ]]; then
_fail "Variable S3_FRONTEND_BUCKET no definida"
fi
_log_info "Listando versiones anteriores de index.html..."
local version_id
version_id=$(aws s3api list-object-versions \
--bucket "$bucket" \
--prefix index.html \
--query 'Versions[?IsLatest==`false`].VersionId' \
--output text 2>/dev/null | awk '{print $1}' || true)
if [[ -z "$version_id" ]]; then
_fail "No se encontró versión anterior de index.html en S3"
fi
aws s3api copy-object \
--bucket "$bucket" \
--copy-source "${bucket}/index.html?versionId=${version_id}" \
--key index.html
_log_info "index.html restaurado a versión anterior"
if [[ -n "$distribution_id" ]]; then
aws cloudfront create-invalidation \
--distribution-id "$distribution_id" \
--paths "/*"
_log_info "Invalidación de CloudFront creada"
else
_log_warn "CLOUDFRONT_DISTRIBUTION_ID no definido, omitiendo invalidación"
fi
}
# -------------------------------------------------------------------------------
# Main
# -------------------------------------------------------------------------------
main() {
case "$ROLLBACK_TARGET" in
api)
rollback_api
;;
frontend)
rollback_frontend
;;
both)
rollback_api
rollback_frontend
;;
*)
echo "Uso: $0 [api|frontend|both]"
exit 1
;;
esac
_log_info "Rollback completado"
}
main "$@"
+5
View File
@@ -0,0 +1,5 @@
bucket = "ccsoft-terraform-state-739086995772"
key = "proyectosacc/terraform.tfstate"
region = "mx-central-1"
encrypt = true
dynamodb_table = "terraform-locks-proyectosacc-prod"
+21
View File
@@ -0,0 +1,21 @@
# ===============================================================================================================
# backend.tf - Configuración del backend de estado para proyectosacc
# Descripción:
# Configura el backend remoto de Terraform en S3 para estado compartido.
#
# Uso:
# - DEV (default): terraform init
# - PROD: terraform init -backend-config=backend.prod.hcl
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
terraform {
backend "s3" {
bucket = "ccsoft-terraform-state"
key = "proyectosacc/terraform.tfstate"
region = "mx-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
+24
View File
@@ -0,0 +1,24 @@
# ===============================================================================================================
# dev.tfvars - Variables de ambiente develop para proyectosacc
# Descripción:
# Valores representativos para el ambiente de desarrollo.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
# Nota: mx-central-1 es una región opt-in. Debe estar habilitada en la cuenta de AWS antes del despliegue.
environment = "dev"
aws_region = "mx-central-1"
vpc_cidr = "10.1.0.0/16"
availability_zones = ["mx-central-1a", "mx-central-1b"]
ec2_instance_type = "t3.small"
ec2_key_name = "ccsoft-dev-key"
pipeline_public_key = "ssh-ed25519 AAAAC3NzaC... bitbucket.pipeline.ci.cd.proyectosacc.thoth.develop@computocontable.com"
db_instance_class = "db.t3.micro"
db_name = "sacc_db_dev"
db_username = "sacc_admin_dev"
db_password = "<cambiar-por-secret-real>"
s3_frontend_bucket = "ccsoft-proyectosacc-frontend-dev"
s3_artifacts_bucket = "ccsoft-proyectosacc-artifacts-dev"
cloudfront_price_class = "PriceClass_100"
+24
View File
@@ -0,0 +1,24 @@
# ===============================================================================================================
# prod.tfvars - Variables de ambiente producción para proyectosacc
# Descripción:
# Valores representativos para el ambiente de producción.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
# Nota: mx-central-1 es una región opt-in. Debe estar habilitada en la cuenta de AWS antes del despliegue.
environment = "prod"
aws_region = "mx-central-1"
vpc_cidr = "10.2.0.0/16"
availability_zones = ["mx-central-1a", "mx-central-1b"]
ec2_instance_type = "t3.small"
ec2_key_name = "ccsoft-prod-key"
pipeline_public_key = "ssh-ed25519 AAAAC3NzaC... bitbucket.pipeline.ci.cd.proyectosacc.thoth.prod@computocontable.com"
db_instance_class = "db.t3.micro"
db_name = "sacc_db_prod"
db_username = "sacc_admin_prod"
db_password = "<cambiar-por-secret-real>"
s3_frontend_bucket = "ccsoft-proyectosacc-frontend-prod"
s3_artifacts_bucket = "ccsoft-proyectosacc-artifacts-prod"
cloudfront_price_class = "PriceClass_100"
+576
View File
@@ -0,0 +1,576 @@
# ===============================================================================================================
# main.tf - Recursos principales de infraestructura para proyectosacc
# Descripción:
# Crea VPC, EC2, RDS, S3, CloudFront, Route 53, ACM e IAM para la
# arquitectura híbrida S3+CloudFront+EC2 de SACC.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
# -------------------------------------------------------------------------------
# VPC y Networking
# -------------------------------------------------------------------------------
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-vpc-${var.environment}"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw-${var.environment}"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-${var.availability_zones[count.index]}"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-${var.availability_zones[count.index]}"
}
}
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${var.environment}"
}
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.project_name}-nat-${var.environment}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-public-rt-${var.environment}"
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "${var.project_name}-private-rt-${var.environment}"
}
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# -------------------------------------------------------------------------------
# Security Groups
# -------------------------------------------------------------------------------
resource "aws_security_group" "ec2_api" {
name_prefix = "${var.project_name}-ec2-api-"
vpc_id = aws_vpc.main.id
description = "Security Group para la API backend de ${var.project_name}"
ingress {
description = "SSH desde IPs confiables"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # Ajustar a IP/VPN real del pipeline
}
ingress {
description = "HTTP API"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS API"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "API backend directo"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
description = "Salida libre"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-sg-ec2-api"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.project_name}-rds-"
vpc_id = aws_vpc.main.id
description = "Security Group para RDS MariaDB de ${var.project_name}"
ingress {
description = "MariaDB desde EC2 API"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.ec2_api.id]
}
egress {
description = "Salida libre"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-sg-rds"
}
lifecycle {
create_before_destroy = true
}
}
# -------------------------------------------------------------------------------
# IAM Role / Instance Profile para EC2
# -------------------------------------------------------------------------------
resource "aws_iam_role" "ec2_role" {
name = "${var.project_name}-ec2-role-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.project_name}-ec2-role"
}
}
resource "aws_iam_role_policy" "ec2_policy" {
name = "${var.project_name}-ec2-policy-${var.environment}"
role = aws_iam_role.ec2_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.artifacts.arn,
"${aws_s3_bucket.artifacts.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:*:*:*"
},
{
Effect = "Allow"
Action = [
"cloudfront:CreateInvalidation"
]
Resource = "*"
}
]
})
}
resource "aws_iam_instance_profile" "ec2_profile" {
name = "${var.project_name}-ec2-profile-${var.environment}"
role = aws_iam_role.ec2_role.name
tags = {
Name = "${var.project_name}-ec2-profile"
}
}
# -------------------------------------------------------------------------------
# EC2 (API Backend)
# -------------------------------------------------------------------------------
resource "aws_instance" "api" {
ami = var.ec2_ami
instance_type = var.ec2_instance_type
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.ec2_api.id]
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
key_name = var.ec2_key_name
root_block_device {
volume_type = "gp3"
volume_size = var.ec2_root_volume_size
encrypted = true
delete_on_termination = true
}
user_data = base64encode(templatefile("${path.module}/user-data.sh", {
pipeline_public_key = var.pipeline_public_key
}))
tags = {
Name = "${var.project_name}-api-${var.environment}"
Environment = var.environment
}
}
# -------------------------------------------------------------------------------
# RDS MariaDB
# -------------------------------------------------------------------------------
resource "aws_db_subnet_group" "rds" {
name = "${var.project_name}-rds-subnet-group-${var.environment}"
subnet_ids = aws_subnet.private[*].id
tags = {
Name = "${var.project_name}-rds-subnet-group"
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db-${var.environment}"
engine = var.db_engine
engine_version = var.db_engine_version
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.rds.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false
skip_final_snapshot = true
backup_retention_period = 7
tags = {
Name = "${var.project_name}-rds"
Environment = var.environment
}
}
# -------------------------------------------------------------------------------
# S3 Buckets
# -------------------------------------------------------------------------------
resource "aws_s3_bucket" "frontend" {
bucket = var.s3_frontend_bucket
tags = {
Name = "${var.project_name}-frontend"
}
}
resource "aws_s3_bucket_versioning" "frontend" {
bucket = aws_s3_bucket.frontend.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "frontend" {
bucket = aws_s3_bucket.frontend.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_website_configuration" "frontend" {
bucket = aws_s3_bucket.frontend.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
}
resource "aws_s3_bucket" "artifacts" {
bucket = var.s3_artifacts_bucket
tags = {
Name = "${var.project_name}-artifacts"
}
}
resource "aws_s3_bucket_versioning" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# -------------------------------------------------------------------------------
# CloudFront Origin Access Control
# -------------------------------------------------------------------------------
resource "aws_cloudfront_origin_access_control" "frontend" {
name = "${var.project_name}-oac-${var.environment}"
description = "OAC para bucket frontend de ${var.project_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_s3_bucket_policy" "frontend" {
bucket = aws_s3_bucket.frontend.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AllowCloudFrontOAC"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.frontend.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.main.arn
}
}
}]
})
depends_on = [aws_s3_bucket_public_access_block.frontend]
}
# -------------------------------------------------------------------------------
# ACM Certificate (us-east-1 obligatorio para CloudFront)
# -------------------------------------------------------------------------------
resource "aws_acm_certificate" "main" {
provider = aws.us_east_1
domain_name = var.domain_name
validation_method = "DNS"
tags = {
Name = "${var.project_name}-cert"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.main.zone_id
}
resource "aws_acm_certificate_validation" "main" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
# -------------------------------------------------------------------------------
# Route 53
# -------------------------------------------------------------------------------
data "aws_route53_zone" "main" {
name = "ccsoft.mx"
private_zone = false
}
resource "aws_route53_record" "main" {
zone_id = data.aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = false
}
}
# -------------------------------------------------------------------------------
# CloudFront Distribution
# -------------------------------------------------------------------------------
resource "aws_cloudfront_distribution" "main" {
enabled = true
is_ipv6_enabled = true
comment = "CDN para ${var.project_name}"
default_root_object = "index.html"
aliases = [var.domain_name]
price_class = var.cloudfront_price_class
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "saccS3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.frontend.id
}
origin {
domain_name = aws_instance.api.public_dns
origin_id = "saccApiOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "saccS3Origin"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
ordered_cache_behavior {
path_pattern = "/api/*"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "saccApiOrigin"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = true
headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"]
cookies {
forward = "all"
}
}
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.main.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
depends_on = [aws_acm_certificate_validation.main]
tags = {
Name = "${var.project_name}-cdn"
}
}
+57
View File
@@ -0,0 +1,57 @@
# ===============================================================================================================
# outputs.tf - Outputs de infraestructura para proyectosacc
# Descripción:
# Expone información útil de los recursos creados por Terraform.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
output "ec2_public_ip" {
description = "IP pública del servidor de la API"
value = aws_instance.api.public_ip
}
output "ec2_public_dns" {
description = "DNS público del servidor de la API"
value = aws_instance.api.public_dns
}
output "rds_endpoint" {
description = "Endpoint de conexión a la base de datos"
value = aws_db_instance.main.endpoint
}
output "s3_frontend_bucket" {
description = "Nombre del bucket S3 del frontend React"
value = aws_s3_bucket.frontend.bucket
}
output "s3_artifacts_bucket" {
description = "Nombre del bucket S3 de artefactos de la API"
value = aws_s3_bucket.artifacts.bucket
}
output "cloudfront_domain" {
description = "Dominio de la distribución CloudFront"
value = aws_cloudfront_distribution.main.domain_name
}
output "cloudfront_distribution_id" {
description = "ID de la distribución CloudFront"
value = aws_cloudfront_distribution.main.id
}
output "route53_record" {
description = "Registro DNS creado en Route 53"
value = aws_route53_record.main.name
}
output "acm_certificate_arn" {
description = "ARN del certificado SSL en ACM"
value = aws_acm_certificate.main.arn
}
output "vpc_id" {
description = "ID de la VPC creada"
value = aws_vpc.main.id
}
+47
View File
@@ -0,0 +1,47 @@
# ===============================================================================================================
# provider.tf - Configuración del proveedor AWS para proyectosacc
# Descripción:
# Define la región y versiones del provider AWS para Terraform.
#
# Uso:
# terraform init
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = var.project_name
ManagedBy = "terraform"
Environment = var.environment
}
}
}
# Provider exclusivo para ACM en us-east-1 (requerido por CloudFront)
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
default_tags {
tags = {
Project = var.project_name
ManagedBy = "terraform"
Environment = var.environment
}
}
}
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# ===============================================================================================================
# user-data.sh - Script de inicialización de la EC2 para proyectosacc
# Descripción:
# Configura la instancia EC2 al primer boot: instala dependencias,
# crea usuarios, configura Nginx como proxy de API, y prepara
# directorios de despliegue.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
set -euo pipefail
# -------------------------------------------------------------------------------
# Variables
# -------------------------------------------------------------------------------
PIPELINE_PUBLIC_KEY="${pipeline_public_key}"
# -------------------------------------------------------------------------------
# Actualizar sistema e instalar dependencias
# -------------------------------------------------------------------------------
apt-get update -y
apt-get install -y nginx openjdk-21-jdk awscli curl jq
# -------------------------------------------------------------------------------
# Crear usuarios del sistema
# -------------------------------------------------------------------------------
useradd -m -s /bin/bash thoth || true
useradd -m -s /bin/bash osiris || true
# -------------------------------------------------------------------------------
# Configurar SSH para el pipeline (usuario thoth)
# -------------------------------------------------------------------------------
mkdir -p /home/thoth/.ssh
chmod 700 /home/thoth/.ssh
echo "$PIPELINE_PUBLIC_KEY" > /home/thoth/.ssh/authorized_keys
chmod 600 /home/thoth/.ssh/authorized_keys
chown -R thoth:thoth /home/thoth/.ssh
# -------------------------------------------------------------------------------
# Crear estructura de directorios de despliegue
# -------------------------------------------------------------------------------
mkdir -p /home/thoth/deploy/artifacts/{backup,current,logs,pids}
mkdir -p /home/thoth/deploy/{scripts,setup}
chown -R thoth:thoth /home/thoth/deploy
mkdir -p /var/log/proyectosacc/proyectosacc-app
chown -R osiris:osiris /var/log/proyectosacc
# -------------------------------------------------------------------------------
# Configurar Nginx como proxy inverso SOLO para la API
# -------------------------------------------------------------------------------
cat > /etc/nginx/sites-available/proyectosacc-api <<'NGINX_EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location /api/ {
proxy_pass http://localhost:8080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location / {
return 404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
NGINX_EOF
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/proyectosacc-api /etc/nginx/sites-enabled/proyectosacc-api
nginx -t
systemctl enable nginx
systemctl restart nginx
# -------------------------------------------------------------------------------
# Crear servicio systemd template para la API (será sobrescrito por deploy.sh)
# -------------------------------------------------------------------------------
cat > /etc/systemd/system/proyectosacc-app.service <<'SYSTEMD_EOF'
[Unit]
Description=Proyecto SACC App Service
After=network.target
[Service]
Type=simple
User=osiris
Group=osiris
WorkingDirectory=/home/thoth/deploy/artifacts/current
ExecStart=/usr/bin/java -jar /home/thoth/deploy/artifacts/current/proyectosacc-app.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
StandardOutput=append:/var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log
StandardError=append:/var/log/proyectosacc/proyectosacc-app/proyectosacc-app-service.log
[Install]
WantedBy=multi-user.target
SYSTEMD_EOF
systemctl daemon-reload
systemctl enable proyectosacc-app.service || true
# -------------------------------------------------------------------------------
# Ajustar permisos finales
# -------------------------------------------------------------------------------
usermod -aG osiris thoth || true
chown -R osiris:osiris /home/thoth/deploy/artifacts
chmod 750 /home/thoth/deploy/artifacts
+148
View File
@@ -0,0 +1,148 @@
# ===============================================================================================================
# variables.tf - Variables de infraestructura para proyectosacc
# Descripción:
# Define todas las variables parametrizables de la infraestructura AWS.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
# -------------------------------------------------------------------------------
# Generales
# -------------------------------------------------------------------------------
variable "aws_region" {
description = "Región principal de AWS"
type = string
default = "mx-central-1"
}
variable "project_name" {
description = "Nombre del proyecto"
type = string
default = "proyectosacc"
}
variable "environment" {
description = "Ambiente de despliegue (dev, uat, prod)"
type = string
}
variable "domain_name" {
description = "Dominio principal de la aplicación"
type = string
default = "sacc.ccsoft.mx"
}
# -------------------------------------------------------------------------------
# Red
# -------------------------------------------------------------------------------
variable "vpc_cidr" {
description = "CIDR block de la VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "Zonas de disponibilidad a utilizar"
type = list(string)
default = ["mx-central-1a", "mx-central-1b"]
}
# -------------------------------------------------------------------------------
# EC2 (API Backend)
# -------------------------------------------------------------------------------
variable "ec2_instance_type" {
description = "Tipo de instancia EC2 para la API"
type = string
default = "t3.small"
}
variable "ec2_ami" {
description = "AMI de Ubuntu 22.04 LTS"
type = string
# AMI oficial de Ubuntu 22.04 LTS en mx-central-1 (validada: 2026-04-10)
default = "ami-09289f290e76061f8"
}
variable "ec2_root_volume_size" {
description = "Tamaño del volumen raíz en GB"
type = number
default = 20
}
variable "ec2_key_name" {
description = "Nombre del Key Pair SSH para acceso inicial (administrado externamente)"
type = string
default = null
}
variable "pipeline_public_key" {
description = "Llave pública SSH del pipeline CI/CD (usuario thoth)"
type = string
}
# -------------------------------------------------------------------------------
# RDS (Base de datos)
# -------------------------------------------------------------------------------
variable "db_instance_class" {
description = "Clase de instancia RDS"
type = string
default = "db.t3.micro"
}
variable "db_engine" {
description = "Motor de base de datos"
type = string
default = "mariadb"
}
variable "db_engine_version" {
description = "Versión del motor de base de datos"
type = string
default = "10.11"
}
variable "db_name" {
description = "Nombre de la base de datos inicial"
type = string
default = "sacc_db"
}
variable "db_username" {
description = "Usuario administrador de la base de datos"
type = string
default = "sacc_admin"
}
variable "db_password" {
description = "Contraseña del usuario administrador de la base de datos"
type = string
sensitive = true
}
variable "db_allocated_storage" {
description = "Almacenamiento asignado a RDS en GB"
type = number
default = 20
}
# -------------------------------------------------------------------------------
# S3
# -------------------------------------------------------------------------------
variable "s3_frontend_bucket" {
description = "Nombre del bucket S3 para el frontend React"
type = string
}
variable "s3_artifacts_bucket" {
description = "Nombre del bucket S3 para artefactos de la API"
type = string
}
# -------------------------------------------------------------------------------
# CloudFront / ACM
# -------------------------------------------------------------------------------
variable "cloudfront_price_class" {
description = "Clase de precio de CloudFront"
type = string
default = "PriceClass_100"
}