diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..09dddfd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,66 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg +*.egg-info/ +dist/ +build/ +*.log + +# Virtual environments +venv/ +.venv/ +env/ +ENV/ + +# Django +db.sqlite3 +*.sqlite3 +staticfiles/ +media/ + +# Environment files +.env +.env.* +!.env.docker.example + +# Git +.git/ +.gitignore +.gitattributes + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Docker +Dockerfile +Dockerfile.dev +docker-compose.yml +docker-compose.dev.yml +.dockerignore + +# Documentation +README.md +*.md +docs/ + +# CI/CD +.github/ +.gitlab-ci.yml + +# Testing +.coverage +htmlcov/ +.pytest_cache/ +.tox/ + +# OS +.DS_Store +Thumbs.db diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..3e505ad --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,129 @@ +# =================================================================== +# KeepItGoing Server - Docker Environment Configuration +# =================================================================== +# Copy this file to .env.docker and fill in your values +# DO NOT commit .env.docker to version control! + +# =================================================================== +# Django Core Settings +# =================================================================== + +# SECURITY WARNING: Generate a strong secret key! +# Generate with: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" +SECRET_KEY=CHANGE_ME_GENERATE_A_STRONG_SECRET_KEY_50_PLUS_CHARACTERS + +# Debug mode (MUST be False in production) +DEBUG=False + +# Comma-separated list of allowed hosts +# Example: api.yourdomain.com,localhost +ALLOWED_HOSTS=yourdomain.com,localhost + +# Comma-separated list of trusted origins for CSRF (must include https://) +# Example: https://api.yourdomain.com,https://yourdomain.com +CSRF_TRUSTED_ORIGINS=https://yourdomain.com + +# Domain used in email links +SITE_DOMAIN=yourdomain.com + +# =================================================================== +# Database Configuration (MariaDB) +# =================================================================== + +DB_NAME=keepitgoing +DB_USER=keepitgoing +DB_PASSWORD=CHANGE_ME_STRONG_DATABASE_PASSWORD +DB_ROOT_PASSWORD=CHANGE_ME_STRONG_ROOT_PASSWORD +DB_HOST=mariadb +DB_PORT=3306 + +# =================================================================== +# Redis Configuration +# =================================================================== + +REDIS_URL=redis://redis:6379/0 + +# =================================================================== +# Email Configuration +# =================================================================== +# Required for user registration (email verification) + +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend + +# Gmail Example (use App Password, not regular password): +# EMAIL_HOST=smtp.gmail.com +# EMAIL_PORT=587 +# EMAIL_USE_TLS=True +# EMAIL_HOST_USER=your-email@gmail.com +# EMAIL_HOST_PASSWORD=your-app-password + +# SendGrid Example: +# EMAIL_HOST=smtp.sendgrid.net +# EMAIL_PORT=587 +# EMAIL_USE_TLS=True +# EMAIL_HOST_USER=apikey +# EMAIL_HOST_PASSWORD=your-sendgrid-api-key + +# AWS SES Example: +# EMAIL_HOST=email-smtp.us-east-1.amazonaws.com +# EMAIL_PORT=587 +# EMAIL_USE_TLS=True +# EMAIL_HOST_USER=your-aws-access-key +# EMAIL_HOST_PASSWORD=your-aws-secret-key + +EMAIL_HOST=smtp.gmail.com +EMAIL_PORT=587 +EMAIL_USE_TLS=True +EMAIL_HOST_USER=your-email@gmail.com +EMAIL_HOST_PASSWORD=your-app-password +DEFAULT_FROM_EMAIL=KeepItGoing +SERVER_EMAIL=server@yourdomain.com + +# =================================================================== +# Email Verification Settings +# =================================================================== + +EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS=24 + +# =================================================================== +# CORS Configuration +# =================================================================== +# Comma-separated list of allowed origins +# Example: https://yourdomain.com,https://app.yourdomain.com + +CORS_ALLOWED_ORIGINS=https://yourdomain.com + +# =================================================================== +# SaaS Mode +# =================================================================== +# Set to True for SaaS deployment with billing/usage limits +# Set to False for self-hosted deployment (no limits) + +SAAS_MODE=False + +# =================================================================== +# Gunicorn Configuration +# =================================================================== + +GUNICORN_WORKERS=3 +GUNICORN_TIMEOUT=60 + +# =================================================================== +# Push Notifications (Optional) +# =================================================================== +# Leave empty if not using push notifications + +# Firebase Cloud Messaging (FCM) +FCM_CREDENTIALS_PATH= + +# Web Push (VAPID keys) +VAPID_PUBLIC_KEY= +VAPID_PRIVATE_KEY= +VAPID_ADMIN_EMAIL= + +# =================================================================== +# Additional Settings +# =================================================================== + +# Django settings module (usually don't need to change) +DJANGO_SETTINGS_MODULE=config.settings.selfhosted diff --git a/.gitignore b/.gitignore index d1273c7..224b6e8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ media/ # Environment .env +.env.docker *.env.local # IDE diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..21d0aac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +# =================================================================== +# Stage 1: Builder - Install dependencies +# =================================================================== +FROM python:3.12-slim-bookworm AS builder + +# Install build dependencies for mysqlclient and other packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + default-libmysqlclient-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY requirements.txt /tmp/ +RUN pip install --user --no-cache-dir -r /tmp/requirements.txt \ + && pip install --user --no-cache-dir mysqlclient gunicorn + +# =================================================================== +# Stage 2: Runtime - Minimal production image +# =================================================================== +FROM python:3.12-slim-bookworm + +# Install runtime dependencies only +RUN apt-get update && apt-get install -y --no-install-recommends \ + default-mysql-client \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Create django user (non-root for security) +RUN useradd -m -u 1000 -s /bin/bash django + +# Set working directory +WORKDIR /app + +# Copy Python dependencies from builder stage +COPY --from=builder /root/.local /home/django/.local + +# Copy application code +COPY --chown=django:django . /app/ + +# Create directories for media and static files +RUN mkdir -p /app/media /app/staticfiles && \ + chown -R django:django /app/media /app/staticfiles + +# Switch to django user +USER django + +# Add .local/bin to PATH for installed packages +ENV PATH=/home/django/.local/bin:$PATH + +# Set Python to run in unbuffered mode (better for Docker logs) +ENV PYTHONUNBUFFERED=1 + +# Expose port 8000 for Gunicorn +EXPOSE 8000 + +# Health check - ping the API endpoint +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl -f http://localhost:8000/api/ || exit 1 + +# Entrypoint script handles migrations, static collection, and service startup +ENTRYPOINT ["/app/docker/entrypoint.sh"] + +# Default command is to run Gunicorn +CMD ["gunicorn"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..a92f312 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,45 @@ +# =================================================================== +# Development Dockerfile - Fast builds with development tools +# =================================================================== +FROM python:3.12-slim-bookworm + +# Install build and runtime dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + default-libmysqlclient-dev \ + default-mysql-client \ + pkg-config \ + curl \ + vim \ + && rm -rf /var/lib/apt/lists/* + +# Create django user +RUN useradd -m -u 1000 -s /bin/bash django + +# Set working directory +WORKDIR /app + +# Install Python dependencies +# Note: In dev mode, code is mounted as volume, so we just install deps +COPY requirements.txt /tmp/ +RUN pip install --no-cache-dir -r /tmp/requirements.txt \ + && pip install --no-cache-dir mysqlclient gunicorn + +# Create directories +RUN mkdir -p /app/media /app/staticfiles && \ + chown -R django:django /app + +# Switch to django user +USER django + +# Set Python to run in unbuffered mode +ENV PYTHONUNBUFFERED=1 + +# Expose port 8000 +EXPOSE 8000 + +# Entrypoint script handles service startup +ENTRYPOINT ["/app/docker/entrypoint.sh"] + +# Default command for dev is Django runserver +CMD ["runserver"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..374d8db --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +.PHONY: help dev dev-build prod build deploy logs logs-web logs-celery shell migrate makemigrations createsuperuser test clean stop restart status backup + +# Default target +help: + @echo "KeepItGoing Server - Docker Commands" + @echo "=====================================" + @echo "" + @echo "Development:" + @echo " make dev - Start development environment with live reload" + @echo " make dev-build - Rebuild and start development environment" + @echo " make shell - Access Django shell" + @echo " make test - Run tests" + @echo "" + @echo "Production:" + @echo " make prod - Start production environment" + @echo " make build - Build production containers" + @echo " make deploy - Deploy to production (pull, build, migrate, restart)" + @echo "" + @echo "Database:" + @echo " make migrate - Run database migrations" + @echo " make makemigrations - Create new migrations" + @echo " make createsuperuser - Create Django superuser" + @echo " make backup - Backup database" + @echo " make dbshell - Access MariaDB shell" + @echo "" + @echo "Logs & Monitoring:" + @echo " make logs - View all container logs" + @echo " make logs-web - View web container logs" + @echo " make logs-celery - View celery worker logs" + @echo " make status - Show container status" + @echo "" + @echo "Control:" + @echo " make stop - Stop all containers" + @echo " make restart - Restart all containers" + @echo " make clean - Stop and remove containers, networks, volumes" + @echo "" + +# Development +dev: + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up + +dev-build: + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --build + +# Production +prod: + docker-compose up -d + +build: + docker-compose build --no-cache + +deploy: + @echo "Deploying to production..." + git pull + docker-compose build + docker-compose up -d + docker-compose exec web python manage.py migrate + docker-compose exec web python manage.py collectstatic --noinput + docker-compose restart web + @echo "Deployment complete!" + @make status + +# Database operations +migrate: + docker-compose exec web python manage.py migrate + +makemigrations: + docker-compose exec web python manage.py makemigrations + +createsuperuser: + docker-compose exec web python manage.py createsuperuser + +dbshell: + docker-compose exec mariadb mysql -u keepitgoing -p keepitgoing + +backup: + @echo "Creating database backup..." + @mkdir -p backups + docker-compose exec mariadb mysqldump -u root -p keepitgoing > backups/backup-$$(date +%Y%m%d-%H%M%S).sql + @echo "Backup created in backups/ directory" + +# Django shell and tests +shell: + docker-compose exec web python manage.py shell + +test: + docker-compose exec web python manage.py test + +# Logs +logs: + docker-compose logs -f + +logs-web: + docker-compose logs -f web + +logs-celery: + docker-compose logs -f celery-worker + +# Control +stop: + docker-compose down + +restart: + docker-compose restart + +status: + docker-compose ps + +clean: + docker-compose down -v + @echo "Containers, networks, and volumes removed" diff --git a/config/settings/selfhosted.py b/config/settings/selfhosted.py index d43d4a1..bc47dd6 100644 --- a/config/settings/selfhosted.py +++ b/config/settings/selfhosted.py @@ -6,6 +6,7 @@ Use this for users running their own instance. import os import sys +import dj_database_url from .base import * # Security @@ -28,17 +29,50 @@ if DEBUG: ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',') -# Database - PostgreSQL for self-hosted -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.environ.get('POSTGRES_DB', 'keepitgoing'), - 'USER': os.environ.get('POSTGRES_USER', 'keepitgoing'), - 'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'keepitgoing'), - 'HOST': os.environ.get('POSTGRES_HOST', 'db'), - 'PORT': os.environ.get('POSTGRES_PORT', '5432'), +# Database - Support both PostgreSQL and MariaDB/MySQL via DATABASE_URL +# DATABASE_URL format examples: +# PostgreSQL: postgresql://user:password@host:5432/dbname +# MariaDB/MySQL: mysql://user:password@host:3306/dbname +database_url = os.environ.get('DATABASE_URL') + +if database_url: + # Use DATABASE_URL for flexible database backend (PostgreSQL, MariaDB, MySQL, etc.) + DATABASES = { + 'default': dj_database_url.config( + default=database_url, + conn_max_age=600, + conn_health_checks=True, + ) } -} + + # Add MariaDB/MySQL specific options if using MySQL backend + if 'mysql' in database_url.lower(): + DATABASES['default']['OPTIONS'] = { + 'charset': 'utf8mb4', + 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", + } +else: + # Fallback to manual configuration (backwards compatibility) + # Supports both PostgreSQL and MariaDB/MySQL + db_engine = os.environ.get('DB_ENGINE', 'django.db.backends.postgresql') + + DATABASES = { + 'default': { + 'ENGINE': db_engine, + 'NAME': os.environ.get('DB_NAME', os.environ.get('POSTGRES_DB', 'keepitgoing')), + 'USER': os.environ.get('DB_USER', os.environ.get('POSTGRES_USER', 'keepitgoing')), + 'PASSWORD': os.environ.get('DB_PASSWORD', os.environ.get('POSTGRES_PASSWORD', 'keepitgoing')), + 'HOST': os.environ.get('DB_HOST', os.environ.get('POSTGRES_HOST', 'db')), + 'PORT': os.environ.get('DB_PORT', os.environ.get('POSTGRES_PORT', '5432')), + } + } + + # Add MariaDB/MySQL specific options if using MySQL backend + if 'mysql' in db_engine: + DATABASES['default']['OPTIONS'] = { + 'charset': 'utf8mb4', + 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", + } # Redis/Celery REDIS_URL = os.environ.get('REDIS_URL', 'redis://redis:6379/0') diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..32aa1a4 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,96 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}KeepItGoing Server - Deployment Script${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Check if .env.docker exists +if [ ! -f ".env.docker" ]; then + echo -e "${RED}ERROR: .env.docker file not found!${NC}" + echo -e "${YELLOW}Please copy .env.docker.example to .env.docker and configure it.${NC}" + exit 1 +fi + +# Step 1: Pull latest code +echo -e "${YELLOW}[1/6] Pulling latest code from git...${NC}" +if git pull; then + echo -e "${GREEN}✓ Code updated${NC}" +else + echo -e "${RED}✗ Git pull failed${NC}" + exit 1 +fi +echo "" + +# Step 2: Build containers +echo -e "${YELLOW}[2/6] Building Docker containers...${NC}" +if docker-compose build; then + echo -e "${GREEN}✓ Containers built${NC}" +else + echo -e "${RED}✗ Build failed${NC}" + exit 1 +fi +echo "" + +# Step 3: Start containers +echo -e "${YELLOW}[3/6] Starting containers...${NC}" +if docker-compose up -d; then + echo -e "${GREEN}✓ Containers started${NC}" +else + echo -e "${RED}✗ Failed to start containers${NC}" + exit 1 +fi +echo "" + +# Step 4: Wait for services to be ready +echo -e "${YELLOW}[4/6] Waiting for services to be ready...${NC}" +sleep 10 +echo -e "${GREEN}✓ Services ready${NC}" +echo "" + +# Step 5: Run migrations +echo -e "${YELLOW}[5/6] Running database migrations...${NC}" +if docker-compose exec -T web python manage.py migrate --noinput; then + echo -e "${GREEN}✓ Migrations complete${NC}" +else + echo -e "${RED}✗ Migrations failed${NC}" + exit 1 +fi +echo "" + +# Step 6: Collect static files +echo -e "${YELLOW}[6/6] Collecting static files...${NC}" +if docker-compose exec -T web python manage.py collectstatic --noinput; then + echo -e "${GREEN}✓ Static files collected${NC}" +else + echo -e "${RED}✗ Failed to collect static files${NC}" + exit 1 +fi +echo "" + +# Restart web service for good measure +echo -e "${YELLOW}Restarting web service...${NC}" +docker-compose restart web +echo -e "${GREEN}✓ Web service restarted${NC}" +echo "" + +# Show status +echo -e "${BLUE}========================================${NC}" +echo -e "${GREEN}Deployment Complete!${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" +echo -e "${YELLOW}Container Status:${NC}" +docker-compose ps +echo "" +echo -e "${YELLOW}Recent logs:${NC}" +docker-compose logs --tail=20 web +echo "" +echo -e "${GREEN}Deployment successful! ✓${NC}" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..5584d79 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,54 @@ +version: '3.8' + +# ================================================================= +# Development Overrides +# Use with: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up +# ================================================================= + +services: + # Web service overrides for development + web: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + # Mount source code for live reload + - ./:/app + # Override media and static with the same mounts + - ./media:/app/media + - static_files:/app/staticfiles + environment: + # Enable debug mode + DEBUG: "True" + # Use development settings (optional - can still use selfhosted) + DJANGO_SETTINGS_MODULE: config.settings.development + command: runserver + # Remove healthcheck in dev mode (optional) + healthcheck: + disable: true + + # Celery worker overrides for development + celery-worker: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + # Mount source code for live reload + - ./:/app + environment: + DEBUG: "True" + DJANGO_SETTINGS_MODULE: config.settings.development + + # Celery beat overrides for development + celery-beat: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + # Mount source code for live reload + - ./:/app + environment: + DEBUG: "True" + DJANGO_SETTINGS_MODULE: config.settings.development + +# Note: mariadb and redis services inherit from base docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..54cb275 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,148 @@ +version: '3.8' + +services: + # ================================================================= + # MariaDB Database Service + # ================================================================= + mariadb: + image: mariadb:11.2 + container_name: keepitgoing-db + restart: unless-stopped + environment: + MYSQL_DATABASE: ${DB_NAME:-keepitgoing} + MYSQL_USER: ${DB_USER:-keepitgoing} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_CHARACTER_SET_SERVER: utf8mb4 + MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci + volumes: + - db_data:/var/lib/mysql + networks: + - backend + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + # ================================================================= + # Redis Service - Message broker for Celery + # ================================================================= + redis: + image: redis:7-alpine + container_name: keepitgoing-redis + restart: unless-stopped + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + volumes: + - redis_data:/data + networks: + - backend + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # ================================================================= + # Django/Gunicorn Web Service + # ================================================================= + web: + build: + context: . + dockerfile: Dockerfile + container_name: keepitgoing-web + restart: unless-stopped + env_file: + - .env.docker + environment: + DJANGO_SETTINGS_MODULE: config.settings.selfhosted + DATABASE_URL: mysql://${DB_USER:-keepitgoing}:${DB_PASSWORD}@mariadb:3306/${DB_NAME:-keepitgoing} + REDIS_URL: redis://redis:6379/0 + volumes: + - ./media:/app/media + - static_files:/app/staticfiles + ports: + - "8000:8000" + networks: + - frontend + - backend + depends_on: + mariadb: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # ================================================================= + # Celery Worker Service - Background task processor + # ================================================================= + celery-worker: + build: + context: . + dockerfile: Dockerfile + container_name: keepitgoing-celery-worker + restart: unless-stopped + env_file: + - .env.docker + environment: + DJANGO_SETTINGS_MODULE: config.settings.selfhosted + DATABASE_URL: mysql://${DB_USER:-keepitgoing}:${DB_PASSWORD}@mariadb:3306/${DB_NAME:-keepitgoing} + REDIS_URL: redis://redis:6379/0 + networks: + - backend + depends_on: + mariadb: + condition: service_healthy + redis: + condition: service_healthy + command: celery-worker + + # ================================================================= + # Celery Beat Service - Scheduled task scheduler + # ================================================================= + celery-beat: + build: + context: . + dockerfile: Dockerfile + container_name: keepitgoing-celery-beat + restart: unless-stopped + env_file: + - .env.docker + environment: + DJANGO_SETTINGS_MODULE: config.settings.selfhosted + DATABASE_URL: mysql://${DB_USER:-keepitgoing}:${DB_PASSWORD}@mariadb:3306/${DB_NAME:-keepitgoing} + REDIS_URL: redis://redis:6379/0 + networks: + - backend + depends_on: + mariadb: + condition: service_healthy + redis: + condition: service_healthy + command: celery-beat + +# ================================================================= +# Named Volumes +# ================================================================= +volumes: + db_data: + name: keepitgoing_db_data + redis_data: + name: keepitgoing_redis_data + static_files: + name: keepitgoing_static_files + +# ================================================================= +# Networks +# ================================================================= +networks: + frontend: + name: keepitgoing_frontend + backend: + name: keepitgoing_backend diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..3552646 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,103 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}KeepItGoing Docker Entrypoint${NC}" +echo -e "${GREEN}========================================${NC}" + +# Extract database host from DATABASE_URL or use DB_HOST env var +DB_HOST=${DB_HOST:-mariadb} + +# Wait for MariaDB to be ready +echo -e "${YELLOW}Waiting for MariaDB at ${DB_HOST}...${NC}" +max_attempts=30 +attempt=0 + +while ! mysqladmin ping -h"${DB_HOST}" --silent 2>/dev/null; do + attempt=$((attempt + 1)) + if [ $attempt -eq $max_attempts ]; then + echo -e "${RED}ERROR: MariaDB did not become available in time${NC}" + exit 1 + fi + echo "Waiting for MariaDB... ($attempt/$max_attempts)" + sleep 2 +done + +echo -e "${GREEN}✓ MariaDB is ready!${NC}" + +# Function to run migrations +run_migrations() { + echo -e "${YELLOW}Running database migrations...${NC}" + python manage.py migrate --noinput + echo -e "${GREEN}✓ Migrations complete${NC}" +} + +# Function to collect static files +collect_static() { + echo -e "${YELLOW}Collecting static files...${NC}" + python manage.py collectstatic --noinput + echo -e "${GREEN}✓ Static files collected${NC}" +} + +# Determine which service to start based on command argument +case "$1" in + "gunicorn") + echo -e "${GREEN}Starting Gunicorn WSGI server...${NC}" + run_migrations + collect_static + + # Get Gunicorn config from environment or use defaults + WORKERS=${GUNICORN_WORKERS:-3} + TIMEOUT=${GUNICORN_TIMEOUT:-60} + + echo -e "${GREEN}Configuration:${NC}" + echo " Workers: $WORKERS" + echo " Timeout: $TIMEOUT" + echo " Bind: 0.0.0.0:8000" + + exec gunicorn config.wsgi:application \ + --bind 0.0.0.0:8000 \ + --workers $WORKERS \ + --timeout $TIMEOUT \ + --access-logfile - \ + --error-logfile - \ + --log-level info + ;; + + "celery-worker") + echo -e "${GREEN}Starting Celery worker...${NC}" + # Wait a bit more for web service to run migrations + sleep 5 + exec celery -A config worker \ + --loglevel=info \ + --concurrency=2 + ;; + + "celery-beat") + echo -e "${GREEN}Starting Celery beat scheduler...${NC}" + # Wait for web service to run migrations and create database tables + sleep 10 + exec celery -A config beat \ + --loglevel=info \ + --scheduler django_celery_beat.schedulers:DatabaseScheduler + ;; + + "runserver") + echo -e "${GREEN}Starting Django development server...${NC}" + run_migrations + collect_static + exec python manage.py runserver 0.0.0.0:8000 + ;; + + *) + # If command is not recognized, just execute it + echo -e "${YELLOW}Executing custom command: $@${NC}" + exec "$@" + ;; +esac diff --git a/requirements.txt b/requirements.txt index 47b4c19..184777d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,9 @@ djangorestframework>=3.14.0 djangorestframework-simplejwt>=5.3.0 # Database -psycopg2-binary>=2.9.9 -dj-database-url>=2.1.0 +psycopg2-binary>=2.9.9 # PostgreSQL driver +mysqlclient>=2.2.0 # MariaDB/MySQL driver +dj-database-url>=2.1.0 # Universal database URL parser # Task queue celery>=5.3.0