diff --git a/.env b/.env deleted file mode 100644 index 5be865a..0000000 --- a/.env +++ /dev/null @@ -1,10 +0,0 @@ -# Seguridad -DEBUG=True -SECRET_KEY=una-clave-muy-secreta-y-larga-123456 - -# Base de Datos (Conectando al PostgreSQL que instalamos) -DB_NAME=django_test -DB_USER=django_user -DB_PASSWORD=django_password -DB_HOST=db -DB_PORT=5432 \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index f8f4030..0000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -# Seguridad -DEBUG=True -SECRET_KEY=una-clave-muy-secreta-y-larga-123456 - -# Base de Datos (Conectando al PostgreSQL que instalamos) -DB_NAME=gitea -DB_USER=gitea -DB_PASSWORD=gitea -DB_HOST=gitea-db -DB_PORT=5432 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0bba40d..066dafe 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ postgres_data/ local_postgres_data/ *.pyc apps/backend_admin/migrations/0001_initial.py +# Bloquear todos los .env en cualquier carpeta +.env +**/core/.env +**/deployments/.env \ No newline at end of file diff --git a/apps/backend_admin/migrations/0002_alter_log_options.py b/apps/backend_admin/migrations/0002_alter_log_options.py new file mode 100644 index 0000000..ce7ca54 --- /dev/null +++ b/apps/backend_admin/migrations/0002_alter_log_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.3 on 2026-04-14 19:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('backend_admin', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='log', + options={'managed': False}, + ), + ] diff --git a/core/.env.example b/core/.env.example new file mode 100644 index 0000000..be7f52a --- /dev/null +++ b/core/.env.example @@ -0,0 +1,3 @@ +# core/.env +APP_CUSTOM_SETTING="Este es un valor privado de la app" +EXTERNAL_SERVICE_API_KEY="sk_test_12345" \ No newline at end of file diff --git a/core/settings.py b/core/settings.py index 13c8d2d..ae2e211 100644 --- a/core/settings.py +++ b/core/settings.py @@ -3,35 +3,30 @@ import os from dotenv import load_dotenv from datetime import timedelta -# Cargar variables desde el archivo .env -load_dotenv() +# 1. RUTA DEL SETTINGS Y CARGA DEL .ENV +# Obtenemos la ruta de la carpeta donde está este archivo (core/) +CURRENT_DIR = Path(__file__).resolve().parent +BASE_DIR = CURRENT_DIR.parent -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +# Cargamos el .env específico de esta carpeta (core/.env) +load_dotenv(dotenv_path=CURRENT_DIR / '.env') # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-change-me-for-production') +SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-default-key-change-it') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv('DEBUG', 'True').lower() == 'true' - -# Constants from .env -DB_NAME = os.getenv('DB_NAME') -DB_USER = os.getenv('DB_USER') -DB_PASSWORD = os.getenv('DB_PASSWORD') -DB_HOST = os.getenv('DB_HOST') -DB_PORT = os.getenv('DB_PORT') +# Mejoramos el parseo de DEBUG para que no falle si viene como string +DEBUG = os.getenv('DEBUG', 'True').lower() in ('true', '1', 't') +# 2. ALLOWED HOSTS +# Limpiamos y centralizamos los hosts permitidos ALLOWED_HOSTS = [ 'v-encore-lab.com', 'dev.v-encore-lab.com', - '185.187.169.109', # Añade la IP aquí + '185.187.169.109', 'localhost', '127.0.0.1', - 'django_app_dev', - 'django_app_master', - 'rest_framework', - 'rest_framework.authtoken' + os.getenv('APP_CONTAINER_NAME', 'django_app_dev'), # Dinámico para Docker ] # Application definition @@ -42,12 +37,16 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + # Plugins 'rest_framework', 'rest_framework.authtoken', + 'corsheaders', + + # Tus Apps (Asegúrate de que el path sea correcto) 'apps.promociones', 'apps.backend_admin', 'apps.common', - 'corsheaders', ] MIDDLEWARE = [ @@ -81,15 +80,16 @@ TEMPLATES = [ WSGI_APPLICATION = 'core.wsgi.application' -# Database +# 3. DATABASE +# Extraemos con fallback por si el .env falla DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': DB_NAME, - 'USER': DB_USER, - 'PASSWORD': DB_PASSWORD, - 'HOST': DB_HOST, - 'PORT': DB_PORT, + 'NAME': os.getenv('DB_NAME', 'postgres'), + 'USER': os.getenv('DB_USER', 'postgres'), + 'PASSWORD': os.getenv('DB_PASSWORD', ''), + 'HOST': os.getenv('DB_HOST', 'localhost'), + 'PORT': os.getenv('DB_PORT', '5432'), } } @@ -99,7 +99,7 @@ TIME_ZONE = 'Europe/Madrid' USE_I18N = True USE_TZ = True -# Static files (CSS, JavaScript, Images) +# Static files STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles' @@ -120,27 +120,43 @@ SIMPLE_JWT = { "REFRESH_TOKEN_LIFETIME": timedelta(days=1), } -# CORS -CORS_ALLOW_ALL_ORIGINS = True +# CORS & CSRF +CORS_ALLOW_ALL_ORIGINS = DEBUG # Solo permitir todo en modo DEBUG CSRF_TRUSTED_ORIGINS = [ - 'chrome-extension://amknoiejhlmhancpahfcfcfhllgkpbld', + 'https://v-encore-lab.com', 'http://localhost:8000', 'http://127.0.0.1:8000', ] -# Logging +# Logging simplificado LOGGING = { 'version': 1, 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {message}', + 'style': '{', + }, + }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', + 'formatter': 'verbose', }, }, 'loggers': { '': { 'handlers': ['console'], - 'level': 'INFO', + 'level': os.getenv('LOG_LEVEL', 'INFO'), }, }, -} \ No newline at end of file +} + +# --- CONFIGURACIONES PERSONALIZADAS DE LA APP --- + +# Leemos la variable del .env (cargado previamente con load_dotenv) +# Ponemos un valor por defecto por si se nos olvida ponerlo en el .env +APP_CUSTOM_SETTING = os.getenv('APP_CUSTOM_SETTING', 'valor_por_defecto_seguro') + +# Ejemplo de otra variable de API +EXTERNAL_SERVICE_API_KEY = os.getenv('EXTERNAL_SERVICE_API_KEY', None) \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 933e1e8..e3b2ea1 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/deployments/.env.example b/deployments/.env.example new file mode 100644 index 0000000..a71f24d --- /dev/null +++ b/deployments/.env.example @@ -0,0 +1,34 @@ +# ================================================================= +# 🏗️ INFRAESTRUCTURA (Docker & Jenkins) +# ================================================================= +# Nombres que tomarán los contenedores en Docker +APP_CONTAINER_NAME= +DB_CONTAINER_NAME= + +# Nombre del proyecto para Docker Compose (usado en Jenkins) +PROJECT_NAME= + +# Puertos que se abrirán al exterior (Host) +PORT= +DATABASE_EXPOSE_PORT= + +# ================================================================= +# 🐍 CONFIGURACIÓN DE DJANGO +# ================================================================= +# True para desarrollo, False para producción +DEBUG= +# Genera una clave segura: https://djecrety.ir/ +SECRET_KEY= +# Lista separada por comas: localhost,127.0.0.1,tudominio.com +ALLOWED_HOSTS= + +# ================================================================= +# 🗄️ BASE DE DATOS (PostgreSQL) +# ================================================================= +DB_NAME= +DB_USER= +DB_PASSWORD= + +# El HOST debe ser "db" cuando se usa Docker Compose +DB_HOST= +DB_PORT= \ No newline at end of file diff --git a/deployments/Dockerfile b/deployments/Dockerfile index af963c3..fd3fd91 100644 --- a/deployments/Dockerfile +++ b/deployments/Dockerfile @@ -4,27 +4,31 @@ FROM python:3.12-slim # Evitar que Python genere archivos .pyc y que el buffer se sature ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 -# Definimos la zona horaria como variable de entorno ENV TZ=Europe/Madrid + # Directorio de trabajo WORKDIR /app -# Instalar dependencias del sistema necesarias +# Instalar dependencias del sistema necesarias y limpiar caché en un solo paso RUN apt-get update && apt-get install -y \ libpq-dev \ gcc \ gettext \ && rm -rf /var/lib/apt/lists/* -# Instalar dependencias de Python -COPY requirements.txt /app/ +# Copiar dependencias (Ajustado: asumiendo que están en deployments/) +COPY deployments/requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt -# Copiar el resto del código +# Copiar el resto del código del proyecto +# El contexto de docker-compose suele ser la raíz, por lo que copiamos todo a /app COPY . /app/ # Exponer el puerto de Django EXPOSE 8000 -# Comando por defecto para arrancar (usaremos manage.py en dev y gunicorn en prod) -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file +# Script de entrada (lo crearemos en el siguiente paso) +COPY deployments/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/deployments/Jenkinsfile b/deployments/Jenkinsfile index 7bb4a3a..fcd0924 100644 --- a/deployments/Jenkinsfile +++ b/deployments/Jenkinsfile @@ -5,37 +5,55 @@ pipeline { stage('Configurar Entorno') { steps { script { + // Seleccionamos ID de credencial y config según la rama if (env.BRANCH_NAME == 'master') { env.PROJECT_NAME = "django_master" - env.CONTAINER_NAME = "django_app_master" + env.APP_CONTAINER_NAME = "django_app_master" env.PORT = "8001" env.DEBUG_MODE = "0" + env.ENV_CREDENTIAL_ID = "2" // Tu ID para master } else { env.PROJECT_NAME = "django_dev" - env.CONTAINER_NAME = "django_app_dev" + env.APP_CONTAINER_NAME = "django_app_dev" env.PORT = "8000" env.DEBUG_MODE = "1" + env.ENV_CREDENTIAL_ID = "1" // Tu ID para dev } } } } - stage('Despliegue') { + stage('Fase Final: Containerización') { when { anyOf { branch 'dev'; branch 'master' } } steps { - echo "DESPLEGANDO: ${env.CONTAINER_NAME} en el puerto ${env.PORT}" - - // CAMBIAMOS docker-compose (guion) por docker compose (espacio) - sh """ - CONTAINER_NAME=${env.CONTAINER_NAME} \ - PORT=${env.PORT} \ - DEBUG_MODE=${env.DEBUG_MODE} \ - docker compose -p ${env.PROJECT_NAME} -f deployments/docker-compose.yml up -d --build web - """ - - echo "Ejecutando migraciones en ${env.CONTAINER_NAME}..." - sh "docker exec ${env.CONTAINER_NAME} python manage.py migrate --noinput" + // Bloque mágico: extrae el archivo .env de la bóveda de Jenkins + withCredentials([file(credentialsId: env.ENV_CREDENTIAL_ID, variable: 'SECRET_ENV')]) { + sh """ + echo "Copiando configuración segura..." + cp \$SECRET_ENV deployments/.env + + echo "🚀 DESPLEGANDO: ${env.APP_CONTAINER_NAME} en puerto ${env.PORT}" + + export APP_CONTAINER_NAME=${env.APP_CONTAINER_NAME} + export PORT=${env.PORT} + export DEBUG_MODE=${env.DEBUG_MODE} + + docker compose -p ${env.PROJECT_NAME} -f deployments/docker-compose.yml up -d --build web + """ + } } } } + + post { + success { + echo "✅ Despliegue completado con éxito." + // Limpieza preventiva: borramos el .env físico después del despliegue + sh "rm -f deployments/.env" + } + failure { + echo "❌ Error en el despliegue. Revisa los logs." + sh "rm -f deployments/.env" + } + } } \ No newline at end of file diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 4d2c46c..a3cdb79 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -1,36 +1,42 @@ services: db: image: postgres:15 - container_name: django_db_local - # Cargamos el archivo directamente - env_file: - - ../.env + container_name: ${DB_CONTAINER_NAME:-django_db_local} + restart: always + # Solo usamos el .env de esta carpeta + env_file: .env environment: - # POSTGRES_DB espera estas variables exactas, - # así que las mapeamos a lo que tienes en tu .env - POSTGRES_DB=${DB_NAME} - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} ports: - - "5432:5432" + - "${DATABASE_EXPOSE_PORT:-5432}:5432" volumes: - local_postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] + interval: 5s + timeout: 5s + retries: 5 web: - build: . - container_name: django_app_dev + build: + context: .. + dockerfile: deployments/Dockerfile + container_name: ${APP_CONTAINER_NAME:-django_app_dev} + restart: always volumes: - ..:/app - # Cargamos el archivo directamente aquí también - env_file: - - ../.env + ports: + - "${PORT:-8000}:8000" + # Inyectamos el .env local al contenedor web + env_file: .env environment: - DB_HOST=db - DB_PORT=5432 - ports: - - "8000:8000" depends_on: - - db + db: + condition: service_healthy volumes: local_postgres_data: \ No newline at end of file diff --git a/deployments/entrypoint.sh b/deployments/entrypoint.sh new file mode 100644 index 0000000..115690f --- /dev/null +++ b/deployments/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Salir inmediatamente si un comando falla +set -e + +echo "--> Ejecutando migraciones..." +python manage.py makemigrations --noinput +python manage.py migrate --noinput + +# En el futuro, aquí podrías añadir: +# python manage.py collectstatic --noinput + +echo "--> Arrancando el servidor Django..." +# Usamos exec para que Django sea el proceso principal (PID 1) y reciba señales de Docker +exec python manage.py runserver 0.0.0.0:8000 \ No newline at end of file