Compare commits

..

155 Commits

Author SHA1 Message Date
Markos Gogoulos
48537515cb changes 2026-01-30 16:14:55 +02:00
Markos Gogoulos
e6db138d11 xif 2026-01-30 15:17:22 +02:00
Markos Gogoulos
2f2d32f0db fix 2026-01-30 13:36:41 +02:00
Markos Gogoulos
f4d3439246 fix 2026-01-30 13:31:47 +02:00
Markos Gogoulos
7fe9891942 wtv 2026-01-30 13:30:32 +02:00
Markos Gogoulos
9eb8a1ad62 plug 2026-01-30 13:04:22 +02:00
Markos Gogoulos
23ee0dc7cc new 2026-01-30 13:01:21 +02:00
Markos Gogoulos
e5be39f392 feat: make S3 bucket for Terraform unique 2026-01-30 11:58:57 +02:00
Markos Gogoulos
f0c084fa53 wtv 2026-01-30 11:55:15 +02:00
Markos Gogoulos
571bfcc4ce wtv 2026-01-29 16:36:57 +02:00
Markos Gogoulos
c04380af47 w 2026-01-29 16:09:01 +02:00
Markos Gogoulos
97741f780e wtv 2026-01-29 16:03:35 +02:00
Markos Gogoulos
78cce0eb10 wtv 2026-01-29 16:01:34 +02:00
Markos Gogoulos
472b3029c4 wtv 2026-01-29 15:58:07 +02:00
Markos Gogoulos
343f1e7009 wtv 2026-01-29 15:49:11 +02:00
Markos Gogoulos
8c78b67b0c wtv 2026-01-29 15:40:33 +02:00
Markos Gogoulos
29fc7fb861 wtv 2026-01-29 15:37:41 +02:00
Markos Gogoulos
b03a33d93e wtv 2026-01-29 15:31:44 +02:00
Markos Gogoulos
64472be406 wtv 2026-01-29 15:27:49 +02:00
Markos Gogoulos
cc0f4d4645 new 2026-01-29 15:18:15 +02:00
Markos Gogoulos
095e4d2cb4 latest 2026-01-29 14:58:44 +02:00
Markos Gogoulos
5c8978453e a 2026-01-29 14:52:48 +02:00
Markos Gogoulos
83189076e4 a 2026-01-29 14:42:44 +02:00
Markos Gogoulos
ca6dbf3740 rev 2026-01-29 12:57:40 +02:00
Markos Gogoulos
8646bd70dc wtv 2026-01-29 12:49:45 +02:00
Markos Gogoulos
1f493c8e15 wtv 2026-01-29 12:49:37 +02:00
Markos Gogoulos
e11cb7ea6e wtv 2026-01-29 12:45:04 +02:00
Markos Gogoulos
3131e76ef7 this 2026-01-29 12:30:57 +02:00
Markos Gogoulos
809cdccc42 remove 2026-01-29 12:30:06 +02:00
Markos Gogoulos
ed36240f45 wtv 2026-01-28 22:46:27 +02:00
Markos Gogoulos
77bafff6f6 wtv 2026-01-28 22:44:22 +02:00
Markos Gogoulos
f6252f4f77 wtv 2026-01-28 22:40:48 +02:00
Markos Gogoulos
764580287f tv 2026-01-28 22:36:47 +02:00
Markos Gogoulos
ce6c9a0a3c tv 2026-01-28 22:35:02 +02:00
Markos Gogoulos
1ced023a07 tv 2026-01-28 22:30:13 +02:00
Markos Gogoulos
981fec296c tv 2026-01-28 22:27:19 +02:00
Markos Gogoulos
40cd7916e7 this 2026-01-28 22:25:33 +02:00
Yiannis Christodoulou
bcef59c3a9 Update select_media.html 2026-01-28 17:19:15 +02:00
Markos Gogoulos
e93c8225c4 d 2026-01-25 11:37:26 +02:00
Markos Gogoulos
5c3c33ca84 this 2026-01-25 10:26:58 +02:00
Markos Gogoulos
7a954e7a3d this 2026-01-25 10:18:00 +02:00
Markos Gogoulos
8610df0c2b this 2026-01-16 13:45:06 +02:00
Markos Gogoulos
8ab9030d14 this 2026-01-16 13:44:46 +02:00
Markos Gogoulos
15c8dec041 you 2026-01-16 13:44:04 +02:00
Markos Gogoulos
9af4686bd4 static 2026-01-10 15:59:44 +02:00
Markos Gogoulos
bcc8a0858c droplist for actions 2026-01-10 15:58:16 +02:00
Markos Gogoulos
549b672d48 add friendly_token in indexing 2026-01-10 15:30:03 +02:00
Markos Gogoulos
abe950f1da w 2026-01-09 13:42:36 +02:00
Markos Gogoulos
5fecda02d6 w 2026-01-09 13:40:20 +02:00
Markos Gogoulos
3c6f8c102c same window 2026-01-09 13:37:18 +02:00
Markos Gogoulos
2d28520cd4 Merge branch 'main' into feat-lti-integration 2026-01-09 13:29:52 +02:00
Markos Gogoulos
4bd56da2d8 push 2026-01-09 13:29:18 +02:00
Markos Gogoulos
fdfa857794 fix 2026-01-08 17:44:44 +02:00
Markos Gogoulos
2c1f27c0be remove 2026-01-08 17:30:33 +02:00
Markos Gogoulos
2f0bbd2533 gunicorn 2026-01-08 17:29:41 +02:00
Yiannis Stergiou
1c15880ae3 feat: frontend unit tests 2026-01-07 19:47:54 +02:00
Markos Gogoulos
54336f6c31 fix 2026-01-06 17:19:47 +02:00
Markos Gogoulos
37e21f7ebf this 2025-12-30 19:57:27 +02:00
Markos Gogoulos
3deee80dd0 fix 2025-12-30 19:53:30 +02:00
Markos Gogoulos
2e57164831 req 2025-12-30 17:55:23 +02:00
Markos Gogoulos
de0c16729b a 2025-12-30 15:43:42 +02:00
Markos Gogoulos
2c0bba1427 a 2025-12-30 15:30:08 +02:00
Markos Gogoulos
54a8e41f6d a 2025-12-30 15:19:36 +02:00
Markos Gogoulos
78fb19b464 a 2025-12-30 14:58:01 +02:00
Markos Gogoulos
8e5e7991b7 a 2025-12-30 14:49:40 +02:00
Markos Gogoulos
5cf435eca0 f 2025-12-30 13:50:37 +02:00
Markos Gogoulos
5026ce73da wtv 2025-12-29 20:45:48 +02:00
Markos Gogoulos
8b2ebe2415 wtv 2025-12-29 20:40:48 +02:00
Markos Gogoulos
8df320e134 wtv 2025-12-29 20:40:36 +02:00
Markos Gogoulos
8c8f737460 wtv 2025-12-29 20:32:56 +02:00
Markos Gogoulos
995faedb08 wtv 2025-12-29 20:16:21 +02:00
Markos Gogoulos
bde300b4bd all 2025-12-29 20:14:32 +02:00
Markos Gogoulos
fd5c0a2908 all 2025-12-29 20:04:41 +02:00
Markos Gogoulos
9c145da2e2 all 2025-12-29 20:02:55 +02:00
Markos Gogoulos
e9e5d44c3e wtv 2025-12-29 19:47:03 +02:00
Markos Gogoulos
a624c2e5b8 wtv 2025-12-29 19:46:36 +02:00
Markos Gogoulos
748d3b39ba wtv 2025-12-29 19:42:17 +02:00
Markos Gogoulos
ddc6bf9e67 wtv 2025-12-29 19:36:53 +02:00
Markos Gogoulos
aa7dbfe534 wtv 2025-12-29 19:34:53 +02:00
Markos Gogoulos
5cc72357c6 wtv 2025-12-29 19:33:24 +02:00
Markos Gogoulos
01b061a47b wtv 2025-12-29 19:29:41 +02:00
Markos Gogoulos
fbc78e7944 wtv 2025-12-29 19:23:55 +02:00
Markos Gogoulos
9e7a8afdda wtv 2025-12-29 19:21:26 +02:00
Markos Gogoulos
5572a67019 wtv 2025-12-29 19:16:29 +02:00
Markos Gogoulos
610590972f wtv 2025-12-29 19:15:10 +02:00
Markos Gogoulos
bdf7d3c2d0 wtv 2025-12-29 19:14:50 +02:00
Markos Gogoulos
a47bf5a3f8 wtv 2025-12-29 19:11:02 +02:00
Markos Gogoulos
38caea3c7c wtv 2025-12-29 19:08:44 +02:00
Markos Gogoulos
30491bf420 wtv 2025-12-29 19:06:58 +02:00
Markos Gogoulos
d0ebe19c2a wtv 2025-12-29 19:03:31 +02:00
Markos Gogoulos
59be9f16c0 wtv 2025-12-29 19:01:47 +02:00
Markos Gogoulos
a2d898c54e wtv 2025-12-29 18:59:24 +02:00
Markos Gogoulos
9733d53c0b wtv 2025-12-29 18:57:39 +02:00
Markos Gogoulos
70e2c67f3d wtv 2025-12-29 18:55:44 +02:00
Markos Gogoulos
77721d9c0e wtv 2025-12-29 18:48:35 +02:00
Markos Gogoulos
06bc64b2c4 all 2025-12-29 18:21:44 +02:00
Markos Gogoulos
b9899476b9 this 2025-12-29 17:45:12 +02:00
Markos Gogoulos
107750406e this 2025-12-29 17:43:54 +02:00
Markos Gogoulos
ae4ae5a07e this 2025-12-29 17:41:16 +02:00
Markos Gogoulos
f346a5604c this 2025-12-29 17:34:47 +02:00
Markos Gogoulos
56026a1a96 this 2025-12-29 17:26:19 +02:00
Markos Gogoulos
a88413ce14 this 2025-12-29 17:10:38 +02:00
Markos Gogoulos
9dab3ad858 this 2025-12-29 16:53:23 +02:00
Markos Gogoulos
dfe7e8fab0 this 2025-12-29 16:46:29 +02:00
Markos Gogoulos
1181d16ab9 this 2025-12-29 16:36:53 +02:00
Markos Gogoulos
d032ee3baa this 2025-12-29 16:35:47 +02:00
Markos Gogoulos
93f66d206b this 2025-12-29 14:17:30 +02:00
Markos Gogoulos
0585513439 this 2025-12-29 14:13:45 +02:00
Markos Gogoulos
9667e6b0ad this 2025-12-29 13:57:40 +02:00
Markos Gogoulos
f56948a4a2 this 2025-12-28 16:51:03 +02:00
Markos Gogoulos
8b3e76b554 this 2025-12-28 16:44:43 +02:00
Markos Gogoulos
dc417de628 this 2025-12-28 16:43:00 +02:00
Markos Gogoulos
35cd56c85c this 2025-12-28 16:34:59 +02:00
Markos Gogoulos
f0b2451815 this 2025-12-28 16:18:32 +02:00
Markos Gogoulos
7696251394 doc 2025-12-28 16:13:37 +02:00
Markos Gogoulos
b95725660b notes 2025-12-28 16:07:42 +02:00
Markos Gogoulos
d6bf98b30e this 2025-12-28 15:47:26 +02:00
Markos Gogoulos
3baa8ef7d7 this 2025-12-28 15:41:23 +02:00
Markos Gogoulos
45246eac4f this 2025-12-28 15:41:01 +02:00
Markos Gogoulos
9685c1b5d4 this 2025-12-28 15:39:47 +02:00
Markos Gogoulos
20a1da22bb this 2025-12-28 15:37:35 +02:00
Markos Gogoulos
f9a94321ad this 2025-12-28 15:35:29 +02:00
Markos Gogoulos
f85299a600 this 2025-12-28 15:30:37 +02:00
Markos Gogoulos
29ab2a715b this 2025-12-28 15:23:51 +02:00
Markos Gogoulos
43ce685f08 this 2025-12-28 15:22:08 +02:00
Markos Gogoulos
8c682a76af this 2025-12-28 15:19:49 +02:00
Markos Gogoulos
ec6b6daa81 this 2025-12-28 15:18:55 +02:00
Markos Gogoulos
cf90169240 this 2025-12-28 15:17:52 +02:00
Markos Gogoulos
fb3f377e27 this 2025-12-28 15:16:23 +02:00
Markos Gogoulos
f5f9a7beac this 2025-12-28 15:14:14 +02:00
Markos Gogoulos
726a5b74a1 this 2025-12-28 15:12:49 +02:00
Markos Gogoulos
40c31f295a this 2025-12-28 15:11:24 +02:00
Markos Gogoulos
1d77293afc this 2025-12-28 15:10:19 +02:00
Markos Gogoulos
5c702387ca this 2025-12-28 15:09:22 +02:00
Markos Gogoulos
0001f370a9 this 2025-12-28 15:05:40 +02:00
Markos Gogoulos
af71d4c906 this 2025-12-28 15:03:58 +02:00
Markos Gogoulos
eb7503125d this 2025-12-28 15:02:57 +02:00
Markos Gogoulos
f897d0ba2b this 2025-12-28 15:00:14 +02:00
Markos Gogoulos
545cca154e this 2025-12-28 14:39:04 +02:00
Markos Gogoulos
ef4ff9cb1d this 2025-12-28 14:33:51 +02:00
Markos Gogoulos
3a40fc6d88 this 2025-12-28 14:31:19 +02:00
Markos Gogoulos
f67d2a4d78 erq 2025-12-24 17:28:27 +02:00
Markos Gogoulos
295578dae2 lti 2025-12-24 17:28:12 +02:00
Markos Gogoulos
ed5cfa1a84 add icon on media profile page 2025-12-24 17:18:30 +02:00
Markos Gogoulos
2fe48d8522 fix formatting 2025-12-24 12:29:25 +02:00
Josh Preston
90331f3b4a Fix: Add regex denoter and improve celerybeat gitignore (#1446)
* (bugfix): Added celerybeat extras to gitignore

* (bugfix): fixed missing regex denoter

* Fix .dockerignore node_modules pattern and add comprehensive exclusions

- Fix #1398: Change 'node_modules' to '**/node_modules' to exclude all nested directories
- Add patterns for Python bytecode, IDE files, logs, and build artifacts
- Consolidate node_modules patterns in .gitignore to use **/node_modules/
2025-12-24 12:28:55 +02:00
Josh Preston
c57f528ab1 Add missing migration for Meta options on Subtitle, TranscriptionRequest, and VideoTrimRequest (#1448)
Fixes #1447

This migration adds the missing AlterModelOptions operations for:
- Subtitle model (verbose_name: 'Caption', verbose_name_plural: 'Captions')
- TranscriptionRequest model (verbose_name: 'Caption Request', verbose_name_plural: 'Caption Requests')
- VideoTrimRequest model (verbose_name: 'Trim Request', verbose_name_plural: 'Trim Requests')

These Meta options were defined in the models but never migrated, causing
makemigrations --dry-run to show pending migrations on fresh clones.
2025-12-24 12:18:48 +02:00
Markos Gogoulos
fa67ffffb4 replace media, shared state, better category options 2025-12-24 12:14:01 +02:00
Markos Gogoulos
872571350f static files 2025-12-22 11:14:35 +02:00
Markos Gogoulos
665971856b version bump 2025-12-22 11:12:37 +02:00
Yiannis Christodoulou
d9b1d6cab1 feat: Improve Visual Distinction Between Trim and Chapters Editors (#1445)
* Update .gitignore

* feat: Improve Visual Distinction Between Trim and Chapters Editors

* fix: Convert timeline header styles to CSS classes

Moved inline styles for timeline headers in chapters and video editors to dedicated CSS classes for better maintainability and consistency.

* Bump version to 7.3.0

Update the VERSION in cms/version.py to 7.3.0 for the new release.

* build assets

* Update segment color schemes in video and chapters editor.

* build assets

* build assets

* fix: Prevent Safari from resetting segments after drag operations

Prevent Safari from resetting segments when loadedmetadata fires multiple times and fix stale state issues in click handlers by using refs instead of closure variables.

* build assets

* Bump version to 7.3.0-beta.3

Update the VERSION string in cms/version.py to reflect the new pre-release version 7.3.0-beta.3.
2025-12-22 11:12:19 +02:00
Markos Gogoulos
aeef8284bf docs: update page link 2025-12-01 11:29:58 +02:00
Markos Gogoulos
a90fcbf8dd version bump 2025-11-21 12:30:12 +02:00
Markos Gogoulos
1b3cdfd302 fix: add delay to task creation 2025-11-21 12:30:05 +02:00
Yiannis Christodoulou
cd7dd4f72c fix: Chapter numbering and preserve custom titles on segment reorder (#1435)
* FIX: Preserve custom chapter titles when renumbering (151)

Updated the renumberAllSegments function to only update chapter titles that match the default 'Chapter X' pattern, preserving any custom titles. Also ensured segments are renumbered after updates for consistent chronological naming.

* build assets (chapters editor)
2025-11-21 12:29:19 +02:00
243 changed files with 12751 additions and 5013 deletions

View File

@@ -1,113 +0,0 @@
FROM python:3.13.5-slim-bookworm AS build-image
# Install system dependencies needed for downloading and extracting
RUN apt-get update -y && \
apt-get install -y --no-install-recommends wget xz-utils unzip && \
rm -rf /var/lib/apt/lists/* && \
apt-get purge --auto-remove && \
apt-get clean
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
RUN mkdir -p ffmpeg-tmp && \
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
# Install Bento4 in the specified location
RUN mkdir -p /home/mediacms.io/bento4 && \
wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d /home/mediacms.io/bento4 && \
mv /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* /home/mediacms.io/bento4/ && \
rm -rf /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
rm -rf /home/mediacms.io/bento4/docs && \
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
SHELL ["/bin/bash", "-c"]
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
pkg-config \
zlib1g-dev \
zlib1g \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Copy requirements files
COPY requirements.txt requirements-dev.txt ./
# Install Python dependencies using pip (within virtualenv)
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir uv && \
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
echo "Installing development dependencies..." && \
uv pip install -r requirements-dev.txt; \
fi && \
apt-get purge -y --auto-remove \
build-essential \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
libpq-dev
# Copy ffmpeg and Bento4 from build image
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Set process control environment variables
ENV ENABLE_UWSGI='yes' \
ENABLE_NGINX='yes' \
ENABLE_CELERY_BEAT='yes' \
ENABLE_CELERY_SHORT='yes' \
ENABLE_CELERY_LONG='yes' \
ENABLE_MIGRATIONS='yes'
EXPOSE 9000 80
RUN chmod +x ./deploy/docker/entrypoint.sh
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
COPY requirements-full.txt ./
RUN mkdir -p /root/.cache/ && \
chmod go+rwx /root/ && \
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@@ -1,119 +0,0 @@
version: "3"
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- conf:/etc/nginx/conf.d
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
- certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./deploy/docker/reverse_proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
acme-companion:
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
migrations:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'Y'
ADMIN_PASSWORD: 'X'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
web:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
VIRTUAL_HOST: 'X.mediacms.io'
LETSENCRYPT_HOST: 'X.mediacms.io'
LETSENCRYPT_EMAIL: 'X'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- redis
celery_worker:
image: mediacms/mediacms:full
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
conf:
vhost:
html:
dhparam:
certs:
acme:

View File

@@ -1,89 +0,0 @@
version: "3"
services:
migrations:
build:
context: .
dockerfile: ./Dockerfile
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:latest
volumes:
- ./:/home/mediacms.io/mediacms/
command: "./deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
frontend:
image: node:20
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
command: bash -c "npm install && npm run start"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
depends_on:
- web
web:
image: mediacms/mediacms-dev:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
celery_worker:
image: mediacms/mediacms-dev:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- web

View File

@@ -1,86 +0,0 @@
version: "3"
services:
migrations:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
web:
image: mediacms/mediacms:latest
deploy:
replicas: 1
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- redis
celery_worker:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3

View File

@@ -1,37 +1,69 @@
# Dependencies
node_modules
npm-debug.log
# Node.js/JavaScript dependencies and artifacts
**/node_modules
**/npm-debug.log*
**/yarn-debug.log*
**/yarn-error.log*
**/.yarn/cache
**/.yarn/unplugged
**/package-lock.json
**/.npm
**/.cache
**/.parcel-cache
**/dist
**/build
**/*.tsbuildinfo
# Local development files - exclude uploaded content but keep placeholder images
media_files/*
!media_files/userlogos/
media_files/userlogos/*
!media_files/userlogos/*.jpg
logs
static_collected
# Python bytecode and cache
**/__pycache__
**/*.py[cod]
**/*$py.class
**/*.so
**/.Python
**/pip-log.txt
**/pip-delete-this-directory.txt
**/.pytest_cache
**/.coverage
**/htmlcov
**/.tox
**/.mypy_cache
**/.ruff_cache
# Version control
.git
.github
.gitignore
**/.git
**/.gitignore
**/.gitattributes
# Development/testing
.pytest_cache
.qodo
.claude
# IDE and editor files
**/.DS_Store
**/.vscode
**/.idea
**/*.swp
**/*.swo
**/*~
# Docker
.dockerignore
Dockerfile
docker-compose*.yml
.docker-backup
# Logs and runtime files
**/logs
**/*.log
**/celerybeat-schedule*
**/.env
**/.env.*
# Documentation (if you don't need it in the image)
docs
# Media files and data directories (should not be in image)
media_files/**
postgres_data/**
pids/**
# Other
*.pyc
__pycache__
.env
.vscode
.idea
# Static files collected at runtime
static_collected/**
# Documentation and development files
**/.github
**/CHANGELOG.md
# Test files and directories
**/tests
**/test_*.py
**/*_test.py
# Frontend build artifacts (built separately)
frontend/dist/**

View File

@@ -21,8 +21,8 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta for web image
id: meta-web
- name: Docker meta for base image
id: meta-base
uses: docker/metadata-action@v4
with:
images: |
@@ -40,95 +40,39 @@ jobs:
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push web image
uses: docker/build-push-action@v4
with:
context: .
target: web
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-web.outputs.tags }}
labels: ${{ steps.meta-web.outputs.labels }}
- name: Docker meta for worker image
id: meta-worker
- name: Docker meta for full image
id: meta-full
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-worker
mediacms/mediacms
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MediaCMS Worker
org.opencontainers.image.description=MediaCMS Celery worker for background task processing.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push worker image
uses: docker/build-push-action@v4
with:
context: .
target: worker
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-worker.outputs.tags }}
labels: ${{ steps.meta-worker.outputs.labels }}
- name: Docker meta for worker-full image
id: meta-worker-full
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-worker
tags: |
type=raw,value=latest-full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=raw,value=full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}-full
type=semver,pattern={{major}}.{{minor}}-full
type=semver,pattern={{major}}-full
labels: |
org.opencontainers.image.title=MediaCMS Worker Full
org.opencontainers.image.description=MediaCMS Celery worker with additional codecs for advanced transcoding features.
org.opencontainers.image.title=MediaCMS Full
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API. This is the full version with additional dependencies.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push worker-full image
- name: Build and push full image
uses: docker/build-push-action@v4
with:
context: .
target: worker-full
target: full
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-worker-full.outputs.tags }}
labels: ${{ steps.meta-worker-full.outputs.labels }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
- name: Docker meta for nginx image
id: meta-nginx
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms-nginx
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MediaCMS Nginx
org.opencontainers.image.description=Nginx web server for MediaCMS, serving static and media files.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push nginx image
- name: Build and push base image
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile.nginx
target: base
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-nginx.outputs.tags }}
labels: ${{ steps.meta-nginx.outputs.labels }}
tags: ${{ steps.meta-base.outputs.tags }}
labels: ${{ steps.meta-base.outputs.labels }}

View File

@@ -0,0 +1,42 @@
name: Frontend build and test
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
strategy:
matrix:
os: [ubuntu-latest]
node: [20]
runs-on: ${{ matrix.os }}
name: '${{ matrix.os }} - node v${{ matrix.node }}'
permissions:
contents: read
defaults:
run:
working-directory: ./frontend
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm install
- name: Build script
run: npm run dist
- name: Test script
run: npm run test

16
.gitignore vendored
View File

@@ -1,18 +1,14 @@
cli-tool/.env
frontend/package-lock.json
custom/local_settings.py
custom/static/images/*
!custom/static/images/.gitkeep
custom/static/css/*
!custom/static/css/.gitkeep
media_files/encoded/
media_files/original/
media_files/hls/
media_files/chunks/
media_files/uploads/
media_files/tinymce_media/
media_files/userlogos/
postgres_data/
celerybeat-schedule
celerybeat-schedule*
logs/
pids/
static/admin/
@@ -22,10 +18,10 @@ static/mptt/
static/rest_framework/
static/drf-yasg
cms/local_settings.py
config/local_settings.py
deploy/docker/local_settings.py
yt.readme.md
/frontend-tools/video-editor/node_modules
/frontend-tools/video-editor/client/node_modules
# Node.js dependencies (covers all node_modules directories, including frontend-tools)
**/node_modules/
/static_collected
/frontend-tools/video-editor-v1
frontend-tools/.DS_Store
@@ -40,4 +36,4 @@ frontend-tools/video-editor/client/public/videos/sample-video.mp3
frontend-tools/chapters-editor/client/public/videos/sample-video.mp3
static/chapters_editor/videos/sample-video.mp3
static/video_editor/videos/sample-video.mp3
backups/
templates/todo-MS4.md

View File

@@ -1,441 +0,0 @@
# MediaCMS Docker Restructure Summary - Version 7.3
## Overview
MediaCMS 7.3 introduces a complete Docker architecture restructure, moving from a monolithic supervisord-based setup to modern microservices with proper separation of concerns.
**⚠️ BREAKING CHANGES** - See [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md) for migration guide.
## Architecture Comparison
### Before (7.x) - Monolithic
```
┌─────────────────────────────────────┐
│ Single Container │
│ ┌──────────┐ │
│ │Supervisor│ │
│ └────┬─────┘ │
│ ├─── nginx (port 80) │
│ ├─── uwsgi (Django) │
│ ├─── celery beat │
│ ├─── celery workers │
│ └─── migrations │
│ │
│ Volumes: ./ mounted to container │
└─────────────────────────────────────┘
```
### After (7.3) - Microservices
```
┌────────┐ ┌─────┐ ┌───────────┐ ┌──────────┐
│ nginx │→ │ web │ │celery_beat│ │ celery │
│ │ │uwsgi│ │ │ │ workers │
└────────┘ └─────┘ └───────────┘ └──────────┘
┌───────┴────────┐
│ db │ redis │
└───────┴────────┘
Volumes: Named volumes + custom/ bind mount
```
## What Changed
### 1. Container Services
| Component | Before (7.x) | After (7.3) |
|-----------|-------------|-------------|
| **nginx** | Inside main container | Separate container |
| **Django/uWSGI** | Inside main container | Dedicated `web` container |
| **Celery Beat** | Inside main container | Dedicated container |
| **Celery Workers** | Inside main container | Separate containers (short/long) |
| **Migrations** | Via environment flag | Init container (runs once) |
### 2. Volume Strategy
| Data | Before (7.x) | After (7.3) |
|------|-------------|-------------|
| **Application code** | Bind mount `./` | **Built into image** |
| **Media files** | `./media_files` | **Named volume** `media_files` |
| **Static files** | `./static` | **Built into image** (collectstatic at build) |
| **Logs** | `./logs` | **Named volume** `logs` |
| **PostgreSQL** | `../postgres_data` | **Named volume** `postgres_data` |
| **Custom config** | `cms/local_settings.py` | **Bind mount** `./custom/` |
### 3. Removed Components
- ❌ supervisord and all supervisord configs
- ❌ docker-entrypoint.sh (permission fixing script)
-`ENABLE_*` environment variables
- ❌ Runtime collectstatic
- ❌ nginx from base image
### 4. New Components
-`custom/` directory for user customizations
- ✅ Multi-stage Dockerfile (base, web, worker, worker-full)
- ✅ Separate nginx image (`Dockerfile.nginx`)
- ✅ Build-time collectstatic
- ✅ USER www-data (non-root containers)
- ✅ Health checks for all services
- ✅ Makefile with common tasks
## Key Improvements
### Security
- ✅ Containers run as `www-data` (UID 33), not root
- ✅ Read-only mounts where possible
- ✅ Smaller attack surface per container
- ✅ No privilege escalation needed
### Performance
- ✅ Named volumes have better I/O than bind mounts
- ✅ Static files built into image (no runtime collection)
- ✅ Faster container startups
- ✅ No chown on millions of files at startup
### Scalability
- ✅ Scale web and workers independently
- ✅ Ready for load balancing
- ✅ Can use Docker Swarm or Kubernetes
- ✅ Horizontal scaling: `docker compose scale celery_short=3`
### Maintainability
- ✅ One process per container (proper separation)
- ✅ Clear service dependencies
- ✅ Standard Docker patterns
- ✅ Easier debugging (service-specific logs)
- ✅ Immutable images
### Developer Experience
- ✅ Separate dev compose with hot reload
-`custom/` directory for all customizations
- ✅ Clear documentation and examples
- ✅ Makefile targets for common tasks
## New Customization System
### The `custom/` Directory
All user customizations now go in a dedicated directory:
```
custom/
├── README.md # Full documentation
├── local_settings.py.example # Template file
├── local_settings.py # Your Django settings (gitignored)
└── static/
├── images/ # Custom logos (gitignored)
│ └── logo_dark.png
└── css/ # Custom CSS (gitignored)
└── custom.css
```
**Benefits:**
- Clear separation from core code
- Works out-of-box (empty directory is fine)
- Gitignored customizations
- Well documented with examples
See [`custom/README.md`](./custom/README.md) for usage guide.
## Docker Images
### Images to Build
```bash
# Web image (Django + uWSGI)
docker build --target web -t mediacms/mediacms:7.3 .
# Worker image (Celery)
docker build --target worker -t mediacms/mediacms-worker:7.3 .
# Worker-full image (Celery with extra codecs)
docker build --target worker-full -t mediacms/mediacms-worker:7.3-full .
# Nginx image
docker build -f Dockerfile.nginx -t mediacms/mediacms-nginx:7.3 .
```
### Image Sizes
| Image | Approximate Size |
|-------|-----------------|
| mediacms:7.3 | ~800MB |
| mediacms-worker:7.3 | ~800MB |
| mediacms-worker:7.3-full | ~1.2GB |
| mediacms-nginx:7.3 | ~50MB |
## Deployment Scenarios
### 1. Development
```bash
docker compose -f docker-compose-dev.yaml up
```
**Features:**
- File mounts for live editing
- Django runserver with DEBUG=True
- Frontend hot reload
- Immediate code changes
### 2. Production (HTTP)
```bash
docker compose up -d
```
**Features:**
- Immutable images
- Named volumes for data
- Production-ready
- Port 80
### 3. Production (HTTPS with Let's Encrypt)
```bash
docker compose -f docker-compose.yaml -f docker-compose-cert.yaml up -d
```
**Features:**
- Automatic SSL certificates
- Auto-renewal
- nginx-proxy + acme-companion
- Production-ready
## Minimal Deployment (No Code Required!)
**Version 7.3 requires ONLY:**
1.`docker-compose.yaml` file
2. ✅ Docker images (from Docker Hub)
3. ⚠️ `custom/` directory (optional, only if customizing)
**No git repo needed!** Download docker-compose.yaml from release/docs and start.
## Migration Requirements
### Breaking Changes
⚠️ **Not backward compatible** - Manual migration required
**What needs migration:**
1. ✅ PostgreSQL database (dump and restore)
2. ✅ Media files (copy to named volume)
3. ✅ Custom settings → `custom/local_settings.py` (if you had them)
4. ✅ Custom logos/CSS → `custom/static/` (if you had them)
5. ⚠️ Backup scripts (new volume paths)
6. ⚠️ Monitoring (new container names)
### Migration Steps
See [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md) for complete guide.
**Quick overview:**
```bash
# 1. Backup
docker compose exec db pg_dump -U mediacms mediacms > backup.sql
tar -czf media_backup.tar.gz media_files/
cp docker-compose.yaml docker-compose.yaml.old
# 2. Download new docker-compose.yaml
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
# 3. Create custom/ if needed
mkdir -p custom/static/{images,css}
# Copy your old settings/logos if you had them
# 4. Pull images and start
docker compose pull
docker compose up -d
# 5. Restore data
cat backup.sql | docker compose exec -T db psql -U mediacms mediacms
# (See full guide for media migration)
```
## Configuration Files
### Created/Reorganized
```
├── Dockerfile # Multi-stage (base, web, worker)
├── Dockerfile.nginx # Nginx image
├── docker-compose.yaml # Production
├── docker-compose-cert.yaml # Production + HTTPS
├── docker-compose-dev.yaml # Development
├── Makefile # Common tasks
├── custom/ # User customizations
│ ├── README.md
│ ├── local_settings.py.example
│ └── static/
├── config/
│ ├── imagemagick/policy.xml
│ ├── nginx/
│ │ ├── nginx.conf
│ │ └── site.conf
│ ├── nginx-proxy/
│ │ └── client_max_body_size.conf
│ └── uwsgi/
│ └── uwsgi.ini
└── scripts/
└── run-migrations.sh
```
## Makefile Targets
New Makefile with common operations:
```bash
make backup-db # PostgreSQL dump with timestamp
make admin-shell # Quick Django shell access
make build-frontend # Rebuild frontend assets
make test # Run test suite
```
## Rollback Strategy
If migration fails:
```bash
# 1. Stop new version
docker compose down
# 2. Checkout old version
git checkout main
# 3. Restore old compose
git checkout main docker-compose.yaml
# 4. Restore data from backups
# (See UPGRADE_TO_7.3.md for details)
# 5. Start old version
docker compose up -d
```
## Testing Checklist
Before production deployment:
- [ ] Migrations run successfully
- [ ] Static files load correctly
- [ ] Media files upload/download work
- [ ] Video transcoding works (check celery_long logs)
- [ ] Admin panel accessible
- [ ] Custom settings loaded (if using custom/)
- [ ] Database persists across restarts
- [ ] Media persists across restarts
- [ ] Logs accessible via `docker compose logs`
- [ ] Health checks pass: `docker compose ps`
## Common Post-Upgrade Tasks
### View Logs
```bash
# Before: tail -f logs/uwsgi.log
# After:
docker compose logs -f web
docker compose logs -f celery_long
```
### Access Shell
```bash
# Before: docker exec -it <container> bash
# After:
make admin-shell
# Or: docker compose exec web bash
```
### Restart Service
```bash
# Before: docker restart <container>
# After:
docker compose restart web
```
### Scale Workers
```bash
# New capability:
docker compose up -d --scale celery_short=3 --scale celery_long=2
```
### Database Backup
```bash
# Before: Custom script
# After:
make backup-db
```
## Performance Considerations
### Startup Time
- **Before**: Slower (chown on all files)
- **After**: Faster (no permission fixing)
### I/O Performance
- **Before**: Bind mount overhead
- **After**: Named volumes (better performance)
### Memory Usage
- **Before**: Single large container
- **After**: Multiple smaller containers (better resource allocation)
## New Volume Management
### List Volumes
```bash
docker volume ls | grep mediacms
```
### Inspect Volume
```bash
docker volume inspect mediacms_media_files
```
### Backup Volume
```bash
docker run --rm \
-v mediacms_media_files:/data:ro \
-v $(pwd):/backup \
alpine tar czf /backup/media_backup.tar.gz -C /data .
```
## Documentation
- **Upgrade Guide**: [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md)
- **Customization**: [`custom/README.md`](./custom/README.md)
- **Admin Docs**: `docs/admins_docs.md`
## Timeline Estimates
| Instance Size | Expected Migration Time |
|---------------|------------------------|
| Small (<100 videos) | 30-60 minutes |
| Medium (100-1000 videos) | 1-3 hours |
| Large (>1000 videos) | 3-8 hours |
**Plan accordingly and schedule during low-traffic periods!**
## Getting Help
1. Read [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md) thoroughly
2. Check [`custom/README.md`](./custom/README.md) for customization
3. Search GitHub Issues
4. Test in staging first
5. Keep backups for at least 1 week post-upgrade
## Next Steps
1. ✅ Read [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md)
2. ✅ Test in development: `docker compose -f docker-compose-dev.yaml up`
3. ✅ Backup production data
4. ✅ Test migration in staging
5. ✅ Plan maintenance window
6. ✅ Execute migration
7. ✅ Monitor for 24-48 hours
---
**Ready to upgrade?** Start with: [`UPGRADE_TO_7.3.md`](./UPGRADE_TO_7.3.md)

View File

@@ -1,5 +1,6 @@
FROM python:3.13.5-slim-bookworm AS build-image
# Install system dependencies needed for downloading and extracting
RUN apt-get update -y && \
apt-get install -y --no-install-recommends wget xz-utils unzip && \
rm -rf /var/lib/apt/lists/* && \
@@ -13,6 +14,7 @@ RUN mkdir -p ffmpeg-tmp && \
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
# Install Bento4 in the specified location
RUN mkdir -p /home/mediacms.io/bento4 && \
wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d /home/mediacms.io/bento4 && \
@@ -24,21 +26,20 @@ RUN mkdir -p /home/mediacms.io/bento4 && \
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
LABEL org.opencontainers.image.version="7.3"
LABEL org.opencontainers.image.title="MediaCMS"
LABEL org.opencontainers.image.description="Modern, scalable and open source video platform"
SHELL ["/bin/bash", "-c"]
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
CELERY_APP='cms' \
VIRTUAL_ENV=/home/mediacms.io \
PATH="$VIRTUAL_ENV/bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
@@ -49,16 +50,18 @@ RUN apt-get update -y && \
libxmlsec1-dev \
libxmlsec1-openssl \
libpq-dev \
gosu \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /home/mediacms.io/mediacms/{logs,media_files,static} && \
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Copy requirements files
COPY requirements.txt requirements-dev.txt ./
# Install Python dependencies using pip (within virtualenv)
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir uv && \
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
@@ -73,54 +76,38 @@ RUN pip install --no-cache-dir uv && \
libxmlsec1-dev \
libpq-dev
# Copy ffmpeg and Bento4 from build image
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
COPY --chown=www-data:www-data . /home/mediacms.io/mediacms
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# Copy imagemagick policy for sprite thumbnail generation
COPY config/imagemagick/policy.xml /etc/ImageMagick-6/policy.xml
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Create www-data user directories and set permissions
RUN mkdir -p /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 \
/var/run/mediacms
# Set process control environment variables
ENV ENABLE_UWSGI='yes' \
ENABLE_NGINX='yes' \
ENABLE_CELERY_BEAT='yes' \
ENABLE_CELERY_SHORT='yes' \
ENABLE_CELERY_LONG='yes' \
ENABLE_MIGRATIONS='yes'
# Collect static files during build
RUN python manage.py collectstatic --noinput && \
chown -R www-data:www-data /home/mediacms.io/mediacms/static
EXPOSE 9000 80
# Run container as www-data user
USER www-data
RUN chmod +x ./deploy/docker/entrypoint.sh
############ WEB IMAGE (Django/uWSGI) ############
FROM base AS web
# Install uWSGI
RUN uv pip install uwsgi
# Copy uWSGI configuration
COPY config/uwsgi/uwsgi.ini /home/mediacms.io/mediacms/uwsgi.ini
EXPOSE 9000
CMD ["/home/mediacms.io/bin/uwsgi", "--ini", "/home/mediacms.io/mediacms/uwsgi.ini"]
############ WORKER IMAGE (Celery) ############
FROM base AS worker
# CMD will be overridden in docker-compose for different worker types
############ FULL WORKER IMAGE (Celery with extra codecs) ############
FROM worker AS worker-full
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
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
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@@ -1,18 +0,0 @@
FROM nginx:alpine
LABEL org.opencontainers.image.version="7.3"
LABEL org.opencontainers.image.title="MediaCMS Nginx"
LABEL org.opencontainers.image.description="Nginx server for MediaCMS"
# Copy nginx configurations
COPY config/nginx/nginx.conf /etc/nginx/nginx.conf
COPY config/nginx/site.conf /etc/nginx/conf.d/default.conf
COPY config/nginx/uwsgi_params /etc/nginx/uwsgi_params
# Create directories for static and media files (will be volumes)
RUN mkdir -p /var/www/media /var/www/static && \
chown -R nginx:nginx /var/www
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,23 +0,0 @@
# History
## 3.0.0
### Features
- Updates Python/Django requirements and Dockerfile to use latest 3.11 Python - https://github.com/mediacms-io/mediacms/pull/826/files. This update requires some manual steps, for existing (not new) installations. Check the update section under the [Admin docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#2-server-installation), either for single server or for Docker Compose installations
- Upgrade postgres on Docker Compose - https://github.com/mediacms-io/mediacms/pull/749
### Fixes
- video player options for HLS - https://github.com/mediacms-io/mediacms/pull/832
- AVI videos not correctly recognised as videos - https://github.com/mediacms-io/mediacms/pull/833
## 2.1.0
### Fixes
- Increase uwsgi buffer-size parameter. This prevents an error by uwsgi with large headers - [#5b60](https://github.com/mediacms-io/mediacms/commit/5b601698a41ad97f08c1830e14b1c18f73ab8315)
- Fix issues with comments. These were not reported on the tracker but it is certain that they would not show comments on media files (non videos but also videos). Unfortunately this reverts work done with Timestamps on comments + Mentions on comments, more on PR [#802](https://github.com/mediacms-io/mediacms/pull/802)
### Features
- Allow tags to contains other characters too, not only English alphabet ones [#801](https://github.com/mediacms-io/mediacms/pull/801)
- Add simple cookie consent code [#799](https://github.com/mediacms-io/mediacms/pull/799)
- Allow password reset & email verify pages on global login required [#790](https://github.com/mediacms-io/mediacms/pull/790)
- Add api_url field to search api [#692](https://github.com/mediacms-io/mediacms/pull/692)

253
LTI_SETUP.md Executable file
View File

@@ -0,0 +1,253 @@
# MediaCMS LTI 1.3 Integration Setup Guide
This guide walks you through integrating MediaCMS with a Learning Management System (LMS) like Moodle using LTI 1.3.
## 1. Configure MediaCMS Settings
Add these settings to `cms/local_settings.py`:
```python
# Enable LTI integration
USE_LTI = True
# Enable RBAC for course-based access control
USE_RBAC = True
# Your production domain
FRONTEND_HOST = 'https://your-mediacms-domain.com'
ALLOWED_HOSTS = ['your-mediacms-domain.com', 'localhost']
```
**Note:** LTI-specific cookie settings (SESSION_COOKIE_SAMESITE='None', etc.) are automatically applied when `USE_LTI=True`.
## 2. MediaCMS Configuration
### A. Verify HTTPS Setup
Ensure your MediaCMS server is running on HTTPS. LTI 1.3 requires HTTPS for security and iframe embedding.
### B. Register Your LMS Platform
1. Access Django Admin: `https://your-mediacms-domain.com/admin/lti/ltiplatform/`
2. Add new LTI Platform with these settings:
**Basic Info:**
- **Name:** My LMS (or any descriptive name)
- **Platform ID (Issuer):** Get this from your LMS (e.g., `https://mylms.example.com`)
- **Client ID:** You'll get this from your LMS after registering MediaCMS as an external tool
**OIDC Endpoints (get from your LMS):**
- **Auth Login URL:** `https://mylms.example.com/mod/lti/auth.php`
- **Auth Token URL:** `https://mylms.example.com/mod/lti/token.php`
- **Key Set URL:** `https://mylms.example.com/mod/lti/certs.php`
**Deployment IDs:** Add the deployment ID(s) provided by your LMS as a JSON list, e.g., `["1"]`
**Features:**
- ✓ Enable NRPS (Names and Role Provisioning)
- ✓ Enable Deep Linking
- ✓ Auto-create categories
- ✓ Auto-create users
- ✓ Auto-sync roles
### C. Note MediaCMS URLs for LMS Configuration
You'll need these URLs when configuring your LMS:
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
- **OIDC Login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
- **JWK Set URL:** `https://your-mediacms-domain.com/lti/jwks/`
- **Redirection URI:** `https://your-mediacms-domain.com/lti/launch/`
- **Deep Linking URL:** `https://your-mediacms-domain.com/lti/select-media/`
## 3. LMS Configuration (Moodle Example)
### A. Register MediaCMS as External Tool
1. Navigate to: **Site administration → Plugins → Activity modules → External tool → Manage tools**
2. Click **Configure a tool manually** or add new tool
**Basic Settings:**
- **Tool name:** MediaCMS
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
- **LTI version:** LTI 1.3
- **Tool configuration usage:** Show in activity chooser
**URLs:**
- **Public keyset URL:** `https://your-mediacms-domain.com/lti/jwks/`
- **Initiate login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
- **Redirection URI(s):** `https://your-mediacms-domain.com/lti/launch/`
**Launch Settings:**
- **Default launch container:** Embed (without blocks) or New window
- **Accept grades from tool:** Optional
- **Share launcher's name:** Always ⚠️ **REQUIRED for user names**
- **Share launcher's email:** Always ⚠️ **REQUIRED for user emails**
> **Important:** MediaCMS creates user accounts automatically on first LTI launch. To ensure users have proper names and email addresses in MediaCMS, you **must** set both "Share launcher's name with tool" and "Share launcher's email with tool" to **Always** in the Privacy settings. Without these settings, users will be created with only a username based on their LTI user ID.
**Services:**
- ✓ IMS LTI Names and Role Provisioning (for roster sync)
- ✓ IMS LTI Deep Linking (for media selection)
**Tool Settings (Important for Deep Linking):**
-**Supports Deep Linking (Content-Item Message)** - Enable this to allow instructors to browse and select media from MediaCMS when adding activities
3. Save the tool configuration
### B. Copy Platform Details to MediaCMS
After saving, your LMS will provide:
- Platform ID (Issuer URL)
- Client ID
- Deployment ID
Copy these values back to the LTIPlatform configuration in MediaCMS admin (step 2B above).
### C. Using MediaCMS in Courses
**Option 1: Embed "My Media" view (Default)**
- In a course, add activity → External tool → MediaCMS
- Leave the custom URL blank (uses default launch URL)
- Students/teachers will see their MediaCMS profile in an iframe
**Option 2: Link to a Specific Video**
- Add activity → External tool → MediaCMS
- Activity name: "November 2020 Video" (or any descriptive name)
- In the activity settings, find **"Custom parameters"** (may be under "Privacy" or "Additional Settings")
- Add this parameter:
```
media_friendly_token=abc123def
```
- Replace `abc123def` with your video's token from MediaCMS (found in the URL: `/view?m=abc123def`)
- Students clicking this activity will go directly to that specific video
**Option 3: Link to Any MediaCMS Page**
- Add activity → External tool → MediaCMS
- In **"Custom parameters"**, add:
```
redirect_path=/featured
```
- Supported paths:
- `/featured` - Featured videos page
- `/latest` - Latest videos
- `/search/?q=keyword` - Search results
- `/category/category-name` - Specific category
- `/user/username` - User's profile
- Any other MediaCMS page path
**Option 4: Embed Specific Media via Deep Linking (Interactive)**
⚠️ **Prerequisite:** Ensure "Supports Deep Linking (Content-Item Message)" is enabled in the External Tool configuration (see section 3.A above)
When adding the activity to your course:
1. Add activity → External tool → MediaCMS
2. In the activity settings, enable **"Supports Deep Linking"** checkbox (may be under "Tool settings" or "Privacy" section)
3. Click **"Select content"** button → This launches the MediaCMS media browser
4. Browse and select media from MediaCMS (you can select multiple)
5. Click **"Add to course"** → Returns to Moodle with selected media configured
6. The activity will be automatically configured with the selected media's title and embed URL
7. Students clicking this activity will go directly to the selected media
### D. Custom Parameters - Complete Examples
**Example 1: Link to a specific video titled "Lecture 1 - Introduction"**
```
Activity Name: Lecture 1 - Introduction
Custom Parameters:
media_friendly_token=a1b2c3d4e5
```
**Example 2: Link to course-specific videos**
```
Activity Name: Course Videos
Custom Parameters:
redirect_path=/category/biology101
```
**Example 3: Link to search results for "genetics"**
```
Activity Name: Genetics Videos
Custom Parameters:
redirect_path=/search/?q=genetics
```
**Example 4: Link to featured content**
```
Activity Name: Featured Videos
Custom Parameters:
redirect_path=/featured
```
**Where to find Custom Parameters in Moodle:**
1. When creating/editing the External Tool activity
2. Expand **"Privacy"** section, or look for **"Additional Settings"**
3. Find the **"Custom parameters"** text field
4. Enter one parameter per line in the format: `key=value`
## 4. Testing Checklist
- [ ] HTTPS is working on MediaCMS
- [ ] `USE_LTI = True` in local_settings.py
- [ ] LTIPlatform configured in Django admin
- [ ] External tool registered in LMS
- [ ] Launch from LMS creates new user in MediaCMS
- [ ] Course is mapped to MediaCMS category
- [ ] Users are added to RBAC group with correct roles
- [ ] Media from course category is visible to course members
- [ ] Public media is accessible
- [ ] Private media from other courses is not accessible
## 5. Default Role Mappings
The system automatically maps LMS roles to MediaCMS:
- **Instructor/Teacher** → advancedUser (global) + manager (course group)
- **Student/Learner** → user (global) + member (course group)
- **Teaching Assistant** → user (global) + contributor (course group)
- **Administrator** → manager (global) + manager (course group)
You can customize these in Django admin under **LTI Role Mappings**.
## 6. User Creation and Authentication
### User Creation via LTI
When a user launches MediaCMS from your LMS for the first time, a MediaCMS account is automatically created with:
- **Username:** Generated from email (preferred) or name, or a unique ID if neither is available
- **Email:** From LTI claim (if shared by LMS)
- **Name:** From LTI given_name/family_name claims (if shared by LMS)
- **Roles:** Mapped from LTI roles to MediaCMS permissions
- **Course membership:** Automatically added to the RBAC group for the course
### Privacy Settings Are Critical
⚠️ **For proper user accounts, you must configure the LTI tool's privacy settings in Moodle:**
1. Edit the External Tool configuration in Moodle
2. Go to the **Privacy** section
3. Set **"Share launcher's name with tool"** to **Always**
4. Set **"Share launcher's email with tool"** to **Always**
Without these settings:
- Users will not have proper names in MediaCMS
- Users will not have email addresses
- Usernames will be generic hashes (e.g., `lti_user_abc123def`)
### Authentication
Users created through LTI integration do **not** have a password set. They can only access MediaCMS through LTI launches from your LMS. This is intentional for security.
If you need a user to have both LTI access and direct login capability, manually set a password using:
```bash
python manage.py changepassword <username>
```
## Need Help?
If you encounter issues, check:
- `/admin/lti/ltilaunchlog/` for launch attempt logs
- Django logs for detailed error messages
- Ensure HTTPS is properly configured (required for iframe cookies)
- Verify all URLs are correct and accessible
- Check that the Client ID and Deployment ID match between MediaCMS and your LMS

View File

@@ -1,4 +1,4 @@
.PHONY: admin-shell build-frontend backup-db
.PHONY: admin-shell build-frontend
admin-shell:
@container_id=$$(docker compose ps -q web); \
@@ -17,16 +17,3 @@ build-frontend:
test:
docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
backup-db:
@echo "Creating PostgreSQL database dump..."
@mkdir -p backups
@timestamp=$$(date +%Y%m%d_%H%M%S); \
dump_file="backups/mediacms_dump_$${timestamp}.sql"; \
docker compose exec -T db pg_dump -U mediacms -d mediacms > "$${dump_file}"; \
if [ $$? -eq 0 ]; then \
echo "Database dump created successfully: $${dump_file}"; \
else \
echo "Database dump failed"; \
exit 1; \
fi

View File

@@ -1,292 +0,0 @@
# MediaCMS 7.3 - Quick Start
## Minimal Deployment (No Code Required!)
MediaCMS 7.3 can be deployed with **just 2 files**:
1. `docker-compose.yaml`
2. `custom/` directory (optional)
**No git repo, no code checkout needed!** Everything runs from Docker images.
---
## Fresh Installation
### 1. Create deployment directory
```bash
mkdir mediacms && cd mediacms
```
### 2. Download docker-compose.yaml
```bash
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
```
Or with curl:
```bash
curl -O https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
```
### 3. Start MediaCMS
```bash
docker compose up -d
```
### 4. Access your site
- **Frontend**: http://localhost
- **Admin**: http://localhost/admin
- Username: `admin`
- Password: Check logs for auto-generated password:
```bash
docker compose logs migrations | grep "password:"
```
**That's it!** 🎉
---
## Optional: Customization
### Add Custom Settings
```bash
# 1. Create custom directory
mkdir -p custom/static/{images,css}
# 2. Download example template
wget -O custom/local_settings.py.example \
https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/custom/local_settings.py.example
# 3. Copy and edit
cp custom/local_settings.py.example custom/local_settings.py
nano custom/local_settings.py
```
Example customizations:
```python
# custom/local_settings.py
DEBUG = False
ALLOWED_HOSTS = ['media.example.com']
PORTAL_NAME = "My Media Portal"
```
### Add Custom Logo
```bash
# 1. Copy your logo
cp ~/my-logo.png custom/static/images/logo_dark.png
# 2. Reference in settings
cat >> custom/local_settings.py <<EOF
PORTAL_LOGO_DARK_PNG = "/custom/static/images/logo_dark.png"
EOF
# 3. Restart (no rebuild needed!)
docker compose restart web
```
### Add Custom CSS
```bash
# 1. Create CSS file
cat > custom/static/css/custom.css <<EOF
body {
font-family: 'Arial', sans-serif;
}
EOF
# 2. Reference in settings
cat >> custom/local_settings.py <<EOF
EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
EOF
# 3. Restart (no rebuild needed!)
docker compose restart web
```
**Note**: Both settings AND static files only need restart - nginx serves custom/ files directly!
---
## HTTPS with Let's Encrypt
### 1. Download cert overlay
```bash
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose-cert.yaml
```
### 2. Edit domains
```bash
nano docker-compose-cert.yaml
```
Change these lines:
```yaml
VIRTUAL_HOST: 'media.example.com' # Your domain
LETSENCRYPT_HOST: 'media.example.com' # Your domain
LETSENCRYPT_EMAIL: 'admin@example.com' # Your email
```
### 3. Start with SSL
```bash
docker compose -f docker-compose.yaml -f docker-compose-cert.yaml up -d
```
**SSL certificates are issued automatically!**
---
## File Structure
Your deployment directory:
```
mediacms/
├── docker-compose.yaml # Required
├── docker-compose-cert.yaml # Optional (for HTTPS)
└── custom/ # Optional (for customizations)
├── local_settings.py # Django settings
└── static/
├── images/ # Custom logos
└── css/ # Custom CSS
```
**Named volumes** (managed by Docker):
- `mediacms_postgres_data` - Database
- `mediacms_media_files` - Uploaded media
- `mediacms_static_files` - Static assets
- `mediacms_logs` - Application logs
---
## Common Commands
### View logs
```bash
docker compose logs -f web
docker compose logs -f celery_long
```
### Access Django shell
```bash
docker compose exec web python manage.py shell
```
### Create admin user
```bash
docker compose exec web python manage.py createsuperuser
```
### Restart service
```bash
docker compose restart web
```
### Stop everything
```bash
docker compose down
```
### Update to newer version
```bash
docker compose pull
docker compose up -d
```
---
## Backup
### Database backup
```bash
docker compose exec db pg_dump -U mediacms mediacms > backup_$(date +%Y%m%d).sql
```
### Media files backup
```bash
docker run --rm \
-v mediacms_media_files:/data:ro \
-v $(pwd):/backup \
alpine tar czf /backup/media_backup_$(date +%Y%m%d).tar.gz -C /data .
```
---
## Upgrading from 7.x?
If you're upgrading from an older MediaCMS version, see:
- **[UPGRADE_TO_7.3.md](./UPGRADE_TO_7.3.md)** - Complete migration guide
- **[DOCKER_RESTRUCTURE_SUMMARY.md](./DOCKER_RESTRUCTURE_SUMMARY.md)** - What changed
---
## Documentation
- **Customization**: Download [`custom/README.md`](https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/custom/README.md)
- **Upgrade Guide**: [UPGRADE_TO_7.3.md](./UPGRADE_TO_7.3.md)
- **Architecture**: [DOCKER_RESTRUCTURE_SUMMARY.md](./DOCKER_RESTRUCTURE_SUMMARY.md)
- **Project Docs**: https://docs.mediacms.io
---
## Troubleshooting
### Can't access the site?
Check services are running:
```bash
docker compose ps
```
All services should be "Up" or "Exited (0)" for migrations.
### Forgot admin password?
Check logs:
```bash
docker compose logs migrations | grep "password:"
```
Or create new admin:
```bash
docker compose exec web python manage.py createsuperuser
```
### Videos not encoding?
Check celery workers:
```bash
docker compose logs celery_long
docker compose logs celery_short
```
### Port 80 already in use?
Edit docker-compose.yaml to use different port:
```yaml
nginx:
ports:
- "8080:80" # Use port 8080 instead
```
Then access at http://localhost:8080
---
## Support
- **Issues**: https://github.com/mediacms-io/mediacms/issues
- **Discussions**: https://github.com/mediacms-io/mediacms/discussions
- **Docs**: https://docs.mediacms.io
---
**🎉 Enjoy MediaCMS!**

View File

@@ -69,7 +69,7 @@ Copyright Markos Gogoulos.
## Support and paid services
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Checkout our [services page](https://mediacms.io/#services/) for more information.
### Commercial Hostings
**Elestio**
@@ -108,7 +108,7 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
## Technology
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, uWSGI, React, Fine Uploader, video.js, FFMPEG, Bento4
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, Gunicorn, React, Fine Uploader, video.js, FFMPEG, Bento4
## Who is using it

View File

@@ -1,477 +0,0 @@
# Upgrade Guide: MediaCMS 7.x to 7.3
**IMPORTANT: This is a major architectural change. Read this entire guide before upgrading.**
---
## 🎯 Fresh Install (Not Upgrading)?
If you're starting fresh with 7.3, you don't need this guide!
**All you need:**
```bash
# 1. Download docker-compose.yaml
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
# 2. Start (creates everything automatically)
docker compose up -d
# 3. Done! Visit http://localhost
```
**Optional: Add customizations**
```bash
# Create custom/ directory
mkdir -p custom/static/{images,css}
# Download example settings
wget -O custom/local_settings.py.example \
https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/custom/local_settings.py.example
# Edit and use
cp custom/local_settings.py.example custom/local_settings.py
nano custom/local_settings.py
# Restart
docker compose restart web
```
See [`custom/README.md`](https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/custom/README.md) for customization options.
---
## ⚠️ Upgrading from 7.x? Continue reading...
## What Changed in 7.3
### Architecture Changes
- **Before**: Monolithic container (supervisor + nginx + uwsgi + celery in one)
- **After**: Microservices (separate nginx, web, celery_beat, celery_short, celery_long containers)
### Volume Strategy Changes
- **Before**: Entire project directory mounted (`./:/home/mediacms.io/mediacms/`)
- **After**: Named volumes for data, bind mount only for `custom/` directory
### Specific Changes
| Component | Before (7.x) | After (7.3) |
|-----------|-------------|-------------|
| media_files | Bind mount `./media_files` | Named volume `media_files` |
| static files | Bind mount `./static` | Named volume `static_files` (built into image) |
| logs | Bind mount `./logs` | Named volume `logs` |
| postgres_data | `../postgres_data` | Named volume `postgres_data` |
| Custom config | `cms/local_settings.py` in mounted dir | `custom/local_settings.py` bind mount |
| Static collection | Runtime (via entrypoint) | Build time (in Dockerfile) |
| User | Root with gosu switch | www-data from start |
## What You Need for 7.3
**Minimal deployment - NO CODE REQUIRED:**
1.`docker-compose.yaml` (download from release or docs)
2. ✅ Docker images (pulled from Docker Hub)
3. ⚠️ `custom/` directory (only if you have customizations)
**That's it!** No git repo, no code checkout needed.
## Pre-Upgrade Checklist
### 1. Backup Everything
```bash
# Stop services
docker compose down
# Backup media files
tar -czf backup_media_$(date +%Y%m%d).tar.gz media_files/
# Backup database
docker compose up -d db
docker compose exec db pg_dump -U mediacms mediacms > backup_db_$(date +%Y%m%d).sql
docker compose down
# Backup logs (optional)
tar -czf backup_logs_$(date +%Y%m%d).tar.gz logs/
# Backup local settings if you had them
cp cms/local_settings.py backup_local_settings.py 2>/dev/null || echo "No local_settings.py found"
# Backup current docker-compose.yaml
cp docker-compose.yaml docker-compose.yaml.old
```
### 2. Document Current Setup
```bash
# Save current docker-compose version
git branch backup-pre-7.3-upgrade
# Document current state
docker compose ps > pre_upgrade_state.txt
docker compose config > pre_upgrade_config.yaml
df -h > pre_upgrade_disk_usage.txt
```
### 3. Check Disk Space
You'll need enough space for:
- Existing data (media_files, postgres_data)
- New Docker volumes (will copy data here)
- Database dump
```bash
du -sh media_files/ postgres_data/ logs/
df -h .
```
## Upgrade Methods
### Method 1: Clean Migration (Recommended)
This method migrates your data to the new volume structure.
#### Step 1: Get New docker-compose.yaml
**Option A: Download from release**
```bash
# Download docker-compose.yaml for 7.3
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
# Or using curl
curl -O https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose.yaml
# Optional: Download HTTPS version
wget https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/docker-compose-cert.yaml
```
**Option B: Copy from docs/release notes**
- Copy the docker-compose.yaml content from release notes
- Save as `docker-compose.yaml` in your deployment directory
#### Step 2: Prepare Custom Configuration (if needed)
```bash
# Create custom directory structure (only if you need customizations)
mkdir -p custom/static/{images,css}
touch custom/static/{images,css}/.gitkeep
# If you had local_settings.py, create it in custom/
if [ -f backup_local_settings.py ]; then
# Copy your old settings
cp backup_local_settings.py custom/local_settings.py
echo "✓ Migrated local_settings.py"
else
# Download example template (optional)
wget -O custom/local_settings.py.example \
https://raw.githubusercontent.com/mediacms-io/mediacms/v7.3/custom/local_settings.py.example
echo "Downloaded example template to custom/local_settings.py.example"
fi
# Copy any custom logos/css you had
# (adjust paths as needed for your old setup)
# cp my-old-logo.png custom/static/images/logo_dark.png
# cp my-custom.css custom/static/css/custom.css
```
#### Step 3: Start New Stack (Without Data)
```bash
# Pull new images
docker compose pull
# Start database first
docker compose up -d db redis
# Wait for DB to be ready
sleep 10
```
#### Step 4: Restore Database
```bash
# Copy backup into container
docker compose cp backup_db_*.sql db:/tmp/backup.sql
# Restore database
docker compose exec db psql -U mediacms mediacms < /tmp/backup.sql
# Or from host:
cat backup_db_*.sql | docker compose exec -T db psql -U mediacms mediacms
```
#### Step 5: Restore Media Files
```bash
# Start all services (will create volumes)
docker compose up -d
# Find the volume name
docker volume ls | grep media_files
# Copy media files to volume
# Method A: Using a temporary container
docker run --rm \
-v $(pwd)/media_files:/source:ro \
-v mediacms_media_files:/dest \
alpine sh -c "cp -av /source/* /dest/"
# Method B: Using existing container
docker compose exec web sh -c "exit" # Ensure web is running
# Then copy from host
tar -C media_files -cf - . | docker compose exec -T web tar -C /home/mediacms.io/mediacms/media_files -xf -
```
#### Step 6: Verify and Test
```bash
# Check logs
docker compose logs -f web
# Verify media files are accessible
docker compose exec web ls -la /home/mediacms.io/mediacms/media_files/
# Check database connection
docker compose exec web python manage.py dbshell
# Access the site
curl http://localhost
# Check admin panel
# Visit http://localhost/admin
```
### Method 2: In-Place Migration with Symlinks (Advanced)
**Warning**: This is more complex but avoids data copying.
#### Step 1: Keep Old Data Locations
```bash
# Modify docker-compose.yaml to mount old locations temporarily
# Add to appropriate services:
volumes:
- ./media_files:/home/mediacms.io/mediacms/media_files
- ./logs:/home/mediacms.io/mediacms/logs
# Instead of named volumes
```
#### Step 2: Gradually Migrate
After confirming everything works:
1. Copy data to named volumes
2. Remove bind mounts
3. Switch to named volumes
### Method 3: Fresh Install (If Possible)
If your MediaCMS instance is new or test:
```bash
# Backup what you need
# ...
# Clean slate
docker compose down -v
rm -rf media_files/ logs/ static/
# Fresh start
docker compose up -d
```
## Post-Upgrade Steps
### 1. Verify Everything Works
```bash
# Check all services are running
docker compose ps
# Should see: migrations (exited 0), web, nginx, celery_beat, celery_short, celery_long, db, redis
# Check logs for errors
docker compose logs web
docker compose logs nginx
# Test upload functionality
# Test video encoding (check celery_long logs)
# Test frontend
```
### 2. Verify Media Files
```bash
# Check media files are accessible
docker compose exec web ls -lh /home/mediacms.io/mediacms/media_files/
# Check file counts match
# Old: ls media_files/ | wc -l
# New: docker compose exec web sh -c "ls /home/mediacms.io/mediacms/media_files/ | wc -l"
```
### 3. Verify Database
```bash
# Check users
docker compose exec db psql -U mediacms mediacms -c "SELECT count(*) FROM users_user;"
# Check videos
docker compose exec db psql -U mediacms mediacms -c "SELECT count(*) FROM files_media;"
```
### 4. Update Backups
```bash
# Update your backup scripts for new volume locations
# Use: make backup-db (if Makefile target exists)
# Or: docker compose exec db pg_dump ...
```
## Rollback Procedure
If something goes wrong:
### Quick Rollback
```bash
# Stop new version
docker compose down
# Restore old docker-compose file
mv docker-compose.yaml.old docker-compose.yaml
# Pull old images (if you had old image tags documented)
docker compose pull
# Start old version
docker compose up -d
```
### Full Rollback with Data Restore
```bash
# Stop everything
docker compose down -v
# Restore old docker-compose
mv docker-compose.yaml.old docker-compose.yaml
# Restore backups
tar -xzf backup_media_*.tar.gz -C ./media_files
cat backup_db_*.sql | docker compose exec -T db psql -U mediacms mediacms
# Start old version
docker compose up -d
```
## Common Issues & Solutions
### Issue: "Volume not found"
**Solution**: Volumes are created with project name prefix. Check:
```bash
docker volume ls
# Look for: mediacms_media_files, mediacms_static_files, etc.
```
### Issue: "Permission denied" on media files
**Solution**: Files must be owned by www-data (UID 33)
```bash
docker compose exec web chown -R www-data:www-data /home/mediacms.io/mediacms/media_files
```
### Issue: Static files not loading
**Solution**: Rebuild image (collectstatic runs at build time)
```bash
docker compose down
docker compose build --no-cache web
docker compose up -d
```
### Issue: Database connection refused
**Solution**: Check database is healthy
```bash
docker compose logs db
docker compose exec db pg_isready -U mediacms
```
### Issue: Custom settings not loading
**Solution**: Check custom/local_settings.py exists and syntax
```bash
docker compose exec web cat /home/mediacms.io/mediacms/custom/local_settings.py
docker compose exec web python -m py_compile /home/mediacms.io/mediacms/custom/local_settings.py
```
## Performance Considerations
### New Volume Performance
Named volumes are typically faster than bind mounts:
- **Before**: Filesystem overhead on host
- **After**: Direct container filesystem (better I/O)
### Monitoring Volume Usage
```bash
# Check volume sizes
docker system df -v
# Check specific volume
docker volume inspect mediacms_media_files
```
## New Backup Strategy
With named volumes, backups change:
```bash
# Database backup
docker compose exec db pg_dump -U mediacms mediacms > backup.sql
# Media files backup
docker run --rm \
-v mediacms_media_files:/data:ro \
-v $(pwd):/backup \
alpine tar czf /backup/media_backup_$(date +%Y%m%d).tar.gz -C /data .
```
Or use the Makefile:
```bash
make backup-db
```
## Getting Help
If you encounter issues:
1. **Check logs**: `docker compose logs <service>`
2. **Check GitHub Issues**: Search for similar problems
3. **Rollback**: Use the rollback procedure above
4. **Report**: Open an issue with:
- Your docker-compose.yaml
- Output of `docker compose ps`
- Relevant logs
- Steps to reproduce
## Summary of Benefits
After upgrading to 7.3:
**Better separation of concerns** - each service has one job
**Easier scaling** - scale web/workers independently
**Better security** - containers run as www-data, not root
**Faster deployments** - static files built into image
**Cleaner customization** - dedicated custom/ directory
**Easier SSL setup** - docker-compose-cert.yaml overlay
**Better volume management** - named volumes instead of bind mounts
## Timeline Recommendation
- **Small instance** (<100 videos): 30-60 minutes
- **Medium instance** (100-1000 videos): 1-3 hours
- **Large instance** (>1000 videos): Plan for several hours
Schedule during low-traffic period!

View File

@@ -24,6 +24,7 @@ INSTALLED_APPS = [
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",

View File

@@ -112,22 +112,18 @@ SITE_ID = 1
# set new paths for svg or png if you want to override
# svg has priority over png, so if you want to use
# custom pngs and not svgs, remove the lines with svgs
# Logo paths (served from /static/)
# Default logos are built into the image
# To customize: place files in custom/static/images/ and reference as /custom/static/images/file.png
# or set as empty strings to disable
# or set as empty strings
# example:
# PORTAL_LOGO_DARK_PNG = "/custom/static/images/my-logo.png"
# PORTAL_LOGO_DARK_SVG = ""
# PORTAL_LOGO_LIGHT_SVG = ""
# place the files on static/images folder
PORTAL_LOGO_DARK_SVG = "/static/images/logo_dark.svg"
PORTAL_LOGO_DARK_PNG = "/static/images/logo_dark.png"
PORTAL_LOGO_LIGHT_SVG = "/static/images/logo_light.svg"
PORTAL_LOGO_LIGHT_PNG = "/static/images/logo_dark.png"
# Extra CSS files to include in templates
# To add custom CSS: place files in custom/static/css/ and add paths here
# Use /custom/static/ prefix for files in custom/ directory
# Example: EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
# paths to extra css files to be included, eg "/static/css/custom.css"
# place css inside static/css folder
EXTRA_CSS_PATHS = []
# protection agains anonymous users
# per ip address limit, for actions as like/dislike/report
@@ -183,10 +179,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_URL = "/static/" # where js/css files are stored on the filesystem
MEDIA_URL = "/media/" # URL where static files are served from the server
STATIC_ROOT = BASE_DIR + "/static/"
# Additional locations for static files
# Note: custom/static is NOT included here because it's served directly by nginx
# at /custom/static/ and doesn't need collectstatic
STATICFILES_DIRS = []
# where uploaded + encoded media are stored
MEDIA_ROOT = BASE_DIR + "/media_files/"
@@ -261,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/bento4/bin/mp4hls"
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
# highly experimental, related with remote workers
ADMIN_TOKEN = ""
@@ -308,6 +300,7 @@ INSTALLED_APPS = [
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",
@@ -378,30 +371,41 @@ 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": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
}
"file": {
"level": "ERROR",
"class": "logging.FileHandler",
"filename": error_filename,
},
},
"loggers": {
"django": {
"handlers": ["file"],
"level": "ERROR",
"propagate": True,
},
},
"root": {"level": "INFO", "handlers": ["console"]},
}
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "db", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
REDIS_LOCATION = "redis://redis:6379/1"
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
@@ -552,6 +556,7 @@ DJANGO_ADMIN_URL = "admin/"
USE_SAML = False
USE_RBAC = False
USE_IDENTITY_PROVIDERS = False
USE_LTI = False # Enable LTI 1.3 integration
JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
USE_ROUNDED_CORNERS = True
@@ -560,7 +565,8 @@ ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False
# Whether to allow anonymous users to list all users
ALLOW_MEDIA_REPLACEMENT = False
ALLOW_ANONYMOUS_USER_LISTING = True
# Who can see the members page
@@ -597,15 +603,13 @@ WHISPER_MODEL = "base"
SIDEBAR_FOOTER_TEXT = ""
try:
# Load custom settings from custom/local_settings.py
import sys
sys.path.insert(0, BASE_DIR)
from custom.local_settings import * # noqa
# keep a local_settings.py file for local overrides
from .local_settings import * # noqa
# ALLOWED_HOSTS needs a url/ip
ALLOWED_HOSTS.append(FRONTEND_HOST.replace("http://", "").replace("https://", ""))
except ImportError:
# custom/local_settings.py not in use or empty
# local_settings not in use
pass
# Don't add new settings below that could be overridden in local_settings.py!!!
@@ -648,3 +652,19 @@ if USERS_NEEDS_TO_BE_APPROVED:
)
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")
# LTI 1.3 Integration Settings
if USE_LTI:
# Session timeout for LTI launches (seconds)
LTI_SESSION_TIMEOUT = 3600 # 1 hour
# Cookie settings required for iframe embedding from LMS
# IMPORTANT: Requires HTTPS to be enabled
SESSION_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# Use cached_db for reliability - stores in both cache AND database
# This prevents session loss during multiple simultaneous LTI launches

View File

@@ -25,6 +25,7 @@ urlpatterns = [
re_path(r"^", include("files.urls")),
re_path(r"^", include("users.urls")),
re_path(r"^accounts/", include("allauth.urls")),
re_path(r"^lti/", include("lti.urls")),
re_path(r"^api-auth/", include("rest_framework.urls")),
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),

View File

@@ -1 +1 @@
VERSION = "7.2.1"
VERSION = "7.8124"

View File

@@ -1,41 +0,0 @@
user nginx;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 10240;
}
worker_rlimit_nofile 20000; #each connection needs a filehandle (or 2 if you are proxying)
http {
proxy_connect_timeout 75;
proxy_read_timeout 12000;
client_max_body_size 5800M;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 10;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/mediacms/nginx-main.access.log;
error_log /var/log/mediacms/nginx-main.error.log;
gzip on;
gzip_disable "msie6";
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio"';
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

View File

@@ -1,16 +0,0 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@@ -1,24 +0,0 @@
[uwsgi]
chdir = /home/mediacms.io/mediacms/
virtualenv = /home/mediacms.io
module = cms.wsgi
uid=www-data
gid=www-data
processes = 2
threads = 2
master = true
socket = 0.0.0.0:9000
workers = 2
vacuum = true
hook-master-start = unix_signal:15 gracefully_kill_them_all
need-app = true
die-on-term = true
buffer-size=32768

View File

@@ -1,238 +0,0 @@
# Custom Configuration
This directory allows you to customize MediaCMS without modifying the codebase or rebuilding images.
## How It Works - Production Ready!
**The Flow:**
```
1. CI/CD builds base image: docker build (no custom files)
Pushes to Docker Hub
2. Production pulls image: docker compose pull
Mounts custom/ directory
3. You add files: custom/static/css/custom.css
custom/static/images/logo.png
Nginx serves directly!
4. You reference in settings: EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
PORTAL_LOGO_DARK_PNG = "/custom/static/images/logo.png"
Restart containers
5. Done! No rebuild needed!
```
**Key Points:**
- ✅ Files go in `custom/static/` on your host
- ✅ Nginx serves them directly from `/custom/static/` URL
-**NO rebuild needed** - just restart containers!
- ✅ Works with pre-built images from Docker Hub
- ✅ Perfect for production deployments
## Quick Start
### Option 1: No Customization (Default)
Just run docker compose - everything works out of the box:
```bash
docker compose up -d
```
### Option 2: With Customization
Add your custom files, then restart:
```bash
# 1. Copy example settings
cp custom/local_settings.py.example custom/local_settings.py
# 2. Edit settings
nano custom/local_settings.py
# 3. Restart containers (no rebuild!)
docker compose restart web celery_beat celery_short celery_long
```
## Customization Options
### 1. Django Settings (`local_settings.py`)
**Create the file:**
```bash
cp custom/local_settings.py.example custom/local_settings.py
```
**Edit with your settings:**
```python
# custom/local_settings.py
DEBUG = False
ALLOWED_HOSTS = ['example.com']
PORTAL_NAME = "My Media Site"
```
**Apply changes (restart only - no rebuild):**
```bash
docker compose restart web celery_beat celery_short celery_long
```
### 2. Custom Logo
**Add your logo:**
```bash
cp ~/my-logo.png custom/static/images/logo_dark.png
```
**Reference it in settings:**
```bash
cat >> custom/local_settings.py <<EOF
PORTAL_LOGO_DARK_PNG = "/custom/static/images/logo_dark.png"
EOF
```
**Restart (no rebuild needed!):**
```bash
docker compose restart web
```
### 3. Custom CSS
**Create CSS file:**
```bash
cat > custom/static/css/custom.css <<EOF
body {
font-family: 'Arial', sans-serif;
}
.header {
background-color: #333;
}
EOF
```
**Reference it in settings:**
```bash
cat >> custom/local_settings.py <<EOF
EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
EOF
```
**Restart (no rebuild needed!):**
```bash
docker compose restart web
```
## Directory Structure
```
custom/
├── README.md # This file
├── local_settings.py.example # Template (copy to local_settings.py)
├── local_settings.py # Your settings (gitignored)
└── static/
├── images/ # Custom logos (gitignored)
│ └── logo_dark.png
└── css/ # Custom CSS (gitignored)
└── custom.css
```
## Important Notes
**No rebuild needed** - nginx serves custom/ files directly
**Works with pre-built images** - perfect for production
**Files are gitignored** - your customizations won't be committed
**Settings need restart only** - just restart containers
**Static files also just restart** - served directly by nginx
## Complete Example
```bash
# 1. Create settings file
cp custom/local_settings.py.example custom/local_settings.py
# 2. Add custom logo
cp ~/logo.png custom/static/images/logo_dark.png
# 3. Add custom CSS
echo "body { background: #f5f5f5; }" > custom/static/css/custom.css
# 4. Configure settings to use them
cat >> custom/local_settings.py <<EOF
# Custom branding
PORTAL_NAME = "My Media Portal"
PORTAL_LOGO_DARK_PNG = "/custom/static/images/logo_dark.png"
EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
# Security
DEBUG = False
ALLOWED_HOSTS = ['media.example.com']
EOF
# 5. Apply changes (just restart!)
docker compose restart web
# Done! No rebuild needed.
```
## URL Paths Explained
| Your file | nginx serves at | You reference as |
|-----------|----------------|------------------|
| `custom/static/css/custom.css` | `http://localhost/custom/static/css/custom.css` | `"/custom/static/css/custom.css"` |
| `custom/static/images/logo.png` | `http://localhost/custom/static/images/logo.png` | `"/custom/static/images/logo.png"` |
**Why `/custom/static/`?**
- Distinguishes from core `/static/` (built into image)
- Allows nginx to serve from different mount point
- No rebuild needed when files change
## Troubleshooting
**Changes not appearing?**
- Restart containers: `docker compose restart web nginx`
- Check nginx has custom/ mounted: `docker compose exec nginx ls /var/www/custom`
- Check file exists: `docker compose exec nginx ls /var/www/custom/css/`
- Test URL: `curl http://localhost/custom/static/css/custom.css`
**Import errors?**
- Make sure `local_settings.py` has valid Python syntax
- Check logs: `docker compose logs web`
**Logo not showing?**
- Verify file is in `custom/static/images/`
- Check path in `local_settings.py` uses `/custom/static/` prefix
- Restart web container: `docker compose restart web`
## Advanced: Multiple CSS Files
```python
# custom/local_settings.py
EXTRA_CSS_PATHS = [
"/custom/static/css/colors.css",
"/custom/static/css/fonts.css",
"/custom/static/css/layout.css",
]
```
## Advanced: Environment-Specific Settings
```python
# custom/local_settings.py
import os
if os.getenv('ENVIRONMENT') == 'production':
DEBUG = False
ALLOWED_HOSTS = ['media.example.com']
else:
DEBUG = True
ALLOWED_HOSTS = ['*']
```
Then set in docker-compose.yaml:
```yaml
web:
environment:
ENVIRONMENT: production
```

View File

@@ -1,57 +0,0 @@
# MediaCMS Local Settings Example
# Copy this file to local_settings.py and customize as needed:
# cp custom/local_settings.py.example custom/local_settings.py
# ===== Basic Settings =====
# DEBUG = False
# ALLOWED_HOSTS = ['example.com', 'www.example.com']
# PORTAL_NAME = "My Media Portal"
# ===== Database Settings =====
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': 'mediacms',
# 'USER': 'mediacms',
# 'PASSWORD': 'mediacms',
# 'HOST': 'db',
# 'PORT': '5432',
# }
# }
# ===== Custom Branding =====
# Custom logos (place files in custom/static/images/)
# Nginx serves these directly from /custom/static/ (no rebuild needed!)
# PORTAL_LOGO_DARK_SVG = "/custom/static/images/logo_dark.svg"
# PORTAL_LOGO_DARK_PNG = "/custom/static/images/logo_dark.png"
# PORTAL_LOGO_LIGHT_SVG = "/custom/static/images/logo_light.svg"
# PORTAL_LOGO_LIGHT_PNG = "/custom/static/images/logo_light.png"
# Custom CSS (place files in custom/static/css/)
# Nginx serves these directly from /custom/static/ (no rebuild needed!)
# EXTRA_CSS_PATHS = ["/custom/static/css/custom.css"]
# ===== Email Settings =====
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.gmail.com'
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = 'your-email@example.com'
# EMAIL_HOST_PASSWORD = 'your-password'
# DEFAULT_FROM_EMAIL = 'noreply@example.com'
# ===== Security Settings =====
# SECRET_KEY = 'your-secret-key-here'
# SECURE_SSL_REDIRECT = True
# SESSION_COOKIE_SECURE = True
# CSRF_COOKIE_SECURE = True
# ===== Other Settings =====
# Any other Django setting can be overridden here
# See cms/settings.py for available settings

View File

View File

@@ -1,7 +1,7 @@
# MediaCMS: Document Changes for DEIC
## Configuration Changes
The following changes are required in `config/local_settings.py`:
The following changes are required in `deploy/docker/local_settings.py`:
```python

3
deploy/docker/README.md Normal file
View File

@@ -0,0 +1,3 @@
# MediaCMS on Docker
See: [Details](../../docs/Docker_deployment.md)

38
deploy/docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
# forward request and error logs to docker log collector
ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && \
ln -sf /dev/stdout /var/log/nginx/mediacms.io.access.log && ln -sf /dev/stderr /var/log/nginx/mediacms.io.error.log
cp /home/mediacms.io/mediacms/deploy/docker/local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
touch /home/mediacms.io/mediacms/logs/debug.log
mkdir -p /var/run/mediacms
chown www-data:www-data /var/run/mediacms
TARGET_GID=$(stat -c "%g" /home/mediacms.io/mediacms/)
EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)
# Create new group using target GID and add www-data user
if [ $EXISTS == "0" ]; then
groupadd -g $TARGET_GID tempgroup
usermod -a -G tempgroup www-data
else
# GID exists, find group name and add
GROUP=$(getent group $TARGET_GID | cut -d: -f1)
usermod -a -G $GROUP www-data
fi
# We should do this only for folders that have a different owner, since it is an expensive operation
# Also ignoring .git folder to fix this issue https://github.com/mediacms-io/mediacms/issues/934
# Exclude package-lock.json files that may not exist or be removed during frontend setup
find /home/mediacms.io/mediacms ! \( -path "*.git*" -o -name "package-lock.json" \) -exec chown www-data:$TARGET_GID {} + 2>/dev/null || true
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
exec "$@"

View File

@@ -0,0 +1,36 @@
import os
FRONTEND_HOST = os.getenv('FRONTEND_HOST', 'http://localhost')
PORTAL_NAME = os.getenv('PORTAL_NAME', 'MediaCMS')
SECRET_KEY = os.getenv('SECRET_KEY', 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2')
REDIS_LOCATION = os.getenv('REDIS_LOCATION', 'redis://redis:6379/1')
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv('POSTGRES_NAME', 'mediacms'),
"HOST": os.getenv('POSTGRES_HOST', 'db'),
"PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True},
}
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_LOCATION,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
# CELERY STUFF
BROKER_URL = REDIS_LOCATION
CELERY_RESULT_BACKEND = BROKER_URL
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
DEBUG = os.getenv('DEBUG', 'False') == 'True'

View File

@@ -19,7 +19,10 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

View File

@@ -1,25 +1,27 @@
# Use existing X-Forwarded-Proto from reverse proxy if present, otherwise use $scheme
map $http_x_forwarded_proto $forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}
server {
listen 80 ;
gzip on;
access_log /var/log/mediacms/nginx.access.log;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/mediacms/nginx.error.log warn;
error_log /var/log/nginx/mediacms.io.error.log warn;
location /static {
alias /var/www/static ;
}
location /custom/static {
alias /var/www/custom ;
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /var/www/media/original;
alias /home/mediacms.io/mediacms/media_files/original;
}
location /media {
alias /var/www/media ;
alias /home/mediacms.io/mediacms/media_files ;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
@@ -32,7 +34,10 @@ server {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/uwsgi_params;
uwsgi_pass web:9000;
proxy_pass http://127.0.0.1:9000;
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 $forwarded_proto;
}
}

70
deploy/docker/prestart.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
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}
if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
echo "Running migrations service"
python manage.py migrate
EXISTING_INSTALLATION=`echo "from users.models import User; print(User.objects.exists())" |python manage.py shell`
if [ "$EXISTING_INSTALLATION" = "True" ]; then
echo "Loaddata has already run"
else
echo "Running loaddata and creating admin user"
python manage.py loaddata fixtures/encoding_profiles.json
python manage.py loaddata fixtures/categories.json
# post_save, needs redis to succeed (ie. migrate depends on redis)
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
--no-input \
--username=$ADMIN_USER \
--email=$ADMIN_EMAIL \
--database=default || true
echo "Created admin user with password: $ADMIN_PASSWORD"
fi
echo "RUNNING COLLECTSTATIC"
python manage.py collectstatic --noinput
# echo "Updating hostname ..."
# TODO: Get the FRONTEND_HOST from cms/local_settings.py
# echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
fi
# Setting up internal nginx server
# HTTPS setup is delegated to a reverse proxy running infront of the application
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
cp deploy/docker/nginx.conf /etc/nginx/
#### Supervisord Configurations #####
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
echo "Enabling gunicorn app server"
cp deploy/docker/supervisord/supervisord-gunicorn.conf /etc/supervisor/conf.d/supervisord-gunicorn.conf
fi
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
echo "Enabling nginx as gunicorn app proxy and media server"
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
fi
if [ X"$ENABLE_CELERY_BEAT" = X"yes" ] ; then
echo "Enabling celery-beat scheduling server"
cp deploy/docker/supervisord/supervisord-celery_beat.conf /etc/supervisor/conf.d/supervisord-celery_beat.conf
fi
if [ X"$ENABLE_CELERY_SHORT" = X"yes" ] ; then
echo "Enabling celery-short task worker"
cp deploy/docker/supervisord/supervisord-celery_short.conf /etc/supervisor/conf.d/supervisord-celery_short.conf
fi
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
echo "Enabling celery-long task worker"
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
rm /var/run/mediacms/* -f # remove any stale id, so that on forced restarts of celery workers there are no stale processes that prevent new ones
fi

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICwzCCAaugAwIBAgIJAOyvdwguJQd+MA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMTCWxvY2FsaG9zdDAeFw0yMTAxMjQxMjUwMzFaFw0zMTAxMjIxMjUwMzFaMBQx
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAONswEwBzkgoO+lkewiKUnwvYqC54qleCUg9hidqjoyzd5XWKh1mIF7aaSCG
rJGSxCce8CbqAqGkpvsgXzwwbY72l7FwmAXFHO5ObQfpmFhjt2fsKRM9MTCo/UyU
liuhgP+Q+BNzUontTUC40NVHs8R7IHG4z8unB7qB/7zGK2tfilLB8JDqPTkc22vN
C4P1YxiGyY5bm37wQrroC9zPJ8bqanrF9Y90QJHubibnPWqnZvK2HkDWjp5LYkn8
IuzBycs1cLd8eMjU9aT72kweykvnGDDc3YbXFzT2zBTGSFEBROsVdPrNF9PaeE3j
pu4UZ8Ge3Fp3VYd+04DnWtbQq0MCAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MA0GCSqGSIb3DQEBBQUAA4IBAQAdm2aGn4evosbdWgBHgzr6oYWBIiPpf1SA
GXizuf5OaMActFP0rZ0mogndLH5d51J2qqSfOtaWSA5qwlPvDSTn1nvJeHoVLfZf
kQHaB7/DaOPGsZCQBELPhYHwl7+Ej3HYE+siiaRfjC2NVgf8P/pAsTlKbe2e+34l
GwWSFol24w5xAmUezCF41JiZbqHoZhSh7s/PuJnK2RvhpjkrIot8GvxnbvOcKDIv
JzEKo3qPq8pc5RBkpP7Kp2+EgAYn1xAn0CekxZracW/MY+tg2mCeFucZW2V1iwVs
LpAw6GJnjYz5mbrQskPbrJ9t78JGUKQ0kL/VUTfryUHMHYCiJlvd
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA42zATAHOSCg76WR7CIpSfC9ioLniqV4JSD2GJ2qOjLN3ldYq
HWYgXtppIIaskZLEJx7wJuoCoaSm+yBfPDBtjvaXsXCYBcUc7k5tB+mYWGO3Z+wp
Ez0xMKj9TJSWK6GA/5D4E3NSie1NQLjQ1UezxHsgcbjPy6cHuoH/vMYra1+KUsHw
kOo9ORzba80Lg/VjGIbJjlubfvBCuugL3M8nxupqesX1j3RAke5uJuc9aqdm8rYe
QNaOnktiSfwi7MHJyzVwt3x4yNT1pPvaTB7KS+cYMNzdhtcXNPbMFMZIUQFE6xV0
+s0X09p4TeOm7hRnwZ7cWndVh37TgOda1tCrQwIDAQABAoIBAQCmKKyOW7tlCNBN
AzbI1JbTWKOMnoM2DxhlCV5cqgOgVPcIKEL428bGxniMZRjr+vkJRBddtxdZFj1R
uSMbjJ5fF1dZMtQ/UvaCPhZ283p1CdXUPbz863ZnAPCf5Oea1RK0piw5ucYSM6h/
owgg65Qx92uK6uYW+uAwqg440+ihNvnaZoVTx5CjZbL9KISkrlNJnuYiB5vzOD0i
UVklO5Qz8VCuOcOVGZCA2SxHm4HAbg/aiQnpaUa9de4TsZ4ygF66pZh77T0wNOos
sS1riKtHQpX+osJyoTI/rIKFAhycsZ+AA7Qpu6GW4xQlNS6K8vRiIbktwkC+IT0O
RSn8Dg7BAoGBAPe5R8SpgXx9jKdA1eFa/Vjx5bmB96r2MviIOIWF8rs2K33xe+rj
v+BZ2ZjdpVjcm2nRMf9r/eDq2ScNFWmKoZsUmdyT84Qq9yLcTSUdno+zCy+L0LNH
DqJq5jIxJaV7amHeR/w10BVuiDmzhSsTmhfnXTUGRO/h2PjRyC3yEYdxAoGBAOsF
2+gTsdOGlq6AVzW5MLZkreq8WCU2wWpZRiCPh6HJa8htuynYxO5AWUiNUbYKddj2
0za9DFiXgH+Oo8wrkTYLEdN0T5/o+ScL5t3VG3m9R6pnuudLC2vmGQP0hNuZUpnF
7FzdJ85h6taR2bM1zFzOfl81K0BhTHGxTU2r70vzAoGAVXuLJ3LyqtnMKn72DzDN
0d6PTkdqBoW0qwyerHy/eRjFQ02MXE7BDJMUwmphv1tJCefVX/WNAwsnahFavTPI
dnJSccpgMtB8vXvV5yPkbmPzTTHrD6JKi4Nl8hYBjqwa1rDUmFSdfHfK7FZlcqrt
9qexAzYpnbmKnLoPYMNyhxECgYEAm5OCUeuPoL2MS7GLiXWwyFx3QFczZlcLzBGS
uYUpvLBwF/qDlhz3p9uS/tMFzyK3hktF4Ate+9o2ZroOtd31PzgusbJh7zIylGVt
i1VB3eGtaiFGeUuVIPTthE++Dvw80KxTXdnMOvNYmHduDBLF2H2c6/tvSSvfhbdf
u9XgD38CgYAiLcVySxMKNpsXatuC31wjT+rnaH22SD/7pXe2q6MRW/s+bGOspu0v
NeJSLoM98v8F99q0W0lgqesYJVI20Frru0DfXIp60ryaDolzve3Iwk8SOJUlcnUG
cCtmPUkjyr18QAlrcCB4PozJGjpPWyabaY8gGwo8wAEpJWHrIJlHew==
-----END RSA PRIVATE KEY-----

17
deploy/docker/start.sh Executable file
View File

@@ -0,0 +1,17 @@
#! /usr/bin/env sh
set -e
# If there's a prestart.sh script in the /app directory, run it before starting
PRE_START_PATH=deploy/docker/prestart.sh
echo "Checking for script in $PRE_START_PATH"
if [ -f $PRE_START_PATH ] ; then
echo "Running script $PRE_START_PATH"
. $PRE_START_PATH
else
echo "There is no script $PRE_START_PATH"
fi
# Start Supervisor, with Nginx and Gunicorn
echo "Starting server using supervisord..."
exec /usr/bin/supervisord

View File

@@ -0,0 +1,12 @@
[program:celery_beat]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=0
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=300
startinorder=true
command=/home/mediacms.io/bin/celery beat --pidfile=/var/run/mediacms/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log

View File

@@ -0,0 +1,13 @@
[program:celery_long]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=500
startinorder=true
startsecs=0
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks

View File

@@ -0,0 +1,12 @@
[program:celery_short]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=0
numprocs=1
user=www-data
directory=/home/mediacms.io/mediacms
priority=400
startinorder=true
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks

View File

@@ -0,0 +1,2 @@
[supervisord]
nodaemon=true

View File

@@ -0,0 +1,9 @@
[program:gunicorn]
command=/home/mediacms.io/bin/gunicorn cms.wsgi:application --workers=2 --threads=2 --worker-class=gthread --bind=127.0.0.1:9000 --user=www-data --group=www-data --timeout=120 --keep-alive=5 --max-requests=1000 --max-requests-jitter=50 --access-logfile=- --error-logfile=- --log-level=info --chdir=/home/mediacms.io/mediacms
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=100
startinorder=true
startsecs=0

View File

@@ -0,0 +1,11 @@
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=200
startinorder=true
startsecs=0
# Graceful stop, see http://nginx.org/en/docs/control.html
stopsignal=QUIT

View File

@@ -1,22 +0,0 @@
[Unit]
Description=MediaCMS celery beat
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms beat --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -1,29 +0,0 @@
[Unit]
Description=MediaCMS celery long queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERYD_NODES="long1"
Environment=CELERY_QUEUE="long_tasks"
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
Environment=CELERYD_MULTI="multi"
Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1"
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -1,39 +0,0 @@
[Unit]
Description=MediaCMS celery short queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERYD_NODES="short1 short2"
Environment=CELERY_QUEUE="short_tasks"
# Absolute or relative path to the 'celery' command:
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
# App instance to use
# comment out this line if you don't use an app
# or fully qualified:
#CELERY_APP="proj.tasks:app"
# How to call manage.py
Environment=CELERYD_MULTI="multi"
# Extra command-line arguments to the worker
Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
# - %n will be replaced with the first part of the nodename.
# - %I will be replaced with the current child process index
# and is important when using the prefork pool to avoid race conditions.
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -1,13 +0,0 @@
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEAo3MMiEY/fNbu+usIM0cDi6x8G3JBApv0Lswta4kiyedWT1WN51iQ
9zhOFpmcu6517f/fR9MUdyhVKHxxSqWQTcmTEFtz4P3VLTS/W1N5VbKE2VEMLpIi
wr350aGvV1Er0ujcp5n4O4h0I1tn4/fNyDe7+pHCdwM+hxe8hJ3T0/tKtad4fnIs
WHDjl4f7m7KuFfheiK7Efb8MsT64HDDAYXn+INjtDZrbE5XPw20BqyWkrf07FcPx
8o9GW50Ox7/FYq7jVMI/skEu0BRc8u6uUD9+UOuWUQpdeHeFcvLOgW53Z03XwWuX
RXosUKzBPuGtUDAaKD/HsGW6xmGr2W9yRmu27jKpfYLUb/eWbbnRJwCw04LdzPqv
jmtq02Gioo3lf5H5wYV9IYF6M8+q/slpbttsAcKERimD1273FBRt5VhSugkXWKjr
XDhoXu6vZgj8Opei38qPa8pI1RUFoXHFlCe6WpZQmU8efL8gAMrJr9jUIY8eea1n
u20t5B9ueb9JMjrNafcq6QkKhZLi6fRDDTUyeDvc0dN9R/3Yts97SXfdi1/lX7HS
Ht4zXd5hEkvjo8GcnjsfZpAC39QfHWkDaeUGEqsl3jXjVMfkvoVY51OuokPWZzrJ
M5+wyXNpfGbH67dPk7iHgN7VJvgX0SYscDPTtms50Vk7RwEzLeGuSHMCAQI=
-----END DH PARAMETERS-----

View File

@@ -1,84 +0,0 @@
server {
listen 80 ;
server_name localhost;
gzip on;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/nginx/mediacms.io.error.log warn;
# # redirect to https if logged in
# if ($http_cookie ~* "sessionid") {
# rewrite ^/(.*)$ https://localhost/$1 permanent;
# }
# # redirect basic forms to https
# location ~ (login|login_form|register|mail_password_form)$ {
# rewrite ^/(.*)$ https://localhost/$1 permanent;
# }
location /static {
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /home/mediacms.io/mediacms/media_files/original;
}
location /media {
alias /home/mediacms.io/mediacms/media_files ;
}
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
}
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate_key /etc/letsencrypt/live/localhost/privkey.pem;
ssl_certificate /etc/letsencrypt/live/localhost/fullchain.pem;
ssl_dhparam /etc/nginx/dhparams/dhparams.pem;
ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
gzip on;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/nginx/mediacms.io.error.log warn;
location /static {
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /home/mediacms.io/mediacms/media_files/original;
#auth_basic "auth protected area";
#auth_basic_user_file /home/mediacms.io/mediacms/deploy/local_install/.htpasswd;
}
location /media {
alias /home/mediacms.io/mediacms/media_files ;
}
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
}
}

View File

@@ -1,58 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFTjCCBDagAwIBAgISBNOUeDlerH9MkKmHLvZJeMYgMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDAzMTAxNzUxNDFaFw0y
MDA2MDgxNzUxNDFaMBYxFDASBgNVBAMTC21lZGlhY21zLmlvMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps5Jn18nW2tq/LYFDgQ1YZGLlpF/B2AAPvvH
3yuD+AcT4skKdZouVL/a5pXrptuYL5lthO9dlcja2tuO2ltYrb7Dp01dAIFaJE8O
DKd+Sv5wr8VWQZykqzMiMBgviml7TBvUHQjvCJg8UwmnN0XSUILCttd6u4qOzS7d
lKMMsKpYzLhElBT0rzhhsWulDiy6aAZbMV95bfR74nIWsBJacy6jx3jvxAuvCtkB
OVdOoVL6BPjDE3SNEk53bAZGIb5A9ri0O5jh/zBFT6tQSjUhAUTkmv9oZP547RnV
fDj+rdvCVk/fE+Jno36mcT183Qd/Ty3fWuqFoM5g/luhnfvWEwIDAQABo4ICYDCC
AlwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTd5EZBt74zu5XxT1uXQs6oM8qOuDAf
BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
MBYGA1UdEQQPMA2CC21lZGlhY21zLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAXqdz+d9WwOe1Nkh90Eng
MnqRmgyEoRIShBh1loFxRVgAAAFwxcnL+AAABAMARzBFAiAb3yeBuW3j9MxcRc0T
icUBvEa/rH7Fv2eB0oQlnZ1exQIhAPf+CtTXmzxoeT/BBiivj4AmGDsq4xWhe/U6
BytYrKLeAHYAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFwxcnM
HAAABAMARzBFAiAuP5gKyyaT0LVXxwjYD9zhezvxf4Icx0P9pk75c5ao+AIhAK0+
fSJv+WTXciMT6gA1sk/tuCHuDFAuexSA/6TcRXcVMA0GCSqGSIb3DQEBCwUAA4IB
AQCPCYBU4Q/ro2MUkjDPKGmeqdxQycS4R9WvKTG/nmoahKNg30bnLaDPUcpyMU2k
sPDemdZ7uTGLZ3ZrlIva8DbrnJmrTPf9BMwaM6j+ZV/QhxvKZVIWkLkZrwiVI57X
Ba+rs5IEB4oWJ0EBaeIrzeKG5zLMkRcIdE4Hlhuwu3zGG56c+wmAPuvpIDlYoO6o
W22xRdxoTIHBvkzwonpVYUaRcaIw+48xnllxh1dHO+X69DT45wlF4tKveOUi+L50
4GWJ8Vjv7Fot/WNHEM4Mnmw0jHj9TPkIZKnPNRMdHmJ5CF/FJFDiptOeuzbfohG+
mdvuInb8JDc0XBE99Gf/S4/y
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmzkmfXydba2r8
tgUOBDVhkYuWkX8HYAA++8ffK4P4BxPiyQp1mi5Uv9rmleum25gvmW2E712VyNra
247aW1itvsOnTV0AgVokTw4Mp35K/nCvxVZBnKSrMyIwGC+KaXtMG9QdCO8ImDxT
Cac3RdJQgsK213q7io7NLt2UowywqljMuESUFPSvOGGxa6UOLLpoBlsxX3lt9Hvi
chawElpzLqPHeO/EC68K2QE5V06hUvoE+MMTdI0STndsBkYhvkD2uLQ7mOH/MEVP
q1BKNSEBROSa/2hk/njtGdV8OP6t28JWT98T4mejfqZxPXzdB39PLd9a6oWgzmD+
W6Gd+9YTAgMBAAECggEADnEJuryYQbf5GUwBAAepP3tEZJLQNqk/HDTcRxwTXuPt
+tKBD1F79WZu40vTjSyx7l0QOFQo/BDZsd0Ubx89fD1p3xA5nxOT5FTb2IifzIpe
4zjokOGo+BGDQjq10vvy6tH1+VWOrGXRwzawvX5UCRhpFz9sptQGLQmDsZy0Oo9B
LtavYVUqsbyqRWlzaclHgbythegIACWkqcalOzOtx+l6TGBRjej+c7URcwYBfr7t
XTAzbP+vnpaJovZyZT1eekr0OLzMpnjx4HvRvzL+NxauRpn6KfabsTfZlk8nrs4I
UdSjeukj1Iz8rGQilHdN/4dVJ3KzrlHVkVTBSjmMUQKBgQDaVXZnhAScfdiKeZbO
rdUAWcnwfkDghtRuAmzHaRM/FhFBEoVhdSbBuu+OUyBnIw/Ra4o2ePuEBcKIUiQO
w2tnE1CY5PPAcjw+OCSpvzy5xxjaqaRbm9BJp3FTeEYGLXERnchPpHg/NpexuF22
QOJ+FrysPyNMxuQp47ZwO9WT3QKBgQDDlSGjq/eeWxemwf7ZqMVlRyqsdJsgnCew
DkC62IGiYCBDfeEmndN+vcA/uzJHYV4iXiqS3aYJCWGaZFMhdIhIn5MgULvO1j5G
u/MxuzaaNPz22FlNCWTLBw4T1HOOvyTL+nLtZDKJ/BHxgHCmur1kiGvvZWrcCthD
afLEmseqrwKBgBuLZKCymxJTHhp6NHhmndSpfzyD8RNibzJhw+90ZiUzV4HqIEGn
Ufhm6Qn/mrroRXqaIpm0saZ6Q4yHMF1cchRS73wahlXlE4yV8KopojOd1pjfhgi4
o5JnOXjaV5s36GfcjATgLvtqm8CkDc6MaQaXP75LSNzKysYuIDoQkmVRAoGAAghF
rja2Pv4BU+lGJarcSj4gEmSvy/nza5/qSka/qhlHnIvtUAJp1TJRkhf24MkBOmgy
Fw6YkBV53ynVt05HsEGAPOC54t9VDFUdpNGmMpoEWuhKnUNQuc9b9RbLEJup3TjA
Avl8kPR+lzzXbtQX7biBLp6mKp0uPB0YubRGCN8CgYA0JMxK0x38Q2x3AQVhOmZh
YubtIa0JqVJhvpweOCFnkq3ebBpLsWYwiLTn86vuD0jupe5M3sxtefjkJmAKd8xY
aBU7QWhjh1fX4mzmggnbjcrIFbkIHsxwMeg567U/4AGxOOUsv9QUn37mqycqRKEn
YfUyYNLM6F3MmQAOs2kaHw==
-----END PRIVATE KEY-----

View File

@@ -1,13 +0,0 @@
[Unit]
Description=MediaCMS uwsgi
[Service]
ExecStart=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/local_install/uwsgi.ini
ExecStop=/usr/bin/killall -9 uwsgi
RestartSec=3
#ExecRestart=killall -9 uwsgi; sleep 5; /home/sss/bin/uwsgi --ini /home/sss/wordgames/uwsgi.ini
Restart=always
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +0,0 @@
/home/mediacms.io/mediacms/logs/*.log {
weekly
missingok
rotate 7
compress
notifempty
}

View File

@@ -1,34 +0,0 @@
module selinux-mediacms 1.0;
require {
type init_t;
type var_t;
type redis_port_t;
type postgresql_port_t;
type httpd_t;
type httpd_sys_content_t;
type httpd_sys_rw_content_t;
class file { append create execute execute_no_trans getattr ioctl lock open read rename setattr unlink write };
class dir { add_name remove_name rmdir };
class tcp_socket name_connect;
class lnk_file read;
}
#============= httpd_t ==============
allow httpd_t var_t:file { getattr open read };
#============= init_t ==============
allow init_t postgresql_port_t:tcp_socket name_connect;
allow init_t redis_port_t:tcp_socket name_connect;
allow init_t httpd_sys_content_t:dir rmdir;
allow init_t httpd_sys_content_t:file { append create execute execute_no_trans ioctl lock open read rename setattr unlink write };
allow init_t httpd_sys_content_t:lnk_file read;
allow init_t httpd_sys_rw_content_t:dir { add_name remove_name rmdir };
allow init_t httpd_sys_rw_content_t:file { create ioctl lock open read setattr unlink write };

View File

@@ -1,27 +0,0 @@
[uwsgi]
chdir = /home/mediacms.io/mediacms/
virtualenv = /home/mediacms.io
module = cms.wsgi
uid=www-data
gid=www-data
processes = 2
threads = 2
master = true
socket = 127.0.0.1:9000
#socket = /home/mediacms.io/mediacms/deploy/uwsgi.sock
workers = 2
vacuum = true
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
disable-logging = true
buffer-size=32768

View File

@@ -1,16 +0,0 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@@ -1,62 +0,0 @@
version: "3.8"
# HTTPS/SSL certificate overlay for docker-compose.yaml
# Uses nginx-proxy with Let's Encrypt via acme-companion
#
# Usage:
# docker compose -f docker-compose.yaml -f docker-compose-cert.yaml up -d
#
# Before running:
# 1. Change VIRTUAL_HOST to your domain
# 2. Change LETSENCRYPT_HOST to your domain
# 3. Change LETSENCRYPT_EMAIL to your email
services:
# Reverse proxy with automatic SSL
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- conf:/etc/nginx/conf.d
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- dhparam:/etc/nginx/dhparam
- certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./config/nginx-proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
# Let's Encrypt certificate manager
acme-companion:
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
restart: unless-stopped
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
# Override nginx to work with nginx-proxy
nginx:
expose:
- "80"
ports: [] # Remove ports, nginx-proxy handles external access
environment:
# CHANGE THESE VALUES:
VIRTUAL_HOST: 'mediacms.example.com'
LETSENCRYPT_HOST: 'mediacms.example.com'
LETSENCRYPT_EMAIL: 'admin@example.com'
volumes:
# nginx-proxy volumes
conf:
vhost:
html:
dhparam:
certs:
acme:

View File

@@ -1,7 +1,4 @@
version: "3.8"
# Development setup with hot-reload and file mounts
# This is the ONLY compose file that mounts the source code
version: "3"
services:
migrations:
@@ -11,126 +8,82 @@ services:
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:7.3
command: ["/bin/bash", "/home/mediacms.io/mediacms/scripts/run-migrations.sh"]
image: mediacms/mediacms-dev:latest
volumes:
- ./:/home/mediacms.io/mediacms/
command: "./deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: "no"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
web:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
ports:
- "80:8000"
command: ["python", "manage.py", "runserver", "0.0.0.0:8000"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
frontend:
image: node:20-alpine
image: node:20
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
command: sh -c "npm install && npm run start"
command: bash -c "npm install && npm run start"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
environment:
- NODE_ENV=development
env_file:
- ./frontend/.env
volumes:
- ./frontend:/home/mediacms.io/mediacms/frontend/
depends_on:
- web
celery_beat:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "beat", "--loglevel=INFO"]
web:
image: mediacms/mediacms-dev:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
celery_short:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "short_tasks", "-c", "10", "--soft-time-limit=300", "--loglevel=INFO", "-n", "short@%h"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
celery_long:
image: mediacms/mediacms-dev:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "long_tasks", "-c", "1", "-Ofair", "--prefetch-multiplier=1", "--loglevel=INFO", "-n", "long@%h"]
environment:
DEVELOPMENT_MODE: 'True'
DEBUG: 'True'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- ./:/home/mediacms.io/mediacms/
- migrations
db:
image: postgres:17.2-alpine
restart: unless-stopped
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
restart: unless-stopped
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
celery_worker:
image: mediacms/mediacms-dev:latest
deploy:
replicas: 1
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- web

View File

@@ -1,126 +1,86 @@
version: "3.8"
version: "3"
services:
migrations:
image: mediacms/mediacms:7.3
command: ["/bin/bash", "/home/mediacms.io/mediacms/scripts/run-migrations.sh"]
image: mediacms/mediacms:latest
volumes:
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: # ADMIN_PASSWORD: 'uncomment_and_set_password_here'
restart: "no"
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./custom:/home/mediacms.io/mediacms/custom:ro
- static_files:/home/mediacms.io/mediacms/static
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
web:
image: mediacms/mediacms:7.3
restart: unless-stopped
expose:
- "9000"
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
db:
condition: service_healthy
volumes:
- ./custom:/home/mediacms.io/mediacms/custom:ro
- static_files:/home/mediacms.io/mediacms/static
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
nginx:
image: mediacms/mediacms-nginx:7.3
restart: unless-stopped
image: mediacms/mediacms:latest
deploy:
replicas: 1
ports:
- "80:80"
depends_on:
- web
volumes:
- ./custom/static:/var/www/custom:ro
- static_files:/var/www/static:ro
- media_files:/var/www/media:ro
- logs:/var/log/mediacms
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_CELERY_BEAT: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
- migrations
celery_beat:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
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
redis:
condition: service_healthy
image: mediacms/mediacms:latest
volumes:
- ./custom:/home/mediacms.io/mediacms/custom:ro
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_short:
image: mediacms/mediacms-worker:7.3
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "short_tasks", "-c", "10", "--soft-time-limit=300", "--loglevel=INFO", "-n", "short@%h"]
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
- redis
celery_worker:
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ./custom:/home/mediacms.io/mediacms/custom:ro
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
celery_long:
image: mediacms/mediacms-worker:7.3
# To use extra codecs, change image to: mediacms/mediacms-worker:7.3-full
restart: unless-stopped
command: ["/home/mediacms.io/bin/celery", "-A", "cms", "worker", "-Q", "long_tasks", "-c", "1", "-Ofair", "--prefetch-multiplier=1", "--loglevel=INFO", "-n", "long@%h"]
- ./:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
depends_on:
migrations:
condition: service_completed_successfully
redis:
condition: service_healthy
volumes:
- ./custom:/home/mediacms.io/mediacms/custom:ro
- media_files:/home/mediacms.io/mediacms/media_files
- logs:/home/mediacms.io/mediacms/logs
- migrations
db:
image: postgres:17.2-alpine
restart: unless-stopped
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
restart: unless-stopped
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: ["CMD", "redis-cli","ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
postgres_data:
static_files:
media_files:
logs:

View File

@@ -1,367 +0,0 @@
# MediaCMS 7.3 Docker Architecture Migration Guide
## Overview
MediaCMS 7.3 introduces a modernized Docker architecture that removes supervisord and implements Docker best practices with one process per container.
## What Changed
### Old Architecture (pre-7.3)
- Single multi-purpose image with supervisord
- Environment variables (`ENABLE_UWSGI`, `ENABLE_NGINX`, etc.) to control services
- All services bundled in `deploy/docker/` folder
- File mounts required for all deployments
### New Architecture (7.3+)
- **Dedicated images** for each service:
- `mediacms/mediacms:7.3` - Django/uWSGI application
- `mediacms/mediacms-worker:7.3` - Celery workers
- `mediacms/mediacms-worker:7.3-full` - Celery workers with extra codecs
- `mediacms/mediacms-nginx:7.3` - Nginx web server
- **No supervisord** - Native Docker process management
- **Separated services**:
- `migrations` - Runs database migrations on every startup
- `nginx` - Serves static/media files and proxies to Django
- `web` - Django application (uWSGI)
- `celery_short` - Short-running tasks (thumbnails, etc.)
- `celery_long` - Long-running tasks (video encoding)
- `celery_beat` - Task scheduler
- **No ENABLE_* environment variables**
- **Config centralized** in `config/` directory
- **File mounts only for development** (`docker-compose-dev.yaml`)
## Directory Structure
```
config/
├── nginx/
│ ├── nginx.conf # Main nginx config
│ ├── site.conf # Virtual host config
│ └── uwsgi_params # uWSGI parameters
├── nginx-proxy/
│ └── client_max_body_size.conf # For production HTTPS proxy
├── uwsgi/
│ └── uwsgi.ini # uWSGI configuration
└── imagemagick/
└── policy.xml # ImageMagick policy
scripts/
├── entrypoint-web.sh # Web container entrypoint
├── entrypoint-worker.sh # Worker container entrypoint
└── run-migrations.sh # Migration script
Dockerfile.new # Main Dockerfile (base, web, worker, worker-full)
Dockerfile.nginx # Nginx Dockerfile
docker-compose.yaml # Production deployment
docker-compose-cert.yaml # Production with HTTPS
docker-compose-dev.yaml # Development with file mounts
```
## Migration Steps
### For Existing Production Systems
#### Step 1: Backup your data
```bash
# Backup database
docker exec mediacms_db_1 pg_dump -U mediacms mediacms > backup.sql
# Backup media files
cp -r media_files media_files.backup
```
#### Step 2: Update configuration location
```bash
# The client_max_body_size.conf has moved
# No action needed if you haven't customized it
```
#### Step 3: Pull latest images
```bash
docker pull mediacms/mediacms:7.3
docker pull mediacms/mediacms-worker:7.3
docker pull mediacms/mediacms-nginx:7.3
```
#### Step 4: Update docker-compose file
If using **docker-compose.yaml**:
- No changes needed, just use the new version
If using **docker-compose-cert.yaml** (HTTPS):
- Update `VIRTUAL_HOST`, `LETSENCRYPT_HOST`, and `LETSENCRYPT_EMAIL` in the nginx service
- Update the path to client_max_body_size.conf:
```yaml
- ./config/nginx-proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
```
#### Step 5: Restart services
```bash
docker compose down
docker compose up -d
```
### For Development Systems
Development now requires the `-dev` compose file:
```bash
# Old way (no longer works)
docker compose up
# New way (development)
docker compose -f docker-compose-dev.yaml up
```
## Deployment Options
### Standard Deployment (HTTP)
**File**: `docker-compose.yaml`
**Command**:
```bash
docker compose up -d
```
**Features**:
- Self-contained images (no file mounts)
- Nginx serves on port 80
- Separate containers for each service
- Named volumes for persistence
**Architecture**:
```
Client → nginx:80 → web:9000 (uWSGI)
static_files (volume)
media_files (volume)
```
### Production Deployment (HTTPS with Let's Encrypt)
**File**: `docker-compose-cert.yaml`
**Prerequisites**:
1. Domain name pointing to your server
2. Ports 80 and 443 open
**Setup**:
```bash
# 1. Edit docker-compose-cert.yaml
# Update these values in the nginx service:
# VIRTUAL_HOST: 'your-domain.com'
# LETSENCRYPT_HOST: 'your-domain.com'
# LETSENCRYPT_EMAIL: 'your-email@example.com'
# 2. Start services
docker compose -f docker-compose-cert.yaml up -d
# 3. Check logs
docker compose -f docker-compose-cert.yaml logs -f nginx-proxy acme-companion
```
**Features**:
- Automatic HTTPS via Let's Encrypt
- Certificate auto-renewal
- Reverse proxy handles SSL termination
**Architecture**:
```
Client → nginx-proxy:443 (HTTPS) → nginx:80 → web:9000 (uWSGI)
```
### Development Deployment
**File**: `docker-compose-dev.yaml`
**Command**:
```bash
docker compose -f docker-compose-dev.yaml up
```
**Features**:
- Source code mounted for live editing
- Django debug mode enabled
- Django's `runserver` instead of uWSGI
- Frontend hot-reload on port 8088
- No nginx (direct Django access on port 80)
**Ports**:
- `80` - Django API
- `8088` - Frontend dev server
## Configuration
### Environment Variables
All configuration is done via environment variables or `cms/local_settings.py`.
**Key Variables**:
- `FRONTEND_HOST` - Your domain (e.g., `https://mediacms.example.com`)
- `PORTAL_NAME` - Your portal name
- `SECRET_KEY` - Django secret key
- `POSTGRES_*` - Database credentials
- `REDIS_LOCATION` - Redis connection string
- `DEBUG` - Enable debug mode (development only)
**Setting variables**:
Option 1: In docker-compose file:
```yaml
environment:
FRONTEND_HOST: 'https://mediacms.example.com'
PORTAL_NAME: 'My MediaCMS'
```
Option 2: Using .env file (recommended):
```bash
# Create .env file
cat > .env << EOF
FRONTEND_HOST=https://mediacms.example.com
PORTAL_NAME=My MediaCMS
SECRET_KEY=your-secret-key-here
EOF
```
### Customizing Settings
For advanced customization, you can build a custom image:
```dockerfile
# Dockerfile.custom
FROM mediacms/mediacms:7.3
COPY my_local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
```
## Celery Workers
### Standard Workers
By default, `celery_long` uses the standard image:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3
```
### Full Workers (Extra Codecs)
To enable extra codecs for better transcoding (including Whisper for subtitles):
**Edit docker-compose file**:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3-full # Changed from :7.3
```
**Then restart**:
```bash
docker compose up -d celery_long
```
### Scaling Workers
You can scale workers independently:
```bash
# Scale short task workers
docker compose up -d --scale celery_short=3
# Scale long task workers
docker compose up -d --scale celery_long=2
```
## Troubleshooting
### Migrations not running
```bash
# Check migrations container logs
docker compose logs migrations
# Manually run migrations
docker compose run --rm migrations
```
### Static files not loading
```bash
# Ensure migrations completed (it runs collectstatic)
docker compose logs migrations
# Check nginx can access volumes
docker compose exec nginx ls -la /var/www/static
```
### Permission issues
```bash
# Check volume ownership
docker compose exec web ls -la /home/mediacms.io/mediacms/media_files
# If needed, rebuild images
docker compose build --no-cache
```
### Celery workers not processing tasks
```bash
# Check worker logs
docker compose logs celery_short celery_long
# Check Redis connection
docker compose exec redis redis-cli ping
# Restart workers
docker compose restart celery_short celery_long celery_beat
```
## Removed Components
The following are **no longer used** in 7.3:
- ❌ `deploy/docker/supervisord/` - Supervisord configs
- ❌ `deploy/docker/start.sh` - Start script
- ❌ `deploy/docker/entrypoint.sh` - Old entrypoint
- ❌ Environment variables: `ENABLE_UWSGI`, `ENABLE_NGINX`, `ENABLE_CELERY_BEAT`, `ENABLE_CELERY_SHORT`, `ENABLE_CELERY_LONG`, `ENABLE_MIGRATIONS`
**These are still available but moved**:
- ✅ `config/nginx/` - Nginx configs (moved from `deploy/docker/`)
- ✅ `config/uwsgi/` - uWSGI config (moved from `deploy/docker/`)
- ✅ `config/nginx-proxy/` - Reverse proxy config (moved from `deploy/docker/reverse_proxy/`)
## Persistent Volumes
MediaCMS 7.3 uses Docker named volumes for data persistence:
- **`media_files`** - All uploaded media (videos, images, thumbnails, HLS streams)
- Mounted on: migrations, web, nginx, celery_beat, celery_short, celery_long
- Persists across container restarts, updates, and image removals
- **`logs`** - Application and nginx logs
- Mounted on: migrations, web, nginx, celery_beat, celery_short, celery_long
- Nginx logs: `/var/log/mediacms/nginx.access.log`, `/var/log/mediacms/nginx.error.log`
- Django/Celery logs: `/home/mediacms.io/mediacms/logs/`
- Persists across container restarts, updates, and image removals
- **`static_files`** - Django static files (CSS, JS, images)
- Mounted on: migrations, web, nginx
- Regenerated during migrations via `collectstatic`
- **`postgres_data`** - PostgreSQL database
- Mounted on: db
- Persists across container restarts, updates, and image removals
**Important**: Use `docker compose down -v` to remove volumes (⚠️ causes data loss!)
## Benefits of New Architecture
1. **Better resource management** - Scale services independently
2. **Easier debugging** - Clear separation of concerns
3. **Faster restarts** - Restart only affected services
4. **Production-ready** - No file mounts, immutable images
5. **Standard Docker practices** - One process per container
6. **Clearer logs** - Each service has isolated logs, persistent storage
7. **Better health checks** - Per-service monitoring
8. **Data persistence** - media_files and logs survive all container operations
## Support
For issues or questions:
- GitHub Issues: https://github.com/mediacms-io/mediacms/issues
- Documentation: https://docs.mediacms.io

View File

@@ -164,123 +164,53 @@ Database is stored on ../postgres_data/ and media_files on media_files/
## 4. Docker Deployment options
**⚠️ IMPORTANT**: MediaCMS 7.3 introduces a new Docker architecture. If you're upgrading from an earlier version, please see the [Migration Guide](DOCKER_V7.3_MIGRATION.md).
The mediacms image is built to use supervisord as the main process, which manages one or more services required to run mediacms. We can toggle which services are run in a given container by setting the environment variables below to `yes` or `no`:
### Architecture Overview
* ENABLE_UWSGI
* ENABLE_NGINX
* ENABLE_CELERY_BEAT
* ENABLE_CELERY_SHORT
* ENABLE_CELERY_LONG
* ENABLE_MIGRATIONS
MediaCMS 7.3+ uses a modern microservices architecture with dedicated containers:
By default, all these services are enabled, but in order to create a scaleable deployment, some of them can be disabled, splitting the service up into smaller services.
- **nginx** - Web server for static/media files and reverse proxy
- **web** - Django application (uWSGI)
- **celery_short** - Short-running background tasks
- **celery_long** - Long-running tasks (video encoding)
- **celery_beat** - Task scheduler
- **migrations** - Database migrations (runs on startup)
- **db** - PostgreSQL database
- **redis** - Cache and message broker
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
### Key Changes from Previous Versions
To run, update the configs above if necessary, build the image by running `docker compose build`, then run `docker compose run`
-**No supervisord** - Native Docker process management
-**Dedicated images** per service
-**No ENABLE_* environment variables** - Services are separated into individual containers
-**Production images** don't mount source code (immutable)
-**config/** directory for centralized configuration
-**Separate celery workers** for short and long tasks
### Simple Deployment, accessed as http://localhost
### Configuration
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database.
Application settings can be overridden using environment variables in your docker-compose file or by building a custom image with a modified `cms/local_settings.py` file.
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
Key environment variables:
- `FRONTEND_HOST` - Your domain (e.g., `https://mediacms.example.com`)
- `PORTAL_NAME` - Portal name
- `SECRET_KEY` - Django secret key
- `DEBUG` - Enable debug mode (development only)
- Database and Redis connection settings
### Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
Before trying this out make sure the ip points to my_domain.com.
See the [Migration Guide](DOCKER_V7.3_MIGRATION.md) for detailed configuration options
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
### Simple Deployment (HTTP)
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
Use `docker-compose.yaml` for a standard HTTP deployment on port 80:
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
```bash
docker compose up -d
```
Now run `docker compose -f docker-compose-letsencrypt.yaml up`, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
This starts all services (nginx, web, celery workers, database, redis) with the nginx container exposed on port 80. Access at http://localhost or http://your-server-ip.
### Advanced Deployment, accessed as http://localhost:8000
**Features:**
- Production-ready with immutable images
- Named volumes for data persistence
- Separate containers for each service
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
### Production Deployment with HTTPS (Let's Encrypt)
### Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
Use `docker-compose-cert.yaml` for automatic HTTPS with Let's Encrypt:
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
**Prerequisites:**
- Domain name pointing to your server
- Ports 80 and 443 open
### Advanced Deployment, with reverse proxy, accessed as https://localhost
**Setup:**
1. Edit `docker-compose-cert.yaml` and update:
- `VIRTUAL_HOST` - Your domain
- `LETSENCRYPT_HOST` - Your domain
- `LETSENCRYPT_EMAIL` - Your email
2. Run:
```bash
docker compose -f docker-compose-cert.yaml up -d
```
This uses `nginxproxy/nginx-proxy` with `acme-companion` for automatic HTTPS certificate management. Access at https://your-domain.com.
### Development Deployment
Use `docker-compose-dev.yaml` for development with live code reloading:
```bash
docker compose -f docker-compose-dev.yaml up
```
**Features:**
- Source code mounted for live editing
- Django debug mode enabled
- Frontend dev server on port 8088
- Direct Django access (no nginx) on port 80
### Scaling Workers
Scale celery workers independently based on load:
```bash
# Scale short task workers to 3 instances
docker compose up -d --scale celery_short=3
# Scale long task workers to 2 instances
docker compose up -d --scale celery_long=2
```
### Using Extra Codecs (Full Image)
For advanced transcoding features (including Whisper for automatic subtitles), use the full worker image:
Edit your docker-compose file:
```yaml
celery_long:
image: mediacms/mediacms-worker:7.3-full # Changed from :7.3
```
Then restart:
```bash
docker compose up -d celery_long
```
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
### A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
The architecture below provides a conceptual design for deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple web instances and celery workers. For large deployments, managed postgres, redis and storage may be adopted.
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
![MediaCMS](images/architecture.png)
@@ -288,36 +218,24 @@ The architecture below provides a conceptual design for deployments based on kub
## 5. Configuration
Several options are available on `cms/settings.py`, most of the things that are allowed or should be disallowed are described there.
It is advisable to override any of them by adding it to `local_settings.py`.
It is advisable to override any of them by adding it to `local_settings.py` .
**Single server installation:** edit `cms/local_settings.py`, make changes and restart MediaCMS:
In case of a the single server installation, add to `cms/local_settings.py` .
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
Any change needs restart of MediaCMS in order to take effect.
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
```bash
systemctl restart mediacms celery_beat celery_short celery_long
#systemctl restart mediacms
```
**Docker installation:** Configuration can be done in two ways:
1. **Environment variables** (recommended for simple changes):
Add to your docker-compose file:
```yaml
environment:
FRONTEND_HOST: 'https://mediacms.example.com'
PORTAL_NAME: 'My MediaCMS'
```
2. **Custom image with local_settings.py** (for complex changes):
- Create a custom Dockerfile:
```dockerfile
FROM mediacms/mediacms:7.3
COPY my_custom_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
```
- Build and use your custom image
After changes, restart the affected containers:
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
```bash
docker compose restart web celery_short celery_long celery_beat
#docker compose restart web celery_worker celery_beat
```
### 5.1 Change portal logo

View File

@@ -23,7 +23,7 @@ and will start all services required for MediaCMS, as Celery/Redis for asynchron
For Django, the changes from the image produced by docker-compose.yaml are these:
* Django runs in debug mode, with `python manage.py runserver`
* uwsgi and nginx are not run
* gunicorn and nginx are not run
* Django runs in Debug mode, with Debug Toolbar
* Static files (js/css) are loaded from static/ folder
* corsheaders is installed and configured to allow all origins

View File

@@ -46,7 +46,7 @@ Before beginning, ensure the following:
## Step 1: Configure MediaCMS for SAML
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./config/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./deploy/docker/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
```python
USE_RBAC = True
@@ -292,7 +292,7 @@ Another issue you might encounter is an **infinite redirect loop**. This can hap
https://<MyDomainName>/accounts/saml/mediacms_entraid/login/
```
* Add the following line to `./config/local_settings.py`:
* Add the following line to `./deploy/docker/local_settings.py`:
```python
LOGIN_URL = "/accounts/saml/mediacms_entraid/login/"

View File

@@ -65,6 +65,7 @@ class CategoryAdminForm(forms.ModelForm):
class Meta:
model = Category
# LTI fields will be shown as read-only when USE_LTI is enabled
fields = '__all__'
def clean(self):
@@ -135,7 +136,7 @@ class CategoryAdmin(admin.ModelAdmin):
list_display = ["title", "user", "add_date", "media_count"]
list_filter = []
ordering = ("-add_date",)
readonly_fields = ("user", "media_count")
readonly_fields = ("user", "media_count", "lti_platform", "lti_context_id")
change_form_template = 'admin/files/category/change_form.html'
def get_list_filter(self, request):
@@ -167,6 +168,14 @@ class CategoryAdmin(admin.ModelAdmin):
),
]
additional_fieldsets = []
if getattr(settings, 'USE_LTI', False):
lti_fieldset = [
('LTI Integration', {'fields': ['lti_platform', 'lti_context_id'], 'classes': ['tab'], 'description': 'LTI/LMS integration settings (automatically managed by LTI provisioning)'}),
]
additional_fieldsets.extend(lti_fieldset)
if getattr(settings, 'USE_RBAC', False):
rbac_fieldset = [
('RBAC Settings', {'fields': ['is_rbac_category'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
@@ -177,9 +186,9 @@ class CategoryAdmin(admin.ModelAdmin):
('RBAC Settings', {'fields': ['is_rbac_category', 'identity_provider'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
]
return basic_fieldset + rbac_fieldset
else:
return basic_fieldset
additional_fieldsets.extend(rbac_fieldset)
return basic_fieldset + additional_fieldsets
class TagAdmin(admin.ModelAdmin):

View File

@@ -58,9 +58,16 @@ def stuff(request):
ret["USE_RBAC"] = settings.USE_RBAC
ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS
ret["INCLUDE_LISTING_NUMBERS"] = settings.INCLUDE_LISTING_NUMBERS
ret["ALLOW_MEDIA_REPLACEMENT"] = getattr(settings, 'ALLOW_MEDIA_REPLACEMENT', False)
ret["VERSION"] = VERSION
if request.user.is_superuser:
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
if getattr(settings, 'USE_LTI', False):
lti_session = request.session.get('lti_session')
if lti_session and request.user.is_authenticated:
ret['lti_session'] = lti_session
return ret

View File

@@ -6,6 +6,7 @@ from django.conf import settings
from .methods import get_next_state, is_mediacms_editor
from .models import MEDIA_STATES, Category, Media, Subtitle
from .widgets import CategoryModalWidget
class CustomField(Field):
@@ -121,13 +122,18 @@ class MediaPublishForm(forms.ModelForm):
fields = ("category", "state", "featured", "reported_times", "is_reviewed", "allow_download")
widgets = {
"category": MultipleSelect(),
"category": CategoryModalWidget(),
}
def __init__(self, user, *args, **kwargs):
self.user = user
super(MediaPublishForm, self).__init__(*args, **kwargs)
self.has_custom_permissions = self.instance.permissions.exists() if self.instance.pk else False
self.has_rbac_categories = self.instance.category.filter(is_rbac_category=True).exists() if self.instance.pk else False
self.is_shared = self.has_custom_permissions or self.has_rbac_categories
self.actual_state = self.instance.state if self.instance.pk else None
if not is_mediacms_editor(user):
for field in ["featured", "reported_times", "is_reviewed"]:
self.fields[field].disabled = True
@@ -140,6 +146,13 @@ class MediaPublishForm(forms.ModelForm):
valid_states.append(self.instance.state)
self.fields["state"].choices = [(state, dict(MEDIA_STATES).get(state, state)) for state in valid_states]
if self.is_shared:
current_choices = list(self.fields["state"].choices)
current_choices.insert(0, ("shared", "Shared"))
self.fields["state"].choices = current_choices
self.fields["state"].initial = "shared"
self.initial["state"] = "shared"
if getattr(settings, 'USE_RBAC', False) and 'category' in self.fields:
if is_mediacms_editor(user):
pass
@@ -178,7 +191,35 @@ class MediaPublishForm(forms.ModelForm):
state = cleaned_data.get("state")
categories = cleaned_data.get("category")
if state in ['private', 'unlisted']:
if self.is_shared and state != "shared":
self.fields['confirm_state'].widget = forms.CheckboxInput()
state_index = None
for i, layout_item in enumerate(self.helper.layout):
if isinstance(layout_item, CustomField) and layout_item.fields[0] == 'state':
state_index = i
break
if state_index is not None:
layout_items = list(self.helper.layout)
layout_items.insert(state_index + 1, CustomField('confirm_state'))
self.helper.layout = Layout(*layout_items)
if not cleaned_data.get('confirm_state'):
if state == 'private':
error_parts = []
if self.has_rbac_categories:
rbac_cat_titles = self.instance.category.filter(is_rbac_category=True).values_list('title', flat=True)
error_parts.append(f"shared with users that have access to categories: {', '.join(rbac_cat_titles)}")
if self.has_custom_permissions:
error_parts.append("shared by me with other users (visible in 'Shared by me' page)")
error_message = f"I understand that changing to Private will remove all sharing. Currently this media is {' and '.join(error_parts)}. All this sharing will be removed."
self.add_error('confirm_state', error_message)
else:
error_message = f"I understand that changing to {state.title()} will maintain existing sharing settings."
self.add_error('confirm_state', error_message)
elif state in ['private', 'unlisted']:
custom_permissions = self.instance.permissions.exists()
rbac_categories = categories.filter(is_rbac_category=True).values_list('title', flat=True)
if rbac_categories or custom_permissions:
@@ -189,7 +230,7 @@ class MediaPublishForm(forms.ModelForm):
state_index = i
break
if state_index:
if state_index is not None:
layout_items = list(self.helper.layout)
layout_items.insert(state_index + 1, CustomField('confirm_state'))
self.helper.layout = Layout(*layout_items)
@@ -202,11 +243,24 @@ class MediaPublishForm(forms.ModelForm):
error_message = f"I understand that although media state is {state}, the media is also shared by me with other users, that I can see in the 'Shared by me' page"
self.add_error('confirm_state', error_message)
# Convert "shared" state to actual underlying state for saving. we dont keep shared state in DB
if state == "shared":
cleaned_data["state"] = self.actual_state
return cleaned_data
def save(self, *args, **kwargs):
data = self.cleaned_data
state = data.get("state")
# If transitioning from shared to private, remove all sharing
if self.is_shared and state == 'private' and data.get('confirm_state'):
# Remove all custom permissions
self.instance.permissions.all().delete()
# Remove RBAC categories
rbac_cats = self.instance.category.filter(is_rbac_category=True)
self.instance.category.remove(*rbac_cats)
if state != self.initial["state"]:
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
@@ -333,3 +387,35 @@ class ContactForm(forms.Form):
if user.is_authenticated:
self.fields.pop("name")
self.fields.pop("from_email")
class ReplaceMediaForm(forms.Form):
new_media_file = forms.FileField(
required=True,
label="New Media File",
help_text="Select a new file to replace the current media",
)
def __init__(self, media_instance, *args, **kwargs):
self.media_instance = media_instance
super(ReplaceMediaForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'post-form'
self.helper.form_method = 'post'
self.helper.form_enctype = "multipart/form-data"
self.helper.form_show_errors = False
self.helper.layout = Layout(
CustomField('new_media_file'),
)
self.helper.layout.append(FormActions(Submit('submit', 'Replace Media', css_class='primaryAction')))
def clean_new_media_file(self):
file = self.cleaned_data.get("new_media_file", False)
if file:
if file.size > settings.UPLOAD_MAX_SIZE:
max_size_mb = settings.UPLOAD_MAX_SIZE / (1024 * 1024)
raise forms.ValidationError(f"File too large. Maximum size: {max_size_mb:.0f}MB")
return file

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "إزالة من القائمة",
"Remove tag": "إزالة العلامة",
"Remove user": "إزالة المستخدم",
"Replace": "",
"SAVE": "حفظ",
"SEARCH": "بحث",
"SHARE": "مشاركة",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"Replace": "",
"SAVE": "সংরক্ষণ করুন",
"SEARCH": "অনুসন্ধান",
"SHARE": "শেয়ার করুন",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Fjern fra liste",
"Remove tag": "Fjern tag",
"Remove user": "Fjern bruger",
"Replace": "",
"SAVE": "GEM",
"SEARCH": "SØG",
"SHARE": "DEL",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Aus Liste entfernen",
"Remove tag": "Tag entfernen",
"Remove user": "Benutzer entfernen",
"Replace": "",
"SAVE": "SPEICHERN",
"SEARCH": "SUCHE",
"SHARE": "TEILEN",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Αφαίρεση από λίστα",
"Remove tag": "Αφαίρεση ετικέτας",
"Remove user": "Αφαίρεση χρήστη",
"Replace": "",
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
"SHARE": "ΚΟΙΝΟΠΟΙΗΣΗ",

View File

@@ -165,6 +165,7 @@ translation_strings = {
"Recommended": "",
"Record Screen": "",
"Register": "",
"Replace": "",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Eliminar de la lista",
"Remove tag": "Eliminar etiqueta",
"Remove user": "Eliminar usuario",
"Replace": "",
"SAVE": "GUARDAR",
"SEARCH": "BUSCAR",
"SHARE": "COMPARTIR",

View File

@@ -163,6 +163,7 @@ translation_strings = {
"Remove from list": "Supprimer de la liste",
"Remove tag": "Supprimer le tag",
"Remove user": "Supprimer l'utilisateur",
"Replace": "",
"SAVE": "ENREGISTRER",
"SEARCH": "RECHERCHER",
"SHARE": "PARTAGER",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"Replace": "",
"SAVE": "שמור",
"SEARCH": "חפש",
"SHARE": "שתף",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "सूची से हटाएं",
"Remove tag": "टैग हटाएं",
"Remove user": "उपयोगकर्ता हटाएं",
"Replace": "",
"SAVE": "सहेजें",
"SEARCH": "खोजें",
"SHARE": "साझा करें",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Hapus dari daftar",
"Remove tag": "Hapus tag",
"Remove user": "Hapus pengguna",
"Replace": "",
"SAVE": "SIMPAN",
"SEARCH": "CARI",
"SHARE": "BAGIKAN",

View File

@@ -163,6 +163,7 @@ translation_strings = {
"Remove from list": "Rimuovi dalla lista",
"Remove tag": "Rimuovi tag",
"Remove user": "Rimuovi utente",
"Replace": "",
"SAVE": "SALVA",
"SEARCH": "CERCA",
"SHARE": "CONDIVIDI",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "リストから削除",
"Remove tag": "タグを削除",
"Remove user": "ユーザーを削除",
"Replace": "",
"SAVE": "保存",
"SEARCH": "検索",
"SHARE": "共有",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "목록에서 제거",
"Remove tag": "태그 제거",
"Remove user": "사용자 제거",
"Replace": "",
"SAVE": "저장",
"SEARCH": "검색",
"SHARE": "공유",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Verwijderen uit lijst",
"Remove tag": "Tag verwijderen",
"Remove user": "Gebruiker verwijderen",
"Replace": "",
"SAVE": "OPSLAAN",
"SEARCH": "ZOEKEN",
"SHARE": "DELEN",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Remover da lista",
"Remove tag": "Remover tag",
"Remove user": "Remover usuário",
"Replace": "",
"SAVE": "SALVAR",
"SEARCH": "PESQUISAR",
"SHARE": "COMPARTILHAR",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Удалить из списка",
"Remove tag": "Удалить тег",
"Remove user": "Удалить пользователя",
"Replace": "",
"SAVE": "СОХРАНИТЬ",
"SEARCH": "ПОИСК",
"SHARE": "ПОДЕЛИТЬСЯ",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Odstrani s seznama",
"Remove tag": "Odstrani oznako",
"Remove user": "Odstrani uporabnika",
"Replace": "",
"SAVE": "SHRANI",
"SEARCH": "ISKANJE",
"SHARE": "DELI",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "Listeden kaldır",
"Remove tag": "Etiketi kaldır",
"Remove user": "Kullanıcıyı kaldır",
"Replace": "",
"SAVE": "KAYDET",
"SEARCH": "ARA",
"SHARE": "PAYLAŞ",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "فہرست سے ہٹائیں",
"Remove tag": "ٹیگ ہٹائیں",
"Remove user": "صارف ہٹائیں",
"Replace": "",
"SAVE": "محفوظ کریں",
"SEARCH": "تلاش کریں",
"SHARE": "شیئر کریں",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"Replace": "",
"SAVE": "保存",
"SEARCH": "搜索",
"SHARE": "分享",

View File

@@ -162,6 +162,7 @@ translation_strings = {
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"Replace": "",
"SAVE": "儲存",
"SEARCH": "搜尋",
"SHARE": "分享",

View File

@@ -965,3 +965,13 @@ def get_alphanumeric_only(string):
"""
string = "".join([char for char in string if char.isalnum()])
return string.lower()
def get_alphanumeric_and_spaces(string):
"""Returns a query that contains only alphanumeric characters and spaces
This include characters other than the English alphabet too
"""
string = "".join([char for char in string if char.isalnum() or char.isspace()])
# Replace multiple spaces with single space and strip
string = " ".join(string.split())
return string

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.6 on 2025-12-16 14:05
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('files', '0013_page_tinymcemedia'),
]
operations = [
migrations.AlterModelOptions(
name='subtitle',
options={'ordering': ['language__title'], 'verbose_name': 'Caption', 'verbose_name_plural': 'Captions'},
),
migrations.AlterModelOptions(
name='transcriptionrequest',
options={'verbose_name': 'Caption Request', 'verbose_name_plural': 'Caption Requests'},
),
migrations.AlterModelOptions(
name='videotrimrequest',
options={'verbose_name': 'Trim Request', 'verbose_name_plural': 'Trim Requests'},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.6 on 2025-12-29 16:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0014_alter_subtitle_options_and_more'),
]
operations = [
migrations.AddField(
model_name='category',
name='is_lms_course',
field=models.BooleanField(db_index=True, default=False, help_text='Whether this category represents an LMS course'),
),
migrations.AddField(
model_name='category',
name='lti_context_id',
field=models.CharField(blank=True, db_index=True, help_text='LTI context ID from platform', max_length=255),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.6 on 2025-12-29 16:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0015_category_is_lms_course_category_lti_context_id'),
('lti', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='category',
name='lti_platform',
field=models.ForeignKey(
blank=True, help_text='LTI Platform if this is an LTI course', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='categories', to='lti.ltiplatform'
),
),
]

View File

@@ -47,6 +47,13 @@ class Category(models.Model):
verbose_name='IDP Config Name',
)
# LTI/LMS integration fields
is_lms_course = models.BooleanField(default=False, db_index=True, help_text='Whether this category represents an LMS course')
lti_platform = models.ForeignKey('lti.LTIPlatform', blank=True, null=True, on_delete=models.SET_NULL, related_name='categories', help_text='LTI Platform if this is an LTI course')
lti_context_id = models.CharField(max_length=255, blank=True, db_index=True, help_text='LTI context ID from platform')
def __str__(self):
return self.title
@@ -137,7 +144,7 @@ class Tag(models.Model):
return True
def save(self, *args, **kwargs):
self.title = helpers.get_alphanumeric_only(self.title)
self.title = helpers.get_alphanumeric_and_spaces(self.title)
self.title = self.title[:100]
super(Tag, self).save(*args, **kwargs)

View File

@@ -270,7 +270,9 @@ class Media(models.Model):
if self.media_file != self.__original_media_file:
# set this otherwise gets to infinite loop
self.__original_media_file = self.media_file
self.media_init()
from .. import tasks
tasks.media_init.apply_async(args=[self.friendly_token], countdown=5)
# for video files, if user specified a different time
# to automatically grub thumbnail
@@ -329,10 +331,17 @@ class Media(models.Model):
if to_transcribe:
TranscriptionRequest.objects.create(media=self, translate_to_english=False)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=False)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, False],
countdown=10,
)
if to_transcribe_and_translate:
TranscriptionRequest.objects.create(media=self, translate_to_english=True)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=True)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, True],
countdown=10,
)
def update_search_vector(self):
"""
@@ -343,20 +352,11 @@ class Media(models.Model):
# first get anything interesting out of the media
# that needs to be search able
a_tags = b_tags = ""
a_tags = ""
if self.id:
a_tags = " ".join([tag.title for tag in self.tags.all()])
b_tags = " ".join([tag.title.replace("-", " ") for tag in self.tags.all()])
items = [
self.title,
self.user.username,
self.user.email,
self.user.name,
self.description,
a_tags,
b_tags,
]
items = [self.friendly_token, self.title, self.user.username, self.user.email, self.user.name, self.description, a_tags]
for subtitle in self.subtitles.all():
items.append(subtitle.subtitle_text)
@@ -410,6 +410,11 @@ class Media(models.Model):
self.media_type = "image"
elif kind == "pdf":
self.media_type = "pdf"
elif kind == "audio":
self.media_type = "audio"
elif kind == "video":
self.media_type = "video"
if self.media_type in ["image", "pdf"]:
self.encoding_status = "success"
else:

View File

@@ -101,10 +101,17 @@ class MediaSerializer(serializers.ModelSerializer):
class SingleMediaSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source="user.username")
url = serializers.SerializerMethodField()
is_shared = serializers.SerializerMethodField()
def get_url(self, obj):
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
def get_is_shared(self, obj):
"""Check if media has custom permissions or RBAC categories"""
custom_permissions = obj.permissions.exists()
rbac_categories = obj.category.filter(is_rbac_category=True).exists()
return custom_permissions or rbac_categories
class Meta:
model = Media
read_only_fields = (
@@ -133,6 +140,7 @@ class SingleMediaSerializer(serializers.ModelSerializer):
"edit_date",
"media_type",
"state",
"is_shared",
"duration",
"thumbnail_url",
"poster_url",

View File

@@ -625,6 +625,18 @@ def create_hls(friendly_token):
return True
@task(name="media_init", queue="short_tasks")
def media_init(friendly_token):
try:
media = Media.objects.get(friendly_token=friendly_token)
except: # noqa
logger.info("failed to get media with friendly_token %s" % friendly_token)
return False
media.media_init()
return True
@task(name="check_running_states", queue="short_tasks")
def check_running_states():
# Experimental - unused

View File

@@ -20,6 +20,7 @@ urlpatterns = [
re_path(r"^contact$", views.contact, name="contact"),
re_path(r"^publish", views.publish_media, name="publish_media"),
re_path(r"^edit_chapters", views.edit_chapters, name="edit_chapters"),
re_path(r"^replace_media", views.replace_media, name="replace_media"),
re_path(r"^edit_video", views.edit_video, name="edit_video"),
re_path(r"^edit", views.edit_media, name="edit_media"),
re_path(r"^embed", views.embed_media, name="get_embed"),
@@ -79,6 +80,7 @@ urlpatterns = [
views.trim_video,
),
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
re_path(r"^api/v1/categories/contributor$", views.CategoryListContributor.as_view()),
re_path(r"^api/v1/tags$", views.TagList.as_view()),
re_path(r"^api/v1/comments$", views.CommentList.as_view()),
re_path(
@@ -110,7 +112,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(r"^(?P<slug>[\w.-]*)$", views.get_page, name="get_page"),
re_path(r"^(?P<slug>[\w.-]*)$", views.get_page, name="get_page"), # noqa: W605
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Some files were not shown because too many files have changed in this diff Show More