diff --git a/Dockerfile b/Dockerfile index aeee5654..4fab184f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,7 @@ RUN apt-get update -y && \ libxmlsec1-dev \ libxmlsec1-openssl \ libpq-dev \ + gosu \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -92,11 +93,26 @@ WORKDIR /home/mediacms.io/mediacms COPY config/imagemagick/policy.xml /etc/ImageMagick-6/policy.xml # Copy local_settings.py from config to cms/ for default Docker config (if exists) -RUN cp config/local_settings.py cms/local_settings.py 2>/dev/null || true +RUN if [ -f config/local_settings.py ]; then \ + cp config/local_settings.py cms/local_settings.py && \ + chown www-data:www-data cms/local_settings.py && \ + echo "Docker local_settings.py applied"; \ + else \ + echo "No config/local_settings.py found, using default settings"; \ + fi # Create www-data user directories and set permissions RUN mkdir -p /var/run/mediacms && \ - chown www-data:www-data /var/run/mediacms + chown -R www-data:www-data /home/mediacms.io/mediacms/logs \ + /home/mediacms.io/mediacms/media_files \ + /home/mediacms.io/mediacms/static_files \ + /var/run/mediacms + +# Copy and set up entrypoint script +COPY scripts/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] ############ WEB IMAGE (Django/uWSGI) ############ FROM base AS web @@ -107,8 +123,6 @@ RUN uv pip install uwsgi # Copy uWSGI configuration COPY config/uwsgi/uwsgi.ini /home/mediacms.io/mediacms/uwsgi.ini -USER www-data - EXPOSE 9000 CMD ["/home/mediacms.io/bin/uwsgi", "--ini", "/home/mediacms.io/mediacms/uwsgi.ini"] @@ -116,19 +130,13 @@ CMD ["/home/mediacms.io/bin/uwsgi", "--ini", "/home/mediacms.io/mediacms/uwsgi.i ############ WORKER IMAGE (Celery) ############ FROM base AS worker -USER www-data - # CMD will be overridden in docker-compose for different worker types ############ FULL WORKER IMAGE (Celery with extra codecs) ############ FROM worker AS worker-full -USER root - COPY requirements-full.txt ./ RUN mkdir -p /root/.cache/ && \ chmod go+rwx /root/ && \ chmod go+rwx /root/.cache/ && \ uv pip install -r requirements-full.txt - -USER www-data diff --git a/cms/settings.py b/cms/settings.py index 4cecca35..1217aa45 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -253,7 +253,7 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = "" CANNOT_ADD_MEDIA_MESSAGE = "User cannot add media, or maximum number of media uploads has been reached." # mp4hls command, part of Bento4 -MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls" +MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls" # highly experimental, related with remote workers ADMIN_TOKEN = "" @@ -370,41 +370,30 @@ FILE_UPLOAD_HANDLERS = [ "django.core.files.uploadhandler.TemporaryFileUploadHandler", ] -LOGS_DIR = os.path.join(BASE_DIR, "logs") - -error_filename = os.path.join(LOGS_DIR, "debug.log") -if not os.path.exists(LOGS_DIR): - try: - os.mkdir(LOGS_DIR) - except PermissionError: - pass - -if not os.path.isfile(error_filename): - open(error_filename, 'a').close() LOGGING = { "version": 1, "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, "handlers": { - "file": { - "level": "ERROR", - "class": "logging.FileHandler", - "filename": error_filename, - }, - }, - "loggers": { - "django": { - "handlers": ["file"], - "level": "ERROR", - "propagate": True, - }, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } }, + "root": {"level": "INFO", "handlers": ["console"]}, } -DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}} +DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "db", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}} -REDIS_LOCATION = "redis://127.0.0.1:6379/1" +REDIS_LOCATION = "redis://redis:6379/1" CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 8ab018e0..b2eeca76 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -1,4 +1,4 @@ -user www-data; +user nginx; worker_processes auto; pid /run/nginx.pid; diff --git a/config/uwsgi/uwsgi.ini b/config/uwsgi/uwsgi.ini index af7f103d..ee51a413 100644 --- a/config/uwsgi/uwsgi.ini +++ b/config/uwsgi/uwsgi.ini @@ -12,7 +12,7 @@ threads = 2 master = true -socket = 127.0.0.1:9000 +socket = 0.0.0.0:9000 workers = 2 diff --git a/docker-compose.yaml b/docker-compose.yaml index 1eecc76f..1c3102b2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: environment: ADMIN_USER: 'admin' ADMIN_EMAIL: 'admin@localhost' - # ADMIN_PASSWORD: 'uncomment_and_set_password_here' + ADMIN_PASSWORD: # ADMIN_PASSWORD: 'uncomment_and_set_password_here' restart: "no" depends_on: redis: @@ -51,7 +51,7 @@ services: celery_beat: image: mediacms/mediacms-worker:7.3 restart: unless-stopped - command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO"] + command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO", "--schedule=/home/mediacms.io/mediacms/logs/celerybeat-schedule"] depends_on: migrations: condition: service_completed_successfully diff --git a/files/urls.py b/files/urls.py index 07b81503..0df771cb 100644 --- a/files/urls.py +++ b/files/urls.py @@ -110,7 +110,7 @@ urlpatterns = [ re_path(r"^manage/users$", views.manage_users, name="manage_users"), # Media uploads in ADMIN created pages re_path(r"^tinymce/upload/", tinymce_handlers.upload_image, name="tinymce_upload_image"), - re_path("^(?P[\w.-]*)$", views.get_page, name="get_page"), # noqa: W605 + re_path(r"^(?P[\w.-]*)$", views.get_page, name="get_page"), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100644 index 00000000..db311320 --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +# Fix permissions on volume mounts +chown -R www-data:www-data \ + /home/mediacms.io/mediacms/logs \ + /home/mediacms.io/mediacms/media_files \ + /home/mediacms.io/mediacms/static_files \ + 2>/dev/null || true + +# Run as www-data user +exec gosu www-data "$@" diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100644 index 00000000..5df6eafc --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# Fix permissions on mounted volumes if running as root +if [ "$(id -u)" = "0" ]; then + echo "Fixing permissions on data directories..." + chown -R www-data:www-data /home/mediacms.io/mediacms/logs \ + /home/mediacms.io/mediacms/media_files \ + /home/mediacms.io/mediacms/static_files \ + /var/run/mediacms 2>/dev/null || true + + # If command starts with python or celery, run as www-data + if [ "${1:0:1}" != '-' ]; then + exec gosu www-data "$@" + fi +fi + +# Execute the command +exec "$@" diff --git a/scripts/run-migrations.sh b/scripts/run-migrations.sh index f324fdf1..fea2e2bd 100755 --- a/scripts/run-migrations.sh +++ b/scripts/run-migrations.sh @@ -5,18 +5,22 @@ echo "=========================================" echo "MediaCMS Migrations Starting..." echo "=========================================" -# Wait for database to be ready -until python manage.py migrate --check 2>/dev/null; do - echo "Waiting for database to be ready..." - sleep 2 -done +# Ensure virtualenv is activated +export VIRTUAL_ENV=/home/mediacms.io +export PATH="$VIRTUAL_ENV/bin:$PATH" + +# Use explicit python path from virtualenv +PYTHON="$VIRTUAL_ENV/bin/python" + +echo "Using Python: $PYTHON" +$PYTHON --version # Run migrations echo "Running database migrations..." -python manage.py migrate +$PYTHON manage.py migrate # Check if this is a new installation -EXISTING_INSTALLATION=$(echo "from users.models import User; print(User.objects.exists())" | python manage.py shell) +EXISTING_INSTALLATION=$(echo "from users.models import User; print(User.objects.exists())" | $PYTHON manage.py shell) if [ "$EXISTING_INSTALLATION" = "True" ]; then echo "Existing installation detected, skipping initial data load" @@ -24,14 +28,14 @@ else echo "New installation detected, loading initial data..." # Load fixtures - python manage.py loaddata fixtures/encoding_profiles.json - python manage.py loaddata fixtures/categories.json + $PYTHON manage.py loaddata fixtures/encoding_profiles.json + $PYTHON manage.py loaddata fixtures/categories.json # Create admin user - RANDOM_ADMIN_PASS=$(python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))") + RANDOM_ADMIN_PASS=$($PYTHON -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))") ADMIN_PASSWORD=${ADMIN_PASSWORD:-$RANDOM_ADMIN_PASS} - DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \ + DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD $PYTHON manage.py createsuperuser \ --no-input \ --username=${ADMIN_USER:-admin} \ --email=${ADMIN_EMAIL:-admin@localhost} \ @@ -44,7 +48,7 @@ fi # Collect static files echo "Collecting static files..." -python manage.py collectstatic --noinput +$PYTHON manage.py collectstatic --noinput echo "=========================================" echo "Migrations completed successfully!"