mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-10 06:57:25 -04:00
Compare commits
264 Commits
feat-docke
...
ltimoodle1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c8a464845 | ||
|
|
e0974433a4 | ||
|
|
e130c7403c | ||
|
|
182fb9599a | ||
|
|
eadaffb27c | ||
|
|
27bba37a55 | ||
|
|
90b480b4f8 | ||
|
|
583a8c4512 | ||
|
|
b806ffd7f5 | ||
|
|
a6f7ce71c6 | ||
|
|
ab5f86ee4d | ||
|
|
216365fdb6 | ||
|
|
66b19b99f2 | ||
|
|
692b6b5f09 | ||
|
|
814a7d3cee | ||
|
|
31571fc4cb | ||
|
|
edd40d45b4 | ||
|
|
45774fbc8c | ||
|
|
5ac0515d05 | ||
|
|
492e3366c0 | ||
|
|
3f1ca5d02f | ||
|
|
8bc09b69f7 | ||
|
|
7b8e4975ab | ||
|
|
df8701a515 | ||
|
|
2c1103e906 | ||
|
|
1c50dcdff8 | ||
|
|
b8fee7d06a | ||
|
|
1df6e0c10d | ||
|
|
dc328cd33c | ||
|
|
b39789c2c4 | ||
|
|
86fa084391 | ||
|
|
533a30b6cb | ||
|
|
cdbed4bc1d | ||
|
|
58d336478c | ||
|
|
d28fc114f3 | ||
|
|
0056e1ed8f | ||
|
|
2423e9517e | ||
|
|
699f4bd09d | ||
|
|
6071921908 | ||
|
|
33da2242b4 | ||
|
|
21ddd04165 | ||
|
|
96755af3b2 | ||
|
|
17526aeecd | ||
|
|
23c5c544ce | ||
|
|
ae9d614e84 | ||
|
|
e044187973 | ||
|
|
b5a76c53e1 | ||
|
|
86348f1747 | ||
|
|
c6be65edd6 | ||
|
|
4656ba1176 | ||
|
|
7e7cd10549 | ||
|
|
d5919e10af | ||
|
|
f16b61644a | ||
|
|
c486574eb0 | ||
|
|
364d22dd8e | ||
|
|
209cede39c | ||
|
|
9b8f63881a | ||
|
|
82fb2665e6 | ||
|
|
abd22426d7 | ||
|
|
86f4484c36 | ||
|
|
f226c2fe1e | ||
|
|
98111234f8 | ||
|
|
467e273ff5 | ||
|
|
166fb5c00b | ||
|
|
d3f722611d | ||
|
|
fae5634729 | ||
|
|
5cdcef2205 | ||
|
|
f6e44f8343 | ||
|
|
1b3a58bc60 | ||
|
|
befc1f0efa | ||
|
|
6075514ffa | ||
|
|
9c7cc293ce | ||
|
|
f7c675596f | ||
|
|
36d815c0cf | ||
|
|
8f28b00a63 | ||
|
|
74952f68d7 | ||
|
|
7950a4655a | ||
|
|
b76282f9e4 | ||
|
|
b405a04e34 | ||
|
|
76a27ae256 | ||
|
|
156e0bddd6 | ||
|
|
637e9eabea | ||
|
|
e12f361935 | ||
|
|
c4d569e7b0 | ||
|
|
24d15d2639 | ||
|
|
1c0805748d | ||
|
|
8e7f1ef15d | ||
|
|
d00b4ed3c5 | ||
|
|
7126cf1cbd | ||
|
|
33135f81e9 | ||
|
|
cfd305d563 | ||
|
|
d4bad33592 | ||
|
|
7c0ea60d0e | ||
|
|
c6726d1b13 | ||
|
|
7c04cc30fb | ||
|
|
f28ce5990b | ||
|
|
27828d798e | ||
|
|
ba53467033 | ||
|
|
a77761ec35 | ||
|
|
4d07ae64c6 | ||
|
|
223e87073f | ||
|
|
1a96ac0703 | ||
|
|
9e0290ae49 | ||
|
|
610a22935f | ||
|
|
81e3325687 | ||
|
|
16f7b010ab | ||
|
|
68cb21ff8a | ||
|
|
2ddc3b24d7 | ||
|
|
63e96b327e | ||
|
|
48537515cb | ||
|
|
e6db138d11 | ||
|
|
2f2d32f0db | ||
|
|
f4d3439246 | ||
|
|
7fe9891942 | ||
|
|
9eb8a1ad62 | ||
|
|
23ee0dc7cc | ||
|
|
e5be39f392 | ||
|
|
f0c084fa53 | ||
|
|
571bfcc4ce | ||
|
|
c04380af47 | ||
|
|
97741f780e | ||
|
|
78cce0eb10 | ||
|
|
472b3029c4 | ||
|
|
343f1e7009 | ||
|
|
8c78b67b0c | ||
|
|
29fc7fb861 | ||
|
|
b03a33d93e | ||
|
|
64472be406 | ||
|
|
cc0f4d4645 | ||
|
|
095e4d2cb4 | ||
|
|
5c8978453e | ||
|
|
83189076e4 | ||
|
|
ca6dbf3740 | ||
|
|
8646bd70dc | ||
|
|
1f493c8e15 | ||
|
|
e11cb7ea6e | ||
|
|
3131e76ef7 | ||
|
|
809cdccc42 | ||
|
|
ed36240f45 | ||
|
|
77bafff6f6 | ||
|
|
f6252f4f77 | ||
|
|
764580287f | ||
|
|
ce6c9a0a3c | ||
|
|
1ced023a07 | ||
|
|
981fec296c | ||
|
|
40cd7916e7 | ||
|
|
bcef59c3a9 | ||
|
|
e93c8225c4 | ||
|
|
5c3c33ca84 | ||
|
|
7a954e7a3d | ||
|
|
8610df0c2b | ||
|
|
8ab9030d14 | ||
|
|
15c8dec041 | ||
|
|
9af4686bd4 | ||
|
|
bcc8a0858c | ||
|
|
549b672d48 | ||
|
|
abe950f1da | ||
|
|
5fecda02d6 | ||
|
|
3c6f8c102c | ||
|
|
2d28520cd4 | ||
|
|
4bd56da2d8 | ||
|
|
fdfa857794 | ||
|
|
2c1f27c0be | ||
|
|
2f0bbd2533 | ||
|
|
1c15880ae3 | ||
|
|
54336f6c31 | ||
|
|
37e21f7ebf | ||
|
|
3deee80dd0 | ||
|
|
2e57164831 | ||
|
|
de0c16729b | ||
|
|
2c0bba1427 | ||
|
|
54a8e41f6d | ||
|
|
78fb19b464 | ||
|
|
8e5e7991b7 | ||
|
|
5cf435eca0 | ||
|
|
5026ce73da | ||
|
|
8b2ebe2415 | ||
|
|
8df320e134 | ||
|
|
8c8f737460 | ||
|
|
995faedb08 | ||
|
|
bde300b4bd | ||
|
|
fd5c0a2908 | ||
|
|
9c145da2e2 | ||
|
|
e9e5d44c3e | ||
|
|
a624c2e5b8 | ||
|
|
748d3b39ba | ||
|
|
ddc6bf9e67 | ||
|
|
aa7dbfe534 | ||
|
|
5cc72357c6 | ||
|
|
01b061a47b | ||
|
|
fbc78e7944 | ||
|
|
9e7a8afdda | ||
|
|
5572a67019 | ||
|
|
610590972f | ||
|
|
bdf7d3c2d0 | ||
|
|
a47bf5a3f8 | ||
|
|
38caea3c7c | ||
|
|
30491bf420 | ||
|
|
d0ebe19c2a | ||
|
|
59be9f16c0 | ||
|
|
a2d898c54e | ||
|
|
9733d53c0b | ||
|
|
70e2c67f3d | ||
|
|
77721d9c0e | ||
|
|
06bc64b2c4 | ||
|
|
b9899476b9 | ||
|
|
107750406e | ||
|
|
ae4ae5a07e | ||
|
|
f346a5604c | ||
|
|
56026a1a96 | ||
|
|
a88413ce14 | ||
|
|
9dab3ad858 | ||
|
|
dfe7e8fab0 | ||
|
|
1181d16ab9 | ||
|
|
d032ee3baa | ||
|
|
93f66d206b | ||
|
|
0585513439 | ||
|
|
9667e6b0ad | ||
|
|
f56948a4a2 | ||
|
|
8b3e76b554 | ||
|
|
dc417de628 | ||
|
|
35cd56c85c | ||
|
|
f0b2451815 | ||
|
|
7696251394 | ||
|
|
b95725660b | ||
|
|
d6bf98b30e | ||
|
|
3baa8ef7d7 | ||
|
|
45246eac4f | ||
|
|
9685c1b5d4 | ||
|
|
20a1da22bb | ||
|
|
f9a94321ad | ||
|
|
f85299a600 | ||
|
|
29ab2a715b | ||
|
|
43ce685f08 | ||
|
|
8c682a76af | ||
|
|
ec6b6daa81 | ||
|
|
cf90169240 | ||
|
|
fb3f377e27 | ||
|
|
f5f9a7beac | ||
|
|
726a5b74a1 | ||
|
|
40c31f295a | ||
|
|
1d77293afc | ||
|
|
5c702387ca | ||
|
|
0001f370a9 | ||
|
|
af71d4c906 | ||
|
|
eb7503125d | ||
|
|
f897d0ba2b | ||
|
|
545cca154e | ||
|
|
ef4ff9cb1d | ||
|
|
3a40fc6d88 | ||
|
|
f67d2a4d78 | ||
|
|
295578dae2 | ||
|
|
ed5cfa1a84 | ||
|
|
2fe48d8522 | ||
|
|
90331f3b4a | ||
|
|
c57f528ab1 | ||
|
|
fa67ffffb4 | ||
|
|
872571350f | ||
|
|
665971856b | ||
|
|
d9b1d6cab1 | ||
|
|
aeef8284bf | ||
|
|
a90fcbf8dd | ||
|
|
1b3cdfd302 | ||
|
|
cd7dd4f72c |
@@ -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
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/**
|
||||
|
||||
88
.github/workflows/docker-build-push.yml
vendored
88
.github/workflows/docker-build-push.yml
vendored
@@ -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 }}
|
||||
|
||||
42
.github/workflows/frontend-build-and-test.yml
vendored
Normal file
42
.github/workflows/frontend-build-and-test.yml
vendored
Normal 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
|
||||
22
.github/workflows/semantic-pull-request.yaml
vendored
Normal file
22
.github/workflows/semantic-pull-request.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: "Lint PR"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
environment: dev
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
47
.github/workflows/semantic-release.yaml
vendored
Normal file
47
.github/workflows/semantic-release.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Semantic Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
semantic-release:
|
||||
runs-on: ubuntu-latest
|
||||
environment: dev
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.GA_DEPLOY_KEY }}
|
||||
|
||||
# use SSH url to ensure git commit using a deploy key bypasses the main
|
||||
# branch protection rule
|
||||
- name: Configure Git for SSH Push
|
||||
run: git remote set-url origin "git@github.com:${{ github.repository }}.git"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm clean-install
|
||||
|
||||
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
||||
run: npm audit signatures
|
||||
|
||||
- name: Run Semantic Release
|
||||
run: npx semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/templates/cms/*
|
||||
/templates/*.html
|
||||
*.scss
|
||||
*.scss
|
||||
/frontend/
|
||||
100
.releaserc.json
Normal file
100
.releaserc.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"branches": [
|
||||
"main"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{
|
||||
"type": "feat",
|
||||
"section": "Features"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"section": "Bug Fixes"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"type": "docs",
|
||||
"section": "Documentation"
|
||||
},
|
||||
{
|
||||
"type": "style",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "Refactors"
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "Performance"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"type": "depr",
|
||||
"section": "Deprecations"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"semantic-release-replace-plugin",
|
||||
{
|
||||
"replacements": [
|
||||
{
|
||||
"files": [
|
||||
"package.json"
|
||||
],
|
||||
"from": "\"version\": \".*\"",
|
||||
"to": "\"version\": \"${nextRelease.version}\"",
|
||||
"results": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"hasChanged": true,
|
||||
"numMatches": 1,
|
||||
"numReplacements": 1
|
||||
}
|
||||
],
|
||||
"countMatches": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
"changelogFile": "CHANGELOG.md",
|
||||
"changelogTitle": "# Changelog"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"package.json",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## [7.5.0](https://github.com/mediacms-io/mediacms/compare/v7.4.0...v7.5.0) (2026-02-06)
|
||||
|
||||
### Features
|
||||
|
||||
* bump version ([36d815c](https://github.com/mediacms-io/mediacms/commit/36d815c0cfbe21d3136541d410d545742b9ebecd))
|
||||
|
||||
## [7.4.0](https://github.com/mediacms-io/mediacms/compare/v7.3.0...v7.4.0) (2026-02-06)
|
||||
|
||||
### Features
|
||||
|
||||
* Add video player context menu with share/embed options ([#1472](https://github.com/mediacms-io/mediacms/issues/1472)) ([74952f6](https://github.com/mediacms-io/mediacms/commit/74952f68d79bc67617edb38eac62d2f5e7457565))
|
||||
|
||||
## [7.3.0](https://github.com/mediacms-io/mediacms/compare/v7.2.0...v7.3.0) (2026-02-06)
|
||||
|
||||
### Features
|
||||
|
||||
* add package json for semantic release ([b405a04](https://github.com/mediacms-io/mediacms/commit/b405a04e346ca81b7d3f4e099eb984e7785cdd0f))
|
||||
* add semantic release github actions ([76a27ae](https://github.com/mediacms-io/mediacms/commit/76a27ae25609178c1bd47c947b9f1a082c791d61))
|
||||
* frontend unit tests ([1c15880](https://github.com/mediacms-io/mediacms/commit/1c15880ae3ef1ce77f53d5b473dfc0cc448b4977))
|
||||
* Implement persistent "Embed Mode" to hide UI shell via Session Storage ([#1484](https://github.com/mediacms-io/mediacms/issues/1484)) ([223e870](https://github.com/mediacms-io/mediacms/commit/223e87073f7d5e44130c9976854cac670db0ae66))
|
||||
* Improve Visual Distinction Between Trim and Chapters Editors ([#1445](https://github.com/mediacms-io/mediacms/issues/1445)) ([d9b1d6c](https://github.com/mediacms-io/mediacms/commit/d9b1d6cab1d2bdfc16f799a0a27b64313e2e0d22))
|
||||
* semantic release ([b76282f](https://github.com/mediacms-io/mediacms/commit/b76282f9e465a39c2da5e9a22184d1db23de3f56))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add delay to task creation ([1b3cdfd](https://github.com/mediacms-io/mediacms/commit/1b3cdfd302abc5e69ebe01ca52b5091f3b24c0b2))
|
||||
* Add regex denoter and improve celerybeat gitignore ([#1446](https://github.com/mediacms-io/mediacms/issues/1446)) ([90331f3](https://github.com/mediacms-io/mediacms/commit/90331f3b4a2a5737de9dd75ab45c096944813c42))
|
||||
* adjust poster url for audio ([01912ea](https://github.com/mediacms-io/mediacms/commit/01912ea1f99ef43793a65712539d6264f1f6410f))
|
||||
* Chapter numbering and preserve custom titles on segment reorder ([#1435](https://github.com/mediacms-io/mediacms/issues/1435)) ([cd7dd4f](https://github.com/mediacms-io/mediacms/commit/cd7dd4f72c9f0bac466c680f686a9ecfdd3a38dd))
|
||||
* Show default chapter names in textarea instead of placeholder text ([#1428](https://github.com/mediacms-io/mediacms/issues/1428)) ([5eb6faf](https://github.com/mediacms-io/mediacms/commit/5eb6fafb8c6928b8bc3fe5f0c7af315273f78a55))
|
||||
* static files ([#1429](https://github.com/mediacms-io/mediacms/issues/1429)) ([ba2c31b](https://github.com/mediacms-io/mediacms/commit/ba2c31b1e65b7f508dee598b1f2d86f01f9bf036))
|
||||
|
||||
### Documentation
|
||||
|
||||
* update page link ([aeef828](https://github.com/mediacms-io/mediacms/commit/aeef8284bfba2a9a7f69c684f96c54f0e0e0cf92))
|
||||
@@ -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)
|
||||
81
Dockerfile
81
Dockerfile
@@ -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
|
||||
@@ -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;"]
|
||||
23
HISTORY.md
23
HISTORY.md
@@ -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)
|
||||
15
Makefile
15
Makefile
@@ -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
|
||||
|
||||
|
||||
292
QUICKSTART.md
292
QUICKSTART.md
@@ -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!**
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
@@ -24,6 +24,7 @@ INSTALLED_APPS = [
|
||||
"actions.apps.ActionsConfig",
|
||||
"rbac.apps.RbacConfig",
|
||||
"identity_providers.apps.IdentityProvidersConfig",
|
||||
"lti.apps.LtiConfig",
|
||||
"debug_toolbar",
|
||||
"mptt",
|
||||
"crispy_forms",
|
||||
|
||||
@@ -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,18 @@ 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"
|
||||
# Consider using cached_db for reliability if sessions are lost between many LTI launches
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION = "7.2.1"
|
||||
VERSION = "8.12"
|
||||
|
||||
@@ -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/*;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
238
custom/README.md
238
custom/README.md
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -73,3 +73,8 @@ Can be set through the SAML Configurations tab:
|
||||
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
|
||||
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
|
||||
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
|
||||
|
||||
|
||||
## More options
|
||||
USER_SEARCH_FIELD = "name_username_email"
|
||||
ALLOW_MEDIA_REPLACEMENT = True
|
||||
|
||||
3
deploy/docker/README.md
Normal file
3
deploy/docker/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# MediaCMS on Docker
|
||||
|
||||
See: [Details](../../docs/Docker_deployment.md)
|
||||
38
deploy/docker/entrypoint.sh
Executable file
38
deploy/docker/entrypoint.sh
Executable 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 "$@"
|
||||
36
deploy/docker/local_settings.py
Normal file
36
deploy/docker/local_settings.py
Normal 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'
|
||||
@@ -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;
|
||||
|
||||
@@ -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
70
deploy/docker/prestart.sh
Executable 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
|
||||
17
deploy/docker/reverse_proxy/certs/localhost.crt
Normal file
17
deploy/docker/reverse_proxy/certs/localhost.crt
Normal 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-----
|
||||
27
deploy/docker/reverse_proxy/certs/localhost.key
Normal file
27
deploy/docker/reverse_proxy/certs/localhost.key
Normal 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
17
deploy/docker/start.sh
Executable 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
|
||||
12
deploy/docker/supervisord/supervisord-celery_beat.conf
Normal file
12
deploy/docker/supervisord/supervisord-celery_beat.conf
Normal 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
|
||||
13
deploy/docker/supervisord/supervisord-celery_long.conf
Normal file
13
deploy/docker/supervisord/supervisord-celery_long.conf
Normal 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
|
||||
12
deploy/docker/supervisord/supervisord-celery_short.conf
Normal file
12
deploy/docker/supervisord/supervisord-celery_short.conf
Normal 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
|
||||
2
deploy/docker/supervisord/supervisord-debian.conf
Normal file
2
deploy/docker/supervisord/supervisord-debian.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
9
deploy/docker/supervisord/supervisord-gunicorn.conf
Normal file
9
deploy/docker/supervisord/supervisord-gunicorn.conf
Normal 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
|
||||
11
deploy/docker/supervisord/supervisord-nginx.conf
Normal file
11
deploy/docker/supervisord/supervisord-nginx.conf
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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-----
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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
|
||||
@@ -1,7 +0,0 @@
|
||||
/home/mediacms.io/mediacms/logs/*.log {
|
||||
weekly
|
||||
missingok
|
||||
rotate 7
|
||||
compress
|
||||
notifempty
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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):
|
||||
@@ -145,6 +146,8 @@ class CategoryAdmin(admin.ModelAdmin):
|
||||
list_filter.insert(0, "is_rbac_category")
|
||||
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||
list_filter.insert(-1, "identity_provider")
|
||||
if getattr(settings, 'USE_LTI', False):
|
||||
list_filter.append("is_lms_course")
|
||||
|
||||
return list_filter
|
||||
|
||||
@@ -154,6 +157,8 @@ class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display.insert(-1, "is_rbac_category")
|
||||
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||
list_display.insert(-1, "identity_provider")
|
||||
if getattr(settings, 'USE_LTI', False):
|
||||
list_display.insert(-1, "is_lms_course")
|
||||
|
||||
return list_display
|
||||
|
||||
@@ -167,6 +172,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 +190,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):
|
||||
|
||||
@@ -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
|
||||
|
||||
119
files/forms.py
119
files/forms.py
@@ -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,19 @@ 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
|
||||
self.request = kwargs.pop('request', None)
|
||||
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 +147,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
|
||||
@@ -156,6 +170,16 @@ class MediaPublishForm(forms.ModelForm):
|
||||
|
||||
self.fields['category'].queryset = Category.objects.filter(id__in=combined_category_ids).order_by('title')
|
||||
|
||||
# Filter for LMS courses only when in embed mode
|
||||
if self.request and 'category' in self.fields:
|
||||
is_embed_mode = self._check_embed_mode()
|
||||
if is_embed_mode:
|
||||
current_queryset = self.fields['category'].queryset
|
||||
self.fields['category'].queryset = current_queryset.filter(is_lms_course=True)
|
||||
self.fields['category'].label = 'Course'
|
||||
self.fields['category'].help_text = 'Media can be shared with one or more courses'
|
||||
self.fields['category'].widget.is_lms_mode = True
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = True
|
||||
self.helper.form_class = 'post-form'
|
||||
@@ -173,12 +197,56 @@ class MediaPublishForm(forms.ModelForm):
|
||||
|
||||
self.helper.layout.append(FormActions(Submit('submit', 'Publish Media', css_class='primaryAction')))
|
||||
|
||||
def _check_embed_mode(self):
|
||||
"""Check if the current request is in embed mode"""
|
||||
if not self.request:
|
||||
return False
|
||||
|
||||
# Check query parameter
|
||||
mode = self.request.GET.get('mode', '')
|
||||
if mode == 'lms_embed_mode':
|
||||
return True
|
||||
|
||||
# Check session storage
|
||||
if self.request.session.get('lms_embed_mode') == 'true':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
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 +257,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 +270,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 +414,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
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "حذف الوسائط",
|
||||
"DOWNLOAD": "تحميل",
|
||||
"DURATION": "المدة",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "حذف الوسائط",
|
||||
"Delete media": "حذف الوسائط",
|
||||
"Disable Comments": "تعطيل التعليقات",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "فشل تغيير المالك. يرجى المحاولة مرة أخرى.",
|
||||
"Failed to copy media.": "فشل نسخ الوسائط.",
|
||||
"Failed to create playlist": "فشل إنشاء قائمة التشغيل",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "فشل حذف الوسائط. يرجى المحاولة مرة أخرى.",
|
||||
"Failed to disable comments.": "فشل تعطيل التعليقات.",
|
||||
"Failed to disable download.": "فشل تعطيل التنزيل.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "إزالة من القائمة",
|
||||
"Remove tag": "إزالة العلامة",
|
||||
"Remove user": "إزالة المستخدم",
|
||||
"Replace": "",
|
||||
"SAVE": "حفظ",
|
||||
"SEARCH": "بحث",
|
||||
"SHARE": "مشاركة",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "تحديد جميع الوسائط",
|
||||
"Select publish state:": "اختر حالة النشر:",
|
||||
"Selected": "محدد",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "مشاركة مني",
|
||||
"Shared with me": "مشاركة معي",
|
||||
"Sign in": "تسجيل الدخول",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "تم تفعيل التعليقات بنجاح",
|
||||
"Successfully changed owner": "تم تغيير المالك بنجاح",
|
||||
"Successfully deleted": "تم الحذف بنجاح",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "تم التحديث بنجاح",
|
||||
"Successfully updated categories": "تم تحديث الفئات بنجاح",
|
||||
"Successfully updated playlist membership": "تم تحديث عضوية قائمة التشغيل بنجاح",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "مرحباً",
|
||||
"You are going to copy": "سوف تقوم بالنسخ",
|
||||
"You are going to delete": "سوف تقوم بالحذف",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "سوف تقوم بتعطيل التعليقات لـ",
|
||||
"You are going to disable download for": "سوف تقوم بتعطيل التنزيل لـ",
|
||||
"You are going to enable comments to": "سوف تقوم بتفعيل التعليقات لـ",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "মিডিয়া মুছুন",
|
||||
"DOWNLOAD": "ডাউনলোড",
|
||||
"DURATION": "সময়কাল",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "",
|
||||
"Delete media": "মিডিয়া মুছুন",
|
||||
"Disable Comments": "",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "",
|
||||
"Failed to copy media.": "মিডিয়া কপি করতে ব্যর্থ হয়েছে।",
|
||||
"Failed to create playlist": "",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "মিডিয়া মুছতে ব্যর্থ হয়েছে। দয়া করে আবার চেষ্টা করুন।",
|
||||
"Failed to disable comments.": "মন্তব্য নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
||||
"Failed to disable download.": "ডাউনলোড নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "সংরক্ষণ করুন",
|
||||
"SEARCH": "অনুসন্ধান",
|
||||
"SHARE": "শেয়ার করুন",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "",
|
||||
"Select publish state:": "",
|
||||
"Selected": "",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "আমার দ্বারা শেয়ার করা",
|
||||
"Shared with me": "আমার সাথে শেয়ার করা",
|
||||
"Sign in": "সাইন ইন করুন",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "মন্তব্য সফলভাবে সক্রিয় হয়েছে",
|
||||
"Successfully changed owner": "",
|
||||
"Successfully deleted": "সফলভাবে মুছে ফেলা হয়েছে",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "",
|
||||
"Successfully updated categories": "",
|
||||
"Successfully updated playlist membership": "",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "স্বাগতম",
|
||||
"You are going to copy": "আপনি কপি করতে চলেছেন",
|
||||
"You are going to delete": "আপনি মুছে ফেলতে চলেছেন",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "আপনি মন্তব্য নিষ্ক্রিয় করতে চলেছেন",
|
||||
"You are going to disable download for": "আপনি ডাউনলোড নিষ্ক্রিয় করতে চলেছেন",
|
||||
"You are going to enable comments to": "আপনি মন্তব্য সক্রিয় করতে চলেছেন",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "SLET MEDIE",
|
||||
"DOWNLOAD": "HENT",
|
||||
"DURATION": "VARIGHED",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Slet Medie",
|
||||
"Delete media": "Slet medie",
|
||||
"Disable Comments": "Deaktiver Kommentarer",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Ændring af ejer mislykkedes. Prøv venligst igen.",
|
||||
"Failed to copy media.": "Kopiering af medie mislykkedes.",
|
||||
"Failed to create playlist": "Oprettelse af playliste mislykkedes",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Sletning af medie mislykkedes. Prøv venligst igen.",
|
||||
"Failed to disable comments.": "Deaktivering af kommentarer mislykkedes.",
|
||||
"Failed to disable download.": "Deaktivering af download mislykkedes.",
|
||||
@@ -162,6 +164,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",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Vælg alle medier",
|
||||
"Select publish state:": "Vælg publiceringsstatus:",
|
||||
"Selected": "Valgt",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Delt af mig",
|
||||
"Shared with me": "Delt med mig",
|
||||
"Sign in": "Log ind",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Kommentarer aktiveret med succes",
|
||||
"Successfully changed owner": "Ejer ændret med succes",
|
||||
"Successfully deleted": "Slettet med succes",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Opdateret med succes",
|
||||
"Successfully updated categories": "Kategorier opdateret med succes",
|
||||
"Successfully updated playlist membership": "Playlistemedlemskab opdateret med succes",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Velkommen",
|
||||
"You are going to copy": "Du er ved at kopiere",
|
||||
"You are going to delete": "Du er ved at slette",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Du er ved at deaktivere kommentarer til",
|
||||
"You are going to disable download for": "Du er ved at deaktivere download for",
|
||||
"You are going to enable comments to": "Du er ved at aktivere kommentarer til",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "MEDIEN LÖSCHEN",
|
||||
"DOWNLOAD": "HERUNTERLADEN",
|
||||
"DURATION": "DAUER",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Medien löschen",
|
||||
"Delete media": "Medien löschen",
|
||||
"Disable Comments": "Kommentare deaktivieren",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Fehler beim Ändern des Eigentümers. Bitte versuchen Sie es erneut.",
|
||||
"Failed to copy media.": "Fehler beim Kopieren der Medien.",
|
||||
"Failed to create playlist": "Fehler beim Erstellen der Playlist",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Fehler beim Löschen der Medien. Bitte versuchen Sie es erneut.",
|
||||
"Failed to disable comments.": "Fehler beim Deaktivieren der Kommentare.",
|
||||
"Failed to disable download.": "Fehler beim Deaktivieren des Downloads.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Aus Liste entfernen",
|
||||
"Remove tag": "Tag entfernen",
|
||||
"Remove user": "Benutzer entfernen",
|
||||
"Replace": "",
|
||||
"SAVE": "SPEICHERN",
|
||||
"SEARCH": "SUCHE",
|
||||
"SHARE": "TEILEN",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Alle Medien auswählen",
|
||||
"Select publish state:": "Veröffentlichungsstatus auswählen:",
|
||||
"Selected": "Ausgewählt",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Von mir geteilt",
|
||||
"Shared with me": "Mit mir geteilt",
|
||||
"Sign in": "Anmelden",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Kommentare erfolgreich aktiviert",
|
||||
"Successfully changed owner": "Eigentümer erfolgreich geändert",
|
||||
"Successfully deleted": "Erfolgreich gelöscht",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Erfolgreich aktualisiert",
|
||||
"Successfully updated categories": "Kategorien erfolgreich aktualisiert",
|
||||
"Successfully updated playlist membership": "Playlist-Mitgliedschaft erfolgreich aktualisiert",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Willkommen",
|
||||
"You are going to copy": "Sie werden kopieren",
|
||||
"You are going to delete": "Sie werden löschen",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Sie werden Kommentare deaktivieren für",
|
||||
"You are going to disable download for": "Sie werden Download deaktivieren für",
|
||||
"You are going to enable comments to": "Sie werden Kommentare aktivieren für",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΟΥ",
|
||||
"DOWNLOAD": "ΚΑΤΕΒΑΣΜΑ",
|
||||
"DURATION": "ΔΙΑΡΚΕΙΑ",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Διαγραφή Αρχείου",
|
||||
"Delete media": "Διαγραφή αρχείου",
|
||||
"Disable Comments": "Απενεργοποίηση Σχολίων",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Αποτυχία αλλαγής ιδιοκτήτη. Παρακαλώ δοκιμάστε ξανά.",
|
||||
"Failed to copy media.": "Αποτυχία αντιγραφής αρχείου.",
|
||||
"Failed to create playlist": "Αποτυχία δημιουργίας λίστας",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Αποτυχία διαγραφής αρχείου. Παρακαλώ δοκιμάστε ξανά.",
|
||||
"Failed to disable comments.": "Αποτυχία απενεργοποίησης σχολίων.",
|
||||
"Failed to disable download.": "Αποτυχία απενεργοποίησης λήψης.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Αφαίρεση από λίστα",
|
||||
"Remove tag": "Αφαίρεση ετικέτας",
|
||||
"Remove user": "Αφαίρεση χρήστη",
|
||||
"Replace": "",
|
||||
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
|
||||
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
|
||||
"SHARE": "ΚΟΙΝΟΠΟΙΗΣΗ",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Επιλογή όλων των αρχείων",
|
||||
"Select publish state:": "Επιλέξτε κατάσταση δημοσίευσης:",
|
||||
"Selected": "Επιλεγμένα",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Κοινοποιήθηκαν από εμένα",
|
||||
"Shared with me": "Κοινοποιήθηκαν σε εμένα",
|
||||
"Sign in": "Σύνδεση",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Τα σχόλια ενεργοποιήθηκαν με επιτυχία",
|
||||
"Successfully changed owner": "Ο ιδιοκτήτης άλλαξε με επιτυχία",
|
||||
"Successfully deleted": "Διαγράφηκε με επιτυχία",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Ενημερώθηκε με επιτυχία",
|
||||
"Successfully updated categories": "Οι κατηγορίες ενημερώθηκαν με επιτυχία",
|
||||
"Successfully updated playlist membership": "Η συμμετοχή στη λίστα ενημερώθηκε με επιτυχία",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Καλώς ήρθατε",
|
||||
"You are going to copy": "Πρόκειται να αντιγράψετε",
|
||||
"You are going to delete": "Πρόκειται να διαγράψετε",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Πρόκειται να απενεργοποιήσετε τα σχόλια για",
|
||||
"You are going to disable download for": "Πρόκειται να απενεργοποιήσετε τη λήψη για",
|
||||
"You are going to enable comments to": "Πρόκειται να ενεργοποιήσετε τα σχόλια για",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE": "",
|
||||
"DELETE MEDIA": "",
|
||||
"Delete media": "",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "",
|
||||
"Disable Comments": "",
|
||||
"Disable Download": "",
|
||||
@@ -71,6 +72,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "",
|
||||
"Failed to copy media.": "",
|
||||
"Failed to create playlist": "",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "",
|
||||
"Failed to disable comments.": "",
|
||||
"Failed to disable download.": "",
|
||||
@@ -165,6 +167,7 @@ translation_strings = {
|
||||
"Recommended": "",
|
||||
"Record Screen": "",
|
||||
"Register": "",
|
||||
"Replace": "",
|
||||
"Remove category": "",
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
@@ -183,6 +186,10 @@ translation_strings = {
|
||||
"Selected": "",
|
||||
"selected": "",
|
||||
"SHARE": "",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "",
|
||||
"Shared with me": "",
|
||||
"SHOW MORE": "",
|
||||
@@ -200,6 +207,7 @@ translation_strings = {
|
||||
"Successfully changed owner": "",
|
||||
"Successfully Copied": "",
|
||||
"Successfully deleted": "",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully Disabled comments": "",
|
||||
"Successfully Disabled Download": "",
|
||||
"Successfully Enabled comments": "",
|
||||
@@ -251,6 +259,7 @@ translation_strings = {
|
||||
"yet": "",
|
||||
"You are going to copy": "",
|
||||
"You are going to delete": "",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "",
|
||||
"You are going to disable download for": "",
|
||||
"You are going to enable comments to": "",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "ELIMINAR MEDIOS",
|
||||
"DOWNLOAD": "DESCARGAR",
|
||||
"DURATION": "DURACIÓN",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Eliminar Medio",
|
||||
"Delete media": "Eliminar medios",
|
||||
"Disable Comments": "Deshabilitar Comentarios",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Error al cambiar propietario. Por favor, inténtelo de nuevo.",
|
||||
"Failed to copy media.": "Error al copiar medios.",
|
||||
"Failed to create playlist": "Error al crear lista de reproducción",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Error al eliminar medios. Por favor, inténtelo de nuevo.",
|
||||
"Failed to disable comments.": "Error al deshabilitar comentarios.",
|
||||
"Failed to disable download.": "Error al deshabilitar descarga.",
|
||||
@@ -162,6 +164,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",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Seleccionar todos los medios",
|
||||
"Select publish state:": "Seleccionar estado de publicación:",
|
||||
"Selected": "Seleccionado",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Compartido por mí",
|
||||
"Shared with me": "Compartido conmigo",
|
||||
"Sign in": "Iniciar sesión",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Comentarios habilitados exitosamente",
|
||||
"Successfully changed owner": "Propietario cambiado exitosamente",
|
||||
"Successfully deleted": "Eliminado exitosamente",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Actualizado exitosamente",
|
||||
"Successfully updated categories": "Categorías actualizadas exitosamente",
|
||||
"Successfully updated playlist membership": "Membresía de lista de reproducción actualizada exitosamente",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Bienvenido",
|
||||
"You are going to copy": "Vas a copiar",
|
||||
"You are going to delete": "Vas a eliminar",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Vas a deshabilitar comentarios de",
|
||||
"You are going to disable download for": "Vas a deshabilitar descarga de",
|
||||
"You are going to enable comments to": "Vas a habilitar comentarios de",
|
||||
|
||||
@@ -49,6 +49,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "SUPPRIMER LE MÉDIA",
|
||||
"DOWNLOAD": "TÉLÉCHARGER",
|
||||
"DURATION": "DURÉE",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Supprimer le média",
|
||||
"Delete media": "Supprimer le média",
|
||||
"Disable Comments": "Désactiver les commentaires",
|
||||
@@ -71,6 +72,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Échec du changement de propriétaire. Veuillez réessayer.",
|
||||
"Failed to copy media.": "Échec de la copie du média.",
|
||||
"Failed to create playlist": "Échec de la création de la playlist",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Échec de la suppression du média. Veuillez réessayer.",
|
||||
"Failed to disable comments.": "Échec de la désactivation des commentaires.",
|
||||
"Failed to disable download.": "Échec de la désactivation du téléchargement.",
|
||||
@@ -163,6 +165,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",
|
||||
@@ -178,6 +181,10 @@ translation_strings = {
|
||||
"Select all media": "Sélectionner tous les médias",
|
||||
"Select publish state:": "Sélectionner l'état de publication:",
|
||||
"Selected": "Sélectionné",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Partagé par moi",
|
||||
"Shared with me": "Partagé avec moi",
|
||||
"Sign in": "Se connecter",
|
||||
@@ -196,6 +203,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Commentaires activés avec succès",
|
||||
"Successfully changed owner": "Propriétaire changé avec succès",
|
||||
"Successfully deleted": "Supprimé avec succès",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Mis à jour avec succès",
|
||||
"Successfully updated categories": "Catégories mises à jour avec succès",
|
||||
"Successfully updated playlist membership": "Adhésion à la playlist mise à jour avec succès",
|
||||
@@ -240,6 +248,7 @@ translation_strings = {
|
||||
"Welcome": "Bienvenue",
|
||||
"You are going to copy": "Vous allez copier",
|
||||
"You are going to delete": "Vous allez supprimer",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Vous allez désactiver les commentaires de",
|
||||
"You are going to disable download for": "Vous allez désactiver le téléchargement de",
|
||||
"You are going to enable comments to": "Vous allez activer les commentaires de",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "מחק מדיה",
|
||||
"DOWNLOAD": "הורד",
|
||||
"DURATION": "משך",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "",
|
||||
"Delete media": "מחק מדיה",
|
||||
"Disable Comments": "",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "",
|
||||
"Failed to copy media.": "העתקת המדיה נכשלה.",
|
||||
"Failed to create playlist": "",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "מחיקת המדיה נכשלה. אנא נסה שוב.",
|
||||
"Failed to disable comments.": "ביטול התגובות נכשל.",
|
||||
"Failed to disable download.": "ביטול ההורדה נכשל.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "שמור",
|
||||
"SEARCH": "חפש",
|
||||
"SHARE": "שתף",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "",
|
||||
"Select publish state:": "",
|
||||
"Selected": "",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "שותף על ידי",
|
||||
"Shared with me": "שותף איתי",
|
||||
"Sign in": "התחבר",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "התגובות הופעלו בהצלחה",
|
||||
"Successfully changed owner": "",
|
||||
"Successfully deleted": "נמחק בהצלחה",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "",
|
||||
"Successfully updated categories": "",
|
||||
"Successfully updated playlist membership": "",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "ברוך הבא",
|
||||
"You are going to copy": "אתה עומד להעתיק",
|
||||
"You are going to delete": "אתה עומד למחוק",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "אתה עומד לבטל תגובות ל",
|
||||
"You are going to disable download for": "אתה עומד לבטל הורדה עבור",
|
||||
"You are going to enable comments to": "אתה עומד להפעיל תגובות ל",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "मीडिया हटाएं",
|
||||
"DOWNLOAD": "डाउनलोड करें",
|
||||
"DURATION": "अवधि",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "मीडिया हटाएं",
|
||||
"Delete media": "मीडिया हटाएं",
|
||||
"Disable Comments": "टिप्पणियां अक्षम करें",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "स्वामी बदलने में विफल। कृपया पुनः प्रयास करें।",
|
||||
"Failed to copy media.": "मीडिया कॉपी करने में विफल।",
|
||||
"Failed to create playlist": "प्लेलिस्ट बनाने में विफल",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "मीडिया हटाने में विफल। कृपया पुनः प्रयास करें।",
|
||||
"Failed to disable comments.": "टिप्पणियों को अक्षम करने में विफल।",
|
||||
"Failed to disable download.": "डाउनलोड अक्षम करने में विफल।",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "सूची से हटाएं",
|
||||
"Remove tag": "टैग हटाएं",
|
||||
"Remove user": "उपयोगकर्ता हटाएं",
|
||||
"Replace": "",
|
||||
"SAVE": "सहेजें",
|
||||
"SEARCH": "खोजें",
|
||||
"SHARE": "साझा करें",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "सभी मीडिया चुनें",
|
||||
"Select publish state:": "प्रकाशन स्थिति चुनें:",
|
||||
"Selected": "चयनित",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "मेरे द्वारा साझा किया गया",
|
||||
"Shared with me": "मेरे साथ साझा किया गया",
|
||||
"Sign in": "साइन इन करें",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "टिप्पणियां सफलतापूर्वक सक्षम की गईं",
|
||||
"Successfully changed owner": "स्वामी सफलतापूर्वक बदला गया",
|
||||
"Successfully deleted": "सफलतापूर्वक हटाया गया",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "सफलतापूर्वक अपडेट किया गया",
|
||||
"Successfully updated categories": "श्रेणियां सफलतापूर्वक अपडेट की गईं",
|
||||
"Successfully updated playlist membership": "प्लेलिस्ट सदस्यता सफलतापूर्वक अपडेट की गई",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "स्वागत है",
|
||||
"You are going to copy": "आप कॉपी करने जा रहे हैं",
|
||||
"You are going to delete": "आप हटाने जा रहे हैं",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "आप टिप्पणियों को अक्षम करने जा रहे हैं",
|
||||
"You are going to disable download for": "आप डाउनलोड को अक्षम करने जा रहे हैं",
|
||||
"You are going to enable comments to": "आप टिप्पणियों को सक्षम करने जा रहे हैं",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "HAPUS MEDIA",
|
||||
"DOWNLOAD": "UNDUH",
|
||||
"DURATION": "DURASI",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Hapus Media",
|
||||
"Delete media": "Hapus media",
|
||||
"Disable Comments": "Nonaktifkan Komentar",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Gagal mengganti pemilik. Silakan coba lagi.",
|
||||
"Failed to copy media.": "Gagal menyalin media.",
|
||||
"Failed to create playlist": "Gagal membuat daftar putar",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Gagal menghapus media. Silakan coba lagi.",
|
||||
"Failed to disable comments.": "Gagal menonaktifkan komentar.",
|
||||
"Failed to disable download.": "Gagal menonaktifkan unduhan.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Hapus dari daftar",
|
||||
"Remove tag": "Hapus tag",
|
||||
"Remove user": "Hapus pengguna",
|
||||
"Replace": "",
|
||||
"SAVE": "SIMPAN",
|
||||
"SEARCH": "CARI",
|
||||
"SHARE": "BAGIKAN",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Pilih semua media",
|
||||
"Select publish state:": "Pilih status publikasi:",
|
||||
"Selected": "Dipilih",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Dibagikan oleh saya",
|
||||
"Shared with me": "Dibagikan dengan saya",
|
||||
"Sign in": "Masuk",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Komentar berhasil diaktifkan",
|
||||
"Successfully changed owner": "Berhasil mengganti pemilik",
|
||||
"Successfully deleted": "Berhasil dihapus",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Berhasil diperbarui",
|
||||
"Successfully updated categories": "Kategori berhasil diperbarui",
|
||||
"Successfully updated playlist membership": "Keanggotaan daftar putar berhasil diperbarui",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Selamat datang",
|
||||
"You are going to copy": "Anda akan menyalin",
|
||||
"You are going to delete": "Anda akan menghapus",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Anda akan menonaktifkan komentar untuk",
|
||||
"You are going to disable download for": "Anda akan menonaktifkan unduhan untuk",
|
||||
"You are going to enable comments to": "Anda akan mengaktifkan komentar untuk",
|
||||
|
||||
@@ -49,6 +49,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "ELIMINA MEDIA",
|
||||
"DOWNLOAD": "SCARICA",
|
||||
"DURATION": "DURATA",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Elimina Media",
|
||||
"Delete media": "Elimina media",
|
||||
"Disable Comments": "Disabilita Commenti",
|
||||
@@ -71,6 +72,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Impossibile cambiare proprietario. Riprova.",
|
||||
"Failed to copy media.": "Impossibile copiare il media.",
|
||||
"Failed to create playlist": "Impossibile creare playlist",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Impossibile eliminare il media. Riprova.",
|
||||
"Failed to disable comments.": "Impossibile disabilitare i commenti.",
|
||||
"Failed to disable download.": "Impossibile disabilitare il download.",
|
||||
@@ -163,6 +165,7 @@ translation_strings = {
|
||||
"Remove from list": "Rimuovi dalla lista",
|
||||
"Remove tag": "Rimuovi tag",
|
||||
"Remove user": "Rimuovi utente",
|
||||
"Replace": "",
|
||||
"SAVE": "SALVA",
|
||||
"SEARCH": "CERCA",
|
||||
"SHARE": "CONDIVIDI",
|
||||
@@ -178,6 +181,10 @@ translation_strings = {
|
||||
"Select all media": "Seleziona tutti i media",
|
||||
"Select publish state:": "Seleziona stato di pubblicazione:",
|
||||
"Selected": "Selezionato",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Condiviso da me",
|
||||
"Shared with me": "Condiviso con me",
|
||||
"Sign in": "Login",
|
||||
@@ -196,6 +203,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Commenti abilitati con successo",
|
||||
"Successfully changed owner": "Proprietario cambiato con successo",
|
||||
"Successfully deleted": "Eliminato con successo",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Aggiornato con successo",
|
||||
"Successfully updated categories": "Categorie aggiornate con successo",
|
||||
"Successfully updated playlist membership": "Appartenenza alla playlist aggiornata con successo",
|
||||
@@ -240,6 +248,7 @@ translation_strings = {
|
||||
"Welcome": "Benvenuto",
|
||||
"You are going to copy": "Stai per copiare",
|
||||
"You are going to delete": "Stai per eliminare",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Stai per disabilitare i commenti di",
|
||||
"You are going to disable download for": "Stai per disabilitare il download di",
|
||||
"You are going to enable comments to": "Stai per abilitare i commenti di",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "メディアを削除",
|
||||
"DOWNLOAD": "ダウンロード",
|
||||
"DURATION": "期間",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "メディアを削除",
|
||||
"Delete media": "メディアを削除",
|
||||
"Disable Comments": "コメントを無効化",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "所有者の変更に失敗しました。もう一度お試しください。",
|
||||
"Failed to copy media.": "メディアのコピーに失敗しました。",
|
||||
"Failed to create playlist": "プレイリストの作成に失敗しました",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "メディアの削除に失敗しました。もう一度お試しください。",
|
||||
"Failed to disable comments.": "コメントの無効化に失敗しました。",
|
||||
"Failed to disable download.": "ダウンロードの無効化に失敗しました。",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "リストから削除",
|
||||
"Remove tag": "タグを削除",
|
||||
"Remove user": "ユーザーを削除",
|
||||
"Replace": "",
|
||||
"SAVE": "保存",
|
||||
"SEARCH": "検索",
|
||||
"SHARE": "共有",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "すべてのメディアを選択",
|
||||
"Select publish state:": "公開状態を選択:",
|
||||
"Selected": "選択済み",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "自分が共有",
|
||||
"Shared with me": "共有されたもの",
|
||||
"Sign in": "サインイン",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "コメントが正常に有効化されました",
|
||||
"Successfully changed owner": "所有者が正常に変更されました",
|
||||
"Successfully deleted": "正常に削除されました",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "正常に更新されました",
|
||||
"Successfully updated categories": "カテゴリーが正常に更新されました",
|
||||
"Successfully updated playlist membership": "プレイリストメンバーシップが正常に更新されました",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "ようこそ",
|
||||
"You are going to copy": "コピーします",
|
||||
"You are going to delete": "削除します",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "コメントを無効化します",
|
||||
"You are going to disable download for": "ダウンロードを無効化します",
|
||||
"You are going to enable comments to": "コメントを有効化します",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "미디어 삭제",
|
||||
"DOWNLOAD": "다운로드",
|
||||
"DURATION": "재생 시간",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "미디어 삭제",
|
||||
"Delete media": "미디어 삭제",
|
||||
"Disable Comments": "댓글 비활성화",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "소유자 변경에 실패했습니다. 다시 시도해주세요.",
|
||||
"Failed to copy media.": "미디어 복사에 실패했습니다.",
|
||||
"Failed to create playlist": "재생 목록 만들기 실패",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "미디어 삭제에 실패했습니다. 다시 시도해주세요.",
|
||||
"Failed to disable comments.": "댓글 비활성화에 실패했습니다.",
|
||||
"Failed to disable download.": "다운로드 비활성화에 실패했습니다.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "목록에서 제거",
|
||||
"Remove tag": "태그 제거",
|
||||
"Remove user": "사용자 제거",
|
||||
"Replace": "",
|
||||
"SAVE": "저장",
|
||||
"SEARCH": "검색",
|
||||
"SHARE": "공유",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "모든 미디어 선택",
|
||||
"Select publish state:": "게시 상태 선택:",
|
||||
"Selected": "선택됨",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "내가 공유함",
|
||||
"Shared with me": "나와 공유됨",
|
||||
"Sign in": "로그인",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "댓글이 활성화되었습니다",
|
||||
"Successfully changed owner": "소유자가 변경되었습니다",
|
||||
"Successfully deleted": "삭제 성공",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "업데이트 성공",
|
||||
"Successfully updated categories": "카테고리가 업데이트되었습니다",
|
||||
"Successfully updated playlist membership": "재생 목록 멤버십이 업데이트되었습니다",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "환영합니다",
|
||||
"You are going to copy": "복사하려고 합니다",
|
||||
"You are going to delete": "삭제하려고 합니다",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "댓글을 비활성화하려고 합니다",
|
||||
"You are going to disable download for": "다운로드를 비활성화하려고 합니다",
|
||||
"You are going to enable comments to": "댓글을 활성화하려고 합니다",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "MEDIA VERWIJDEREN",
|
||||
"DOWNLOAD": "DOWNLOADEN",
|
||||
"DURATION": "DUUR",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Media verwijderen",
|
||||
"Delete media": "Media verwijderen",
|
||||
"Disable Comments": "Reacties uitschakelen",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Eigenaar wijzigen mislukt. Probeer het opnieuw.",
|
||||
"Failed to copy media.": "Media kopiëren mislukt.",
|
||||
"Failed to create playlist": "Afspeellijst maken mislukt",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Media verwijderen mislukt. Probeer het opnieuw.",
|
||||
"Failed to disable comments.": "Reacties uitschakelen mislukt.",
|
||||
"Failed to disable download.": "Download uitschakelen mislukt.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Verwijderen uit lijst",
|
||||
"Remove tag": "Tag verwijderen",
|
||||
"Remove user": "Gebruiker verwijderen",
|
||||
"Replace": "",
|
||||
"SAVE": "OPSLAAN",
|
||||
"SEARCH": "ZOEKEN",
|
||||
"SHARE": "DELEN",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Alle media selecteren",
|
||||
"Select publish state:": "Selecteer publicatiestatus:",
|
||||
"Selected": "Geselecteerd",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Gedeeld door mij",
|
||||
"Shared with me": "Gedeeld met mij",
|
||||
"Sign in": "Inloggen",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Reacties succesvol ingeschakeld",
|
||||
"Successfully changed owner": "Eigenaar succesvol gewijzigd",
|
||||
"Successfully deleted": "Succesvol verwijderd",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Succesvol bijgewerkt",
|
||||
"Successfully updated categories": "Categorieën succesvol bijgewerkt",
|
||||
"Successfully updated playlist membership": "Afspeellijstlidmaatschap succesvol bijgewerkt",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Welkom",
|
||||
"You are going to copy": "Je gaat kopiëren",
|
||||
"You are going to delete": "Je gaat verwijderen",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Je gaat reacties uitschakelen voor",
|
||||
"You are going to disable download for": "Je gaat download uitschakelen voor",
|
||||
"You are going to enable comments to": "Je gaat reacties inschakelen voor",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "EXCLUIR MÍDIA",
|
||||
"DOWNLOAD": "BAIXAR",
|
||||
"DURATION": "DURAÇÃO",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Excluir mídia",
|
||||
"Delete media": "Excluir mídia",
|
||||
"Disable Comments": "Desativar comentários",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Falha ao mudar proprietário. Por favor, tente novamente.",
|
||||
"Failed to copy media.": "Falha ao copiar mídia.",
|
||||
"Failed to create playlist": "Falha ao criar playlist",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Falha ao excluir mídia. Por favor, tente novamente.",
|
||||
"Failed to disable comments.": "Falha ao desativar comentários.",
|
||||
"Failed to disable download.": "Falha ao desativar download.",
|
||||
@@ -162,6 +164,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",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Selecionar todas as mídias",
|
||||
"Select publish state:": "Selecionar estado de publicação:",
|
||||
"Selected": "Selecionado",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Compartilhado por mim",
|
||||
"Shared with me": "Compartilhado comigo",
|
||||
"Sign in": "Entrar",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Comentários ativados com sucesso",
|
||||
"Successfully changed owner": "Proprietário alterado com sucesso",
|
||||
"Successfully deleted": "Excluído com sucesso",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Atualizado com sucesso",
|
||||
"Successfully updated categories": "Categorias atualizadas com sucesso",
|
||||
"Successfully updated playlist membership": "Associação da playlist atualizada com sucesso",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Bem-vindo",
|
||||
"You are going to copy": "Você vai copiar",
|
||||
"You are going to delete": "Você vai excluir",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Você vai desativar comentários de",
|
||||
"You are going to disable download for": "Você vai desativar download de",
|
||||
"You are going to enable comments to": "Você vai ativar comentários de",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "УДАЛИТЬ МЕДИА",
|
||||
"DOWNLOAD": "СКАЧАТЬ",
|
||||
"DURATION": "ДЛИТЕЛЬНОСТЬ",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Удалить медиа",
|
||||
"Delete media": "Удалить медиа",
|
||||
"Disable Comments": "Отключить комментарии",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Не удалось изменить владельца. Пожалуйста, попробуйте снова.",
|
||||
"Failed to copy media.": "Не удалось скопировать медиа.",
|
||||
"Failed to create playlist": "Не удалось создать плейлист",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Не удалось удалить медиа. Пожалуйста, попробуйте снова.",
|
||||
"Failed to disable comments.": "Не удалось отключить комментарии.",
|
||||
"Failed to disable download.": "Не удалось отключить загрузку.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Удалить из списка",
|
||||
"Remove tag": "Удалить тег",
|
||||
"Remove user": "Удалить пользователя",
|
||||
"Replace": "",
|
||||
"SAVE": "СОХРАНИТЬ",
|
||||
"SEARCH": "ПОИСК",
|
||||
"SHARE": "ПОДЕЛИТЬСЯ",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Выбрать все медиа",
|
||||
"Select publish state:": "Выберите состояние публикации:",
|
||||
"Selected": "Выбрано",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Мной поделено",
|
||||
"Shared with me": "Поделено со мной",
|
||||
"Sign in": "Войти",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Комментарии успешно включены",
|
||||
"Successfully changed owner": "Владелец успешно изменен",
|
||||
"Successfully deleted": "Успешно удалено",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Успешно обновлено",
|
||||
"Successfully updated categories": "Категории успешно обновлены",
|
||||
"Successfully updated playlist membership": "Членство в плейлисте успешно обновлено",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Добро пожаловать",
|
||||
"You are going to copy": "Вы собираетесь скопировать",
|
||||
"You are going to delete": "Вы собираетесь удалить",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Вы собираетесь отключить комментарии для",
|
||||
"You are going to disable download for": "Вы собираетесь отключить загрузку для",
|
||||
"You are going to enable comments to": "Вы собираетесь включить комментарии для",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "IZBRIŠI MEDIJ",
|
||||
"DOWNLOAD": "PRENESI",
|
||||
"DURATION": "TRAJANJE",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Izbriši Medij",
|
||||
"Delete media": "Izbriši medij",
|
||||
"Disable Comments": "Onemogoči Komentarje",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Spreminjanje lastnika ni uspelo. Prosim poskusite ponovno.",
|
||||
"Failed to copy media.": "Kopiranje medija ni uspelo.",
|
||||
"Failed to create playlist": "Ustvarjanje seznama predvajanja ni uspelo",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Brisanje medija ni uspelo. Prosim poskusite ponovno.",
|
||||
"Failed to disable comments.": "Onemogočanje komentarjev ni uspelo.",
|
||||
"Failed to disable download.": "Onemogočanje prenosa ni uspelo.",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "Odstrani s seznama",
|
||||
"Remove tag": "Odstrani oznako",
|
||||
"Remove user": "Odstrani uporabnika",
|
||||
"Replace": "",
|
||||
"SAVE": "SHRANI",
|
||||
"SEARCH": "ISKANJE",
|
||||
"SHARE": "DELI",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Izberi vse medije",
|
||||
"Select publish state:": "Izberi stanje objave:",
|
||||
"Selected": "Izbrano",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Deljeno z moje strani",
|
||||
"Shared with me": "Deljeno z mano",
|
||||
"Sign in": "Prijava",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Komentarji uspešno omogočeni",
|
||||
"Successfully changed owner": "Lastnik uspešno spremenjen",
|
||||
"Successfully deleted": "Uspešno izbrisano",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Uspešno posodobljeno",
|
||||
"Successfully updated categories": "Kategorije uspešno posodobljene",
|
||||
"Successfully updated playlist membership": "Članstvo seznama predvajanja uspešno posodobljeno",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Dobrodošli",
|
||||
"You are going to copy": "Kopirate",
|
||||
"You are going to delete": "Brišete",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Onemogočate komentarje za",
|
||||
"You are going to disable download for": "Onemogočate prenos za",
|
||||
"You are going to enable comments to": "Omogočate komentarje za",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "MEDYAYI SİL",
|
||||
"DOWNLOAD": "İNDİR",
|
||||
"DURATION": "SÜRE",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "Medyayı Sil",
|
||||
"Delete media": "Medyayı sil",
|
||||
"Disable Comments": "Yorumları Devre Dışı Bırak",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "Sahip değiştirilemedi. Lütfen tekrar deneyin.",
|
||||
"Failed to copy media.": "Medya kopyalanamadı.",
|
||||
"Failed to create playlist": "Çalma listesi oluşturulamadı",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "Medya silinemedi. Lütfen tekrar deneyin.",
|
||||
"Failed to disable comments.": "Yorumlar devre dışı bırakılamadı.",
|
||||
"Failed to disable download.": "İndirme devre dışı bırakılamadı.",
|
||||
@@ -162,6 +164,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Ş",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "Tüm medyayı seç",
|
||||
"Select publish state:": "Yayınlanma durumunu seç:",
|
||||
"Selected": "Seçildi",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "Paylaştıklarım",
|
||||
"Shared with me": "Benimle paylaşılanlar",
|
||||
"Sign in": "Giriş Yap",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "Yorumlar başarıyla etkinleştirildi",
|
||||
"Successfully changed owner": "Sahip başarıyla değiştirildi",
|
||||
"Successfully deleted": "Başarıyla silindi",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "Başarıyla güncellendi",
|
||||
"Successfully updated categories": "Kategoriler başarıyla güncellendi",
|
||||
"Successfully updated playlist membership": "Çalma listesi üyeliği başarıyla güncellendi",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "Hoş geldiniz",
|
||||
"You are going to copy": "Kopyalayacaksınız",
|
||||
"You are going to delete": "Sileceksiniz",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "Yorumları devre dışı bırakacaksınız",
|
||||
"You are going to disable download for": "İndirmeyi devre dışı bırakacaksınız",
|
||||
"You are going to enable comments to": "Yorumları etkinleştireceksiniz",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "میڈیا حذف کریں",
|
||||
"DOWNLOAD": "ڈاؤن لوڈ",
|
||||
"DURATION": "دورانیہ",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "میڈیا حذف کریں",
|
||||
"Delete media": "میڈیا حذف کریں",
|
||||
"Disable Comments": "تبصرے غیر فعال کریں",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "مالک تبدیل کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
||||
"Failed to copy media.": "میڈیا کاپی کرنے میں ناکام۔",
|
||||
"Failed to create playlist": "پلے لسٹ بنانے میں ناکام",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "میڈیا حذف کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
||||
"Failed to disable comments.": "تبصرے غیر فعال کرنے میں ناکام۔",
|
||||
"Failed to disable download.": "ڈاؤن لوڈ غیر فعال کرنے میں ناکام۔",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "فہرست سے ہٹائیں",
|
||||
"Remove tag": "ٹیگ ہٹائیں",
|
||||
"Remove user": "صارف ہٹائیں",
|
||||
"Replace": "",
|
||||
"SAVE": "محفوظ کریں",
|
||||
"SEARCH": "تلاش کریں",
|
||||
"SHARE": "شیئر کریں",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "تمام میڈیا منتخب کریں",
|
||||
"Select publish state:": "اشاعت کی حالت منتخب کریں:",
|
||||
"Selected": "منتخب شدہ",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "میری طرف سے شیئر کیا گیا",
|
||||
"Shared with me": "میرے ساتھ شیئر کیا گیا",
|
||||
"Sign in": "سائن ان کریں",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "تبصرے کامیابی سے فعال ہو گئے",
|
||||
"Successfully changed owner": "مالک کامیابی سے تبدیل ہو گیا",
|
||||
"Successfully deleted": "کامیابی سے حذف ہو گیا",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "کامیابی سے اپ ڈیٹ ہو گیا",
|
||||
"Successfully updated categories": "اقسام کامیابی سے اپ ڈیٹ ہو گئیں",
|
||||
"Successfully updated playlist membership": "پلے لسٹ ممبرشپ کامیابی سے اپ ڈیٹ ہو گئی",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "خوش آمدید",
|
||||
"You are going to copy": "آپ کاپی کرنے جا رہے ہیں",
|
||||
"You are going to delete": "آپ حذف کرنے جا رہے ہیں",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "آپ تبصرے غیر فعال کرنے جا رہے ہیں",
|
||||
"You are going to disable download for": "آپ ڈاؤن لوڈ غیر فعال کرنے جا رہے ہیں",
|
||||
"You are going to enable comments to": "آپ تبصرے فعال کرنے جا رہے ہیں",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "删除媒体",
|
||||
"DOWNLOAD": "下载",
|
||||
"DURATION": "时长",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "",
|
||||
"Delete media": "删除媒体",
|
||||
"Disable Comments": "",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "",
|
||||
"Failed to copy media.": "复制媒体失败。",
|
||||
"Failed to create playlist": "",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "删除媒体失败。请重试。",
|
||||
"Failed to disable comments.": "禁用评论失败。",
|
||||
"Failed to disable download.": "禁用下载失败。",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "保存",
|
||||
"SEARCH": "搜索",
|
||||
"SHARE": "分享",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "",
|
||||
"Select publish state:": "",
|
||||
"Selected": "",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "我分享的",
|
||||
"Shared with me": "分享给我的",
|
||||
"Sign in": "登录",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "评论已成功启用",
|
||||
"Successfully changed owner": "",
|
||||
"Successfully deleted": "删除成功",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "",
|
||||
"Successfully updated categories": "",
|
||||
"Successfully updated playlist membership": "",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "欢迎",
|
||||
"You are going to copy": "您将复制",
|
||||
"You are going to delete": "您将删除",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "您将禁用评论",
|
||||
"You are going to disable download for": "您将禁用下载",
|
||||
"You are going to enable comments to": "您将启用评论",
|
||||
|
||||
@@ -48,6 +48,7 @@ translation_strings = {
|
||||
"DELETE MEDIA": "刪除影片",
|
||||
"DOWNLOAD": "下載",
|
||||
"DURATION": "時長",
|
||||
"Delete Comments": "",
|
||||
"Delete Media": "",
|
||||
"Delete media": "刪除媒體",
|
||||
"Disable Comments": "",
|
||||
@@ -70,6 +71,7 @@ translation_strings = {
|
||||
"Failed to change owner. Please try again.": "",
|
||||
"Failed to copy media.": "複製媒體失敗。",
|
||||
"Failed to create playlist": "",
|
||||
"Failed to delete comments.": "",
|
||||
"Failed to delete media. Please try again.": "刪除媒體失敗。請再試一次。",
|
||||
"Failed to disable comments.": "停用留言失敗。",
|
||||
"Failed to disable download.": "停用下載失敗。",
|
||||
@@ -162,6 +164,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "儲存",
|
||||
"SEARCH": "搜尋",
|
||||
"SHARE": "分享",
|
||||
@@ -177,6 +180,10 @@ translation_strings = {
|
||||
"Select all media": "",
|
||||
"Select publish state:": "",
|
||||
"Selected": "",
|
||||
"Share with Co-Editors": "",
|
||||
"Share with Co-Owners": "",
|
||||
"Share with Co-Viewers": "",
|
||||
"Share with Course Members": "",
|
||||
"Shared by me": "我分享的",
|
||||
"Shared with me": "與我分享",
|
||||
"Sign in": "登入",
|
||||
@@ -195,6 +202,7 @@ translation_strings = {
|
||||
"Successfully Enabled comments": "成功啟用留言",
|
||||
"Successfully changed owner": "",
|
||||
"Successfully deleted": "成功刪除",
|
||||
"Successfully deleted comments": "",
|
||||
"Successfully updated": "",
|
||||
"Successfully updated categories": "",
|
||||
"Successfully updated playlist membership": "",
|
||||
@@ -239,6 +247,7 @@ translation_strings = {
|
||||
"Welcome": "歡迎",
|
||||
"You are going to copy": "您即將複製",
|
||||
"You are going to delete": "您即將刪除",
|
||||
"You are going to delete all comments from": "",
|
||||
"You are going to disable comments to": "您即將停用留言",
|
||||
"You are going to disable download for": "您即將停用下載",
|
||||
"You are going to enable comments to": "您即將啟用留言",
|
||||
|
||||
@@ -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
|
||||
|
||||
24
files/migrations/0014_alter_subtitle_options_and_more.py
Normal file
24
files/migrations/0014_alter_subtitle_options_and_more.py
Normal 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'},
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
21
files/migrations/0016_category_lti_platform.py
Normal file
21
files/migrations/0016_category_lti_platform.py
Normal 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'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user