feat: Add Docker infrastructure for multi-tenant Django application with services for database, caching, and task processing.
This commit is contained in:
265
docker/entrypoint.sh
Normal file
265
docker/entrypoint.sh
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Ожидание готовности PostgreSQL
|
||||
wait_for_postgres() {
|
||||
echo "Waiting for PostgreSQL..."
|
||||
python -c "
|
||||
import psycopg2
|
||||
import os
|
||||
import sys
|
||||
|
||||
dbname = os.environ.get('DB_NAME', 'inventory_db')
|
||||
user = os.environ.get('DB_USER', 'postgres')
|
||||
password = os.environ.get('DB_PASSWORD', 'postgres')
|
||||
host = os.environ.get('DB_HOST', 'db')
|
||||
port = os.environ.get('DB_PORT', '5432')
|
||||
|
||||
print(f'Attempting connection to: host={host} port={port} dbname={dbname} user={user}')
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
dbname=dbname,
|
||||
user=user,
|
||||
password=password,
|
||||
host=host,
|
||||
port=port
|
||||
)
|
||||
conn.close()
|
||||
print('Connection successful!')
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(f'Error connecting to PostgreSQL: {e}', file=sys.stderr)
|
||||
exit(1)
|
||||
"
|
||||
while [ $? -ne 0 ]; do
|
||||
echo "PostgreSQL is unavailable - sleeping"
|
||||
sleep 2
|
||||
python -c "
|
||||
import psycopg2
|
||||
import os
|
||||
import sys
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
dbname=os.environ.get('DB_NAME', 'inventory_db'),
|
||||
user=os.environ.get('DB_USER', 'postgres'),
|
||||
password=os.environ.get('DB_PASSWORD', 'postgres'),
|
||||
host=os.environ.get('DB_HOST', 'db'),
|
||||
port=os.environ.get('DB_PORT', '5432')
|
||||
)
|
||||
conn.close()
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(f'Retry error: {e}', file=sys.stderr)
|
||||
exit(1)
|
||||
"
|
||||
done
|
||||
echo "PostgreSQL is up!"
|
||||
}
|
||||
|
||||
# Ожидание готовности Redis
|
||||
wait_for_redis() {
|
||||
echo "Waiting for Redis..."
|
||||
python -c "
|
||||
import redis
|
||||
import os
|
||||
import sys
|
||||
|
||||
host = os.environ.get('REDIS_HOST', 'redis')
|
||||
port = int(os.environ.get('REDIS_PORT', '6379'))
|
||||
db = int(os.environ.get('REDIS_DB', '0'))
|
||||
|
||||
print(f'Attempting connection to Redis: host={host} port={port} db={db}')
|
||||
|
||||
try:
|
||||
r = redis.Redis(host=host, port=port, db=db)
|
||||
r.ping()
|
||||
print('Redis connection successful!')
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(f'Error connecting to Redis: {e}', file=sys.stderr)
|
||||
exit(1)
|
||||
"
|
||||
while [ $? -ne 0 ]; do
|
||||
echo "Redis is unavailable - sleeping"
|
||||
sleep 2
|
||||
python -c "
|
||||
import redis
|
||||
import os
|
||||
import sys
|
||||
try:
|
||||
r = redis.Redis(
|
||||
host=os.environ.get('REDIS_HOST', 'redis'),
|
||||
port=int(os.environ.get('REDIS_PORT', '6379')),
|
||||
db=int(os.environ.get('REDIS_DB', '0'))
|
||||
)
|
||||
r.ping()
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(f'Redis retry error: {e}', file=sys.stderr)
|
||||
exit(1)
|
||||
"
|
||||
done
|
||||
echo "Redis is up!"
|
||||
}
|
||||
|
||||
# Создание папок media и staticfiles с правильными правами
|
||||
setup_directories() {
|
||||
echo "Setting up media and static directories..."
|
||||
|
||||
# Определяем пути (в Docker BASE_DIR = /app, поэтому MEDIA_ROOT = /app/myproject/media)
|
||||
MEDIA_ROOT="/app/myproject/media"
|
||||
STATIC_ROOT="/app/myproject/staticfiles"
|
||||
|
||||
# Создаем папки если их нет (рекурсивно)
|
||||
# Важно: создаем структуру папок для tenants
|
||||
mkdir -p "$MEDIA_ROOT/tenants" "$STATIC_ROOT" 2>/dev/null || true
|
||||
|
||||
# Пытаемся установить права доступа
|
||||
# Сначала меняем владельца на appuser (так как мы root)
|
||||
chown -R appuser:appuser "$MEDIA_ROOT" "$STATIC_ROOT" 2>/dev/null || true
|
||||
|
||||
# Используем 777 для папок media, чтобы контейнер мог писать независимо от прав на хосте
|
||||
# Это безопасно, так как доступ контролируется на уровне Docker volume
|
||||
# Устанавливаем права рекурсивно на все существующие файлы и папки
|
||||
find "$MEDIA_ROOT" -type d -exec chmod 777 {} \; 2>/dev/null || true
|
||||
find "$MEDIA_ROOT" -type f -exec chmod 666 {} \; 2>/dev/null || true
|
||||
chmod -R 755 "$STATIC_ROOT" 2>/dev/null || true
|
||||
|
||||
echo "Media directory created/checked: $MEDIA_ROOT (permissions set)"
|
||||
echo "Static directory created/checked: $STATIC_ROOT"
|
||||
}
|
||||
|
||||
# Применение миграций и создание суперпользователя
|
||||
run_migrations() {
|
||||
echo "Running migrations for shared apps..."
|
||||
gosu appuser python manage.py migrate_schemas --shared
|
||||
|
||||
echo "Running migrations for tenant schemas..."
|
||||
gosu appuser python manage.py migrate_schemas --tenant
|
||||
|
||||
echo "Collecting static files..."
|
||||
gosu appuser python manage.py collectstatic --noinput
|
||||
|
||||
# Устанавливаем права ПОСЛЕ collectstatic
|
||||
echo "Setting permissions on static files..."
|
||||
STATIC_ROOT="/app/myproject/staticfiles"
|
||||
find "$STATIC_ROOT" -type d -exec chmod 755 {} \; 2>/dev/null || true
|
||||
find "$STATIC_ROOT" -type f -exec chmod 644 {} \; 2>/dev/null || true
|
||||
|
||||
echo "Ensuring public tenant exists..."
|
||||
gosu appuser python /app/docker/create_public_tenant.py
|
||||
}
|
||||
|
||||
# Создание PlatformAdmin если не существует
|
||||
create_platform_admin() {
|
||||
echo "Creating PlatformAdmin if not exists..."
|
||||
python manage.py shell << EOF
|
||||
from platform_admin.models import PlatformAdmin
|
||||
import os
|
||||
|
||||
# Создаём PlatformAdmin из переменных окружения
|
||||
email = os.environ.get('PLATFORM_ADMIN_EMAIL', 'admin@platform.com')
|
||||
password = os.environ.get('PLATFORM_ADMIN_PASSWORD')
|
||||
name = os.environ.get('PLATFORM_ADMIN_NAME', 'Platform Admin')
|
||||
|
||||
if not password:
|
||||
print('WARNING: PLATFORM_ADMIN_PASSWORD not set. Skipping PlatformAdmin creation.')
|
||||
print('Create PlatformAdmin manually via Django shell:')
|
||||
print(' from platform_admin.models import PlatformAdmin')
|
||||
print(' PlatformAdmin.objects.create_superuser(email="...", name="...", password="...")')
|
||||
else:
|
||||
if not PlatformAdmin.objects.filter(email=email).exists():
|
||||
admin = PlatformAdmin.objects.create_superuser(
|
||||
email=email,
|
||||
name=name,
|
||||
password=password
|
||||
)
|
||||
print(f'PlatformAdmin {email} created successfully!')
|
||||
else:
|
||||
print(f'PlatformAdmin {email} already exists.')
|
||||
EOF
|
||||
}
|
||||
|
||||
# Если manage.py не в текущей директории, но есть в подпапке myproject
|
||||
if [ ! -f "manage.py" ] && [ -d "myproject" ]; then
|
||||
# Пытаемся войти в директорию, перенаправляя ошибки в /dev/null
|
||||
if cd myproject 2>/dev/null; then
|
||||
echo "Changing directory to myproject..."
|
||||
# Устанавливаем PYTHONPATH чтобы Python мог найти модуль myproject
|
||||
export PYTHONPATH=$(pwd):$PYTHONPATH
|
||||
echo "PYTHONPATH set to: $PYTHONPATH"
|
||||
else
|
||||
# Если не можем войти в директорию (проблема с правами), устанавливаем PYTHONPATH из текущей директории
|
||||
echo "Warning: Cannot access myproject directory (permission denied). Setting PYTHONPATH to include myproject..."
|
||||
export PYTHONPATH=/app/myproject:$PYTHONPATH
|
||||
echo "PYTHONPATH set to: $PYTHONPATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
web)
|
||||
wait_for_postgres
|
||||
wait_for_redis
|
||||
setup_directories
|
||||
run_migrations
|
||||
create_platform_admin
|
||||
echo "Starting Gunicorn..."
|
||||
exec gosu appuser gunicorn myproject.wsgi:application \
|
||||
--bind 0.0.0.0:8000 \
|
||||
--workers 3 \
|
||||
--threads 2 \
|
||||
--timeout 600 \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
--capture-output
|
||||
;;
|
||||
celery-worker)
|
||||
wait_for_postgres
|
||||
wait_for_redis
|
||||
setup_directories
|
||||
echo "Starting Celery Worker for photo processing and product import..."
|
||||
exec gosu appuser celery -A myproject worker \
|
||||
-l info \
|
||||
--concurrency=4 \
|
||||
-Q celery,photo_processing
|
||||
;;
|
||||
celery-beat)
|
||||
wait_for_postgres
|
||||
wait_for_redis
|
||||
echo "Starting Celery Beat..."
|
||||
exec gosu appuser celery -A myproject beat -l info
|
||||
;;
|
||||
migrate)
|
||||
wait_for_postgres
|
||||
# Миграции тоже запускаем от gosu
|
||||
gosu appuser python manage.py migrate_schemas --shared
|
||||
gosu appuser python manage.py migrate_schemas --tenant
|
||||
gosu appuser python manage.py collectstatic --noinput
|
||||
|
||||
# Права уже выставлены setup_directories (который запускается перед этим в case web/celery,
|
||||
# но для migrate мы можем вызвать его явно или просто поправить права на статику)
|
||||
# В данном блоке setup_directories не вызывался в оригинальном скрипте, но лучше вызвать если хотим гарантий
|
||||
# setup_directories
|
||||
|
||||
# Для migrate обычно важно просто создать схемы.
|
||||
create_platform_admin
|
||||
;;
|
||||
collectstatic)
|
||||
wait_for_postgres
|
||||
setup_directories
|
||||
echo "Collecting static files..."
|
||||
gosu appuser python manage.py collectstatic --noinput
|
||||
echo "Setting permissions on static files..."
|
||||
STATIC_ROOT="/app/myproject/staticfiles"
|
||||
find "$STATIC_ROOT" -type d -exec chmod 755 {} \; 2>/dev/null || true
|
||||
find "$STATIC_ROOT" -type f -exec chmod 644 {} \; 2>/dev/null || true
|
||||
echo "Static files collected and permissions set."
|
||||
;;
|
||||
shell)
|
||||
exec gosu appuser python manage.py shell
|
||||
;;
|
||||
*)
|
||||
exec "$@"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user