فهرست منبع

Merge in changes from main

Kyle Maas 1 سال پیش
والد
کامیت
d67570ee1f
62فایلهای تغییر یافته به همراه812 افزوده شده و 209 حذف شده
  1. 20 0
      .github/workflows/ci.yml
  2. 52 0
      .github/workflows/docker-build-push.yml
  3. 0 15
      .github/workflows/lint_test.yml
  4. 15 0
      .github/workflows/pre-commit.yml
  5. 2 5
      .github/workflows/python.yml
  6. 0 1
      .mailmap
  7. 4 4
      .pre-commit-config.yaml
  8. 2 2
      Dockerfile
  9. 57 2
      Dockerfile-dev
  10. 23 0
      HISTORY.md
  11. 6 4
      README.md
  12. 0 1
      actions/migrations/0001_initial.py
  13. 0 1
      actions/migrations/0002_mediaaction_media.py
  14. 0 1
      actions/migrations/0003_auto_20201201_0712.py
  15. 0 1
      cms/custom_pagination.py
  16. 6 0
      cms/settings.py
  17. 2 2
      cms/urls.py
  18. 4 0
      deploy/docker/nginx_http_only.conf
  19. 1 0
      deploy/docker/uwsgi.ini
  20. 2 4
      deploy/local_install/celery_beat.service
  21. 4 6
      deploy/local_install/celery_long.service
  22. 4 6
      deploy/local_install/celery_short.service
  23. 34 0
      deploy/local_install/selinux-mediacms.te
  24. 1 1
      deploy/local_install/uwsgi.ini
  25. 3 2
      docker-compose-dev.yaml
  26. 3 2
      docker-compose-http-proxy.yaml
  27. 3 2
      docker-compose-https-proxy.yaml
  28. 3 2
      docker-compose-letsencrypt.yaml
  29. 3 2
      docker-compose-named-volumes.yaml
  30. 3 2
      docker-compose.yaml
  31. 113 38
      docs/admins_docs.md
  32. BIN
      docs/images/cookie_consent.png
  33. 2 1
      files/context_processors.py
  34. 1 1
      files/feeds.py
  35. 8 0
      files/helpers.py
  36. 0 1
      files/methods.py
  37. 0 1
      files/migrations/0001_initial.py
  38. 0 1
      files/migrations/0002_auto_20201201_0712.py
  39. 0 1
      files/migrations/0003_auto_20210927_1245.py
  40. 24 11
      files/models.py
  41. 5 0
      files/serializers.py
  42. 7 6
      files/tasks.py
  43. 2 2
      files/urls.py
  44. 5 5
      files/views.py
  45. 32 26
      frontend/src/static/js/components/comments/Comments.jsx
  46. 1 1
      frontend/src/static/js/components/video-player/VideoPlayer.jsx
  47. 3 1
      frontend/src/static/js/utils/stores/MediaPageStore.js
  48. 302 0
      install-rhel.sh
  49. 3 3
      install.sh
  50. 21 34
      requirements.txt
  51. 0 0
      static/js/_commons.js
  52. 0 0
      static/js/embed.js
  53. 0 0
      static/js/media.js
  54. 0 0
      static/js/playlist.js
  55. 2 0
      templates/cms/index.html
  56. 20 1
      templates/components/header.html
  57. 1 1
      templates/config/installation/site.html
  58. 1 1
      uploader/urls.py
  59. 0 1
      users/migrations/0001_initial.py
  60. 1 2
      users/urls.py
  61. 0 1
      users/views.py
  62. 1 1
      version.py

+ 20 - 0
.github/workflows/ci.yml

@@ -0,0 +1,20 @@
+---
+name: "CI"
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**/README.md'
+jobs:
+  pre-commit:
+    uses: ./.github/workflows/pre-commit.yml
+  test:
+    uses: ./.github/workflows/python.yml
+    needs: [pre-commit]
+  release:
+    uses: ./.github/workflows/docker-build-push.yml
+    secrets: inherit # pass all secrets
+    needs: [test]
+    if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'

+ 52 - 0
.github/workflows/docker-build-push.yml

@@ -0,0 +1,52 @@
+name: Docker build and push
+
+on:
+  workflow_call:
+  push:
+    tags:
+      - v*.*.*
+
+jobs:
+  release:
+    name: Build & release to DockerHub
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v4
+        with:
+          # List of Docker images to use as base name for tags
+          images: |
+            mediacms/mediacms
+          # Generate Docker tags based on the following events/attributes
+          # Set latest tag for default branch
+          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
+            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.
+            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: Login to Docker Hub
+        uses: docker/login-action@v2.2.0
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Build and push
+        uses: docker/build-push-action@v4
+        with:
+          context: .
+          push: ${{ github.event_name != 'pull_request' }}
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}

+ 0 - 15
.github/workflows/lint_test.yml

@@ -1,15 +0,0 @@
-on:
-  pull_request:
-  push:
-    branches:
-      - main
-
-jobs:
-  pre-commit:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-python@v2
-    - uses: pre-commit/action@v2.0.0
-      with:
-        token: ${{ secrets.GITHUB_TOKEN }}

+ 15 - 0
.github/workflows/pre-commit.yml

@@ -0,0 +1,15 @@
+name: pre-commit
+
+on:
+  workflow_call:
+
+jobs:
+  pre-commit:
+    name: Pre-Commit
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - uses: actions/setup-python@v3
+    - uses: pre-commit/action@v3.0.0
+      with:
+        token: ${{ secrets.GITHUB_TOKEN }}

+ 2 - 5
.github/workflows/python.yml

@@ -1,14 +1,11 @@
 name: Python Tests
 
 on:
-  pull_request:
-  push:
-    branches:
-      - main
+  workflow_call:
 
 jobs:
   build:
-
+    name: Build & test via docker-compose
     runs-on: ubuntu-latest
 
     steps:

+ 0 - 1
.mailmap

@@ -1 +0,0 @@
-Swift Ugandan <swiftugandan@gmail.com> <swiftugandan@gmail.com>

+ 4 - 4
.pre-commit-config.yaml

@@ -1,15 +1,15 @@
 repos:
-  - repo: https://gitlab.com/pycqa/flake8
-    rev: 3.7.9
+  - repo: https://github.com/pycqa/flake8
+    rev: 6.0.0
     hooks:
       - id: flake8
   - repo: https://github.com/pycqa/isort
-    rev: 5.5.4
+    rev: 5.12.0
     hooks:
       - id: isort
         args: ["--profile", "black"]
   - repo: https://github.com/psf/black
-    rev: 22.3.0
+    rev: 23.1.0
     hooks:
       - id: black
         language_version: python3

+ 2 - 2
Dockerfile

@@ -1,4 +1,4 @@
-FROM python:3.8-buster AS compile-image
+FROM python:3.11.4-bookworm AS compile-image
 
 SHELL ["/bin/bash", "-c"]
 
@@ -24,7 +24,7 @@ RUN wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-u
     rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
 
 ############ RUNTIME IMAGE ############
-FROM python:3.8-slim-buster as runtime-image
+FROM python:3.11.4-bookworm as runtime-image
 
 ENV PYTHONUNBUFFERED=1
 ENV PYTHONDONTWRITEBYTECODE=1

+ 57 - 2
Dockerfile-dev

@@ -1,4 +1,4 @@
-FROM mediacms/mediacms:latest
+FROM python:3.11.4-bookworm AS compile-image
 
 SHELL ["/bin/bash", "-c"]
 
@@ -7,10 +7,65 @@ ENV VIRTUAL_ENV=/home/mediacms.io
 ENV PATH="$VIRTUAL_ENV/bin:$PATH"
 ENV PIP_NO_CACHE_DIR=1
 
-RUN cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
+RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
 
+# Install dependencies:
 COPY requirements.txt . 
 COPY requirements-dev.txt .
 RUN pip install -r requirements-dev.txt
 
+
+COPY . /home/mediacms.io/mediacms
 WORKDIR /home/mediacms.io/mediacms
+
+RUN 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 ../bento4 && \
+    mv ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* ../bento4/ && \
+    rm -rf ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
+    rm -rf ../bento4/docs && \
+    rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
+
+############ RUNTIME IMAGE ############
+FROM python:3.11.4-bookworm as runtime-image
+
+ENV PYTHONUNBUFFERED=1
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# See: https://github.com/celery/celery/issues/6285#issuecomment-715316219
+ENV CELERY_APP='cms'
+
+# Use these to toggle which processes supervisord should run
+ENV ENABLE_UWSGI='yes'
+ENV ENABLE_NGINX='yes'
+ENV ENABLE_CELERY_BEAT='yes'
+ENV ENABLE_CELERY_SHORT='yes'
+ENV ENABLE_CELERY_LONG='yes'
+ENV ENABLE_MIGRATIONS='yes'
+
+# Set up virtualenv
+ENV VIRTUAL_ENV=/home/mediacms.io
+ENV PATH="$VIRTUAL_ENV/bin:$PATH"
+
+COPY --chown=www-data:www-data --from=compile-image /home/mediacms.io /home/mediacms.io
+
+RUN apt-get update -y && apt-get -y upgrade && apt-get install --no-install-recommends \
+    supervisor nginx imagemagick procps wget xz-utils -y && \
+    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 && \
+    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
+
+WORKDIR /home/mediacms.io/mediacms
+
+EXPOSE 9000 80
+
+RUN chmod +x ./deploy/docker/entrypoint.sh
+
+ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
+
+CMD ["./deploy/docker/start.sh"]

+ 23 - 0
HISTORY.md

@@ -0,0 +1,23 @@
+# 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)

+ 6 - 4
README.md

@@ -1,11 +1,8 @@
 # MediaCMS
 
-[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/python/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:python)
-[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/javascript/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:javascript)
-<br/>
 [![GitHub license](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
 [![Releases](https://img.shields.io/github/v/release/mediacms-io/mediacms?color=green)](https://github.com/mediacms-io/mediacms/releases/)
-[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/repository/docker/mediacms/mediacms/)
+[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/r/mediacms/mediacms)
 
 
 
@@ -95,18 +92,22 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
 * [Single Server](docs/admins_docs.md#2-server-installation) page
 * [Docker Compose](docs/admins_docs.md#3-docker-installation) page
 
+  A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
+
 ## Configuration
 
 Visit [Configuration](docs/admins_docs.md#5-configuration) page.
 
 
 ## Documentation
+
 * [Users documentation](docs/user_docs.md) page
 * [Administrators documentation](docs/admins_docs.md) page
 * [Developers documentation](docs/developers_docs.md) page
 
 
 ## 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
 
 
@@ -130,4 +131,5 @@ If you like the project, here's a few things you can do
 
 
 ## Contact
+
 info@mediacms.io

+ 0 - 1
actions/migrations/0001_initial.py

@@ -4,7 +4,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = []

+ 0 - 1
actions/migrations/0002_mediaaction_media.py

@@ -5,7 +5,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = [

+ 0 - 1
actions/migrations/0003_auto_20201201_0712.py

@@ -6,7 +6,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = [

+ 0 - 1
cms/custom_pagination.py

@@ -18,7 +18,6 @@ class FastPaginationWithoutCount(PageNumberPagination):
     django_paginator_class = FasterDjangoPaginator
 
     def get_paginated_response(self, data):
-
         return Response(
             OrderedDict(
                 [

+ 6 - 0
cms/settings.py

@@ -7,6 +7,7 @@ DEBUG = False
 # PORTAL NAME, this is the portal title and
 # is also shown on several places as emails
 PORTAL_NAME = "MediaCMS"
+PORTAL_DESCRIPTION = ""
 LANGUAGE_CODE = "en-us"
 TIME_ZONE = "Europe/London"
 
@@ -480,4 +481,9 @@ if GLOBAL_LOGIN_REQUIRED:
         r'/accounts/login/$',
         r'/accounts/logout/$',
         r'/accounts/signup/$',
+        r'/accounts/password/.*/$',
+        r'/accounts/confirm-email/.*/$',
+        r'/api/v[0-9]+/',
     ]
+
+DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

+ 2 - 2
cms/urls.py

@@ -1,7 +1,7 @@
 import debug_toolbar
-from django.conf.urls import include, re_path
+from django.conf.urls import include
 from django.contrib import admin
-from django.urls import path
+from django.urls import path, re_path
 from django.views.generic.base import TemplateView
 from drf_yasg import openapi
 from drf_yasg.views import get_schema_view

+ 4 - 0
deploy/docker/nginx_http_only.conf

@@ -16,6 +16,10 @@ server {
 
     location /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';
+        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }
 
     location / {

+ 1 - 0
deploy/docker/uwsgi.ini

@@ -21,3 +21,4 @@ vacuum = true
 hook-master-start = unix_signal:15 gracefully_kill_them_all
 need-app = true
 die-on-term = true
+buffer-size=32768

+ 2 - 4
deploy/local_install/celery_beat.service

@@ -8,15 +8,13 @@ User=www-data
 Group=www-data
 Restart=always
 RestartSec=10
-Environment=APP_DIR="/home/mediacms.io/mediacms"
+WorkingDirectory=/home/mediacms.io/mediacms
 Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
-Environment=CELERY_APP="cms"
 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"
-Environment=APP_DIR="/home/mediacms.io/mediacms"
 
-ExecStart=/bin/sh -c '${CELERY_BIN} beat -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR}'
+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]

+ 4 - 6
deploy/local_install/celery_long.service

@@ -8,23 +8,21 @@ User=www-data
 Group=www-data
 Restart=always
 RestartSec=10
-Environment=APP_DIR="/home/mediacms.io/mediacms"
+WorkingDirectory=/home/mediacms.io/mediacms
 Environment=CELERYD_NODES="long1"
 Environment=CELERY_QUEUE="long_tasks"
 Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
-Environment=CELERY_APP="cms"
 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"
-Environment=APP_DIR="/home/mediacms.io/mediacms"
 
-ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
+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} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
+ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
 
-ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
+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

+ 4 - 6
deploy/local_install/celery_short.service

@@ -8,14 +8,13 @@ User=www-data
 Group=www-data
 Restart=always
 RestartSec=10
-Environment=APP_DIR="/home/mediacms.io/mediacms"
+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
-Environment=CELERY_APP="cms"
 # or fully qualified:
 #CELERY_APP="proj.tasks:app"
 # How to call manage.py
@@ -28,13 +27,12 @@ Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
 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"
-Environment=APP_DIR="/home/mediacms.io/mediacms"
 
-ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
+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} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
+ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
 
-ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
+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

+ 34 - 0
deploy/local_install/selinux-mediacms.te

@@ -0,0 +1,34 @@
+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 - 1
deploy/local_install/uwsgi.ini

@@ -24,4 +24,4 @@ vacuum = true
 logto = /home/mediacms.io/mediacms/logs/errorlog.txt
 
 disable-logging = true
-
+buffer-size=32768

+ 3 - 2
docker-compose-dev.yaml

@@ -32,7 +32,7 @@ services:
       db:
         condition: service_healthy
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - ../postgres_data:/var/lib/postgresql/data/
     restart: always
@@ -40,8 +40,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 10s
       timeout: 5s
       retries: 5

+ 3 - 2
docker-compose-http-proxy.yaml

@@ -68,7 +68,7 @@ services:
     depends_on:
       - migrations
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - ../postgres_data/:/var/lib/postgresql/data/
     restart: always
@@ -76,8 +76,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 10s
       timeout: 5s
       retries: 5

+ 3 - 2
docker-compose-https-proxy.yaml

@@ -70,7 +70,7 @@ services:
     depends_on:
       - migrations
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - ../postgres_data/:/var/lib/postgresql/data/
     restart: always
@@ -78,8 +78,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 10s
       timeout: 5s
       retries: 5

+ 3 - 2
docker-compose-letsencrypt.yaml

@@ -90,7 +90,7 @@ services:
     depends_on:
       - migrations
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - ../postgres_data:/var/lib/postgresql/data/
     restart: always
@@ -98,8 +98,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 30s
       timeout: 10s
       retries: 5

+ 3 - 2
docker-compose-named-volumes.yaml

@@ -66,7 +66,7 @@ services:
     depends_on:
       - migrations
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - postgres_data:/var/lib/postgresql/data/
     restart: always
@@ -74,8 +74,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 30s
       timeout: 10s
       retries: 5

+ 3 - 2
docker-compose.yaml

@@ -62,7 +62,7 @@ services:
     depends_on:
       - migrations
   db:
-    image: postgres:13
+    image: postgres:15.2-alpine
     volumes:
       - ../postgres_data:/var/lib/postgresql/data/
     restart: always
@@ -70,8 +70,9 @@ services:
       POSTGRES_USER: mediacms
       POSTGRES_PASSWORD: mediacms
       POSTGRES_DB: mediacms
+      TZ: Europe/London
     healthcheck:
-      test: ["CMD-SHELL", "pg_isready -U mediacms"]
+      test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
       interval: 10s
       timeout: 5s
       retries: 5

+ 113 - 38
docs/admins_docs.md

@@ -4,7 +4,7 @@
 - [1. Welcome](#1-welcome)
 - [2. Server Installaton](#2-server-installation)
 - [3. Docker Installation](#3-docker-installation)
-- [4. Docker Deployement options](#4-docker-deployment-options)
+- [4. Docker Deployment options](#4-docker-deployment-options)
 - [5. Configuration](#5-configuration)
 - [6. Manage pages](#6-manage-pages)
 - [7. Django admin dashboard](#7-django-admin-dashboard)
@@ -16,19 +16,21 @@
 - [13. How To Add A Static Page To The Sidebar](#13-how-to-add-a-static-page-to-the-sidebar)
 - [14. Add Google Analytics](#14-add-google-analytics)
 - [15. Debugging email issues](#15-debugging-email-issues)
+- [16. Frequently Asked Questions](#16-frequently-asked-questions)
+- [17. Cookie consent code](#17-cookie-consent-code)
 
 
 ## 1. Welcome
-This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications. 
+This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
 
 ## 2. Server Installation
 
-The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
+The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu (tested on versions 20, 22).
 
-Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
-Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings. 
+Installation on an Ubuntu system with git utility installed should be completed in a few minutes with the following steps.
+Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
 
-Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
+Automated script - tested on Ubuntu 20, Ubuntu 22 and Debian Buster
 
 ```bash
 mkdir /home/mediacms.io && cd /home/mediacms.io/
@@ -36,7 +38,7 @@ git clone https://github.com/mediacms-io/mediacms
 cd /home/mediacms.io/mediacms/ && bash ./install.sh
 ```
 
-The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate. 
+The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
 
 
 ### Update
@@ -47,10 +49,25 @@ If you've used the above way to install MediaCMS, update with the following:
 cd /home/mediacms.io/mediacms # enter mediacms directory
 source  /home/mediacms.io/bin/activate # use virtualenv
 git pull # update code
+pip install -r requirements.txt -U # run pip install to update
 python manage.py migrate # run Django migrations
 sudo systemctl restart mediacms celery_long celery_short # restart services
 ```
 
+### Update from version 2 to version 3
+Version 3 is using Django 4 and Celery 5, and needs a recent Python 3.x version. If you are updating from an older version, make sure Python is updated first. Version 2 could run on Python 3.6, but version 3 needs Python3.8 and higher.
+The syntax for starting Celery has also changed, so you have to copy the celery related systemctl files and restart
+
+```
+# cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
+# cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
+# cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
+# systemctl daemon-reload
+# systemctl start celery_long celery_short celery_beat
+```
+
+
+
 ### Configuration
 Checkout the configuration section here.
 
@@ -64,7 +81,7 @@ Database can be backed up with pg_dump and media_files on /home/mediacms.io/medi
 ## Installation
 Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
 
-For Ubuntu 18/20 systems this is:
+For Ubuntu 20/22 systems this is:
 
 ```bash
 curl -fsSL https://get.docker.com -o get-docker.sh
@@ -110,6 +127,18 @@ docker-compose down
 docker-compose up
 ```
 
+### Update from version 2 to version 3
+Version 3 is using Python 3.11 and PostgreSQL 15. If you are updating from an older version, that was using PostgreSQL 13, the automatic update will not work, as you will receive the following message when the PostgreSQL container starts:
+
+```
+db_1              | 2023-06-27 11:07:42.959 UTC [1] FATAL:  database files are incompatible with server
+db_1              | 2023-06-27 11:07:42.959 UTC [1] DETAIL:  The data directory was initialized by PostgreSQL version 13, which is not compatible with this version 15.2.
+```
+
+At this point there are two options: either edit the Docker Compose file and make use of the existing postgres:13 image, or otherwise you have to perform the migration from postgresql 13 to version 15. More notes on https://github.com/mediacms-io/mediacms/pull/749
+
+
+
 ## Configuration
 Checkout the configuration docs here.
 
@@ -144,9 +173,9 @@ The main container runs migrations, mediacms_web, celery_beat, celery_workers (c
  The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
 
 ### 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. 
+Before trying this out make sure the ip points to my_domain.com.
 
-With this method [this deployment](../docker-compose-letsencrypt.yaml) is used. 
+With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
 
 Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
 
@@ -176,15 +205,15 @@ The architecture below generalises all the deployment scenarios above, and provi
 ## 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` .
 
 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. 
+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 
+Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
 
 ```bash
 #systemctl restart mediacms
@@ -212,7 +241,7 @@ PORTAL_NAME = 'my awesome portal'
 
 By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are:
 
-- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails. 
+- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
 
 - **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
 
@@ -281,7 +310,7 @@ Make changes (True/False) to any of the following:
 
 ### 5.9 Show or hide the download option on a media
 
-Edit `templates/config/installation/features.html` and set 
+Edit `templates/config/installation/features.html` and set
 
 ```
 download: false
@@ -290,7 +319,7 @@ download: false
 ### 5.10 Automatically hide media upon being reported
 
 set a low number for variable `REPORTED_TIMES_THRESHOLD`
-eg 
+eg
 
 ```
 REPORTED_TIMES_THRESHOLD = 2
@@ -338,7 +367,7 @@ set value
 MEDIA_IS_REVIEWED = False
 ```
 
-any uploaded media now needs to be reviewed before it can appear to the listings. 
+any uploaded media now needs to be reviewed before it can appear to the listings.
 MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed
 
 ### 5.15 Specify maximum number of media for a playlist
@@ -353,7 +382,7 @@ MAX_MEDIA_PER_PLAYLIST = 14
 
 ### 5.16 Specify maximum size of a media that can be uploaded
 
-change `UPLOAD_MAX_SIZE`. 
+change `UPLOAD_MAX_SIZE`.
 
 default is 4GB
 
@@ -416,7 +445,7 @@ Global notifications that are implemented are controlled by the following option
 
 ```
 USERS_NOTIFICATIONS = {
-    'MEDIA_ADDED': True,    
+    'MEDIA_ADDED': True,
 }
 ```
 
@@ -459,17 +488,19 @@ to be written
 Through the admin section - http://your_installation/admin/
 
 ## 12. Video transcoding
-Add / remove resolutions and profiles through http://your_installation/admin/encodeprofile
+Add / remove resolutions and profiles by modifying the database table of `Encode profiles` through https://your_installation/admin/files/encodeprofile/
+
+For example, the `Active` state of any profile can be toggled to enable or disable it.
 
 ## 13. How To Add A Static Page To The Sidebar
 
-### 1. Create your html page in templates/cms/ 
+### 1. Create your html page in templates/cms/
 e.g. duplicate and rename about.html
 ```
 sudo cp templates/cms/about.html templates/cms/volunteer.html
 ```
 
-### 2. Create your css file in static/css/ 
+### 2. Create your css file in static/css/
 ```
 touch static/css/volunteer.css
 ```
@@ -533,24 +564,24 @@ urlpatterns = [
 
 ### 8. Add your page to the left sidebar
 To add a link to your page as a menu item in the left sidebar,
-add the following code after the last line in _commons.js   
+add the following code after the last line in _commons.js
 ```
 /* Checks that a given selector has loaded. */
 const checkElement = async selector => {
     while ( document.querySelector(selector) === null) {
       await new Promise( resolve =>  requestAnimationFrame(resolve) )
     }
-    return document.querySelector(selector); 
+    return document.querySelector(selector);
   };
 
 /* Checks that sidebar nav menu has loaded, then adds menu item. */
 checkElement('.nav-menu')
 .then((element) => {
-     (function(){    
-        var a = document.createElement('a');        
+     (function(){
+        var a = document.createElement('a');
         a.href = "/volunteer";
         a.title = "Volunteer";
-       
+
         var s = document.createElement('span');
         s.className = "menu-item-icon";
 
@@ -560,7 +591,7 @@ checkElement('.nav-menu')
 
         s.appendChild(icon);
         a.appendChild(s);
-    
+
         var linkText = document.createTextNode("Volunteer");
         var t = document.createElement('span');
 
@@ -572,14 +603,14 @@ checkElement('.nav-menu')
         listItem.appendChild(a);
 
         //if signed out use 3rd nav-menu
-        var elem = document.querySelector(".nav-menu:nth-child(3) nav ul"); 
+        var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
         var loc = elem.innerText;
         if (loc.includes("About")){
           elem.insertBefore(listItem, elem.children[2]);
         } else { //if signed in use 4th nav-menu
           elem = document.querySelector(".nav-menu:nth-child(4) nav ul");
           elem.insertBefore(listItem, elem.children[2]);
-        }       
+        }
     })();
 });
 ```
@@ -605,7 +636,7 @@ Instructions contributed by @alberto98fx
 
 2. Add the Gtag/Analytics script
 
-3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one: 
+3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
 
 ```
 <head>
@@ -616,7 +647,7 @@ Instructions contributed by @alberto98fx
         {% include "common/head-meta.html" %}
 
         {% block headermeta %}
-        
+
         <meta property="og:title" content="{{PORTAL_NAME}}">
         <meta property="og:type" content="website">
 
@@ -629,17 +660,17 @@ Instructions contributed by @alberto98fx
         {% block topimports %}{%endblock topimports %}
 
         {% include "config/index.html" %}
-      
+
     {% endblock head %}
 
 </head>
 ```
 
 4. Add  ``` {% include "tracking.html" %} ``` at the end inside the section ```<head>```
-  
-5. If you are using Docker and didn't  mount the entire dir you need to bind a new volume: 
+
+5. If you are using Docker and didn't  mount the entire dir you need to bind a new volume:
 ```
-  
+
     web:
     image: mediacms/mediacms:latest
     restart: unless-stopped
@@ -650,7 +681,7 @@ Instructions contributed by @alberto98fx
     volumes:
       - ./templates/root.html:/home/mediacms.io/mediacms/templates/root.html
       - ./templates/tracking.html://home/mediacms.io/mediacms/templates/tracking.html
-  
+
  ```
 
 ## 15. Debugging email issues
@@ -681,9 +712,53 @@ email = EmailMessage(
 email.send(fail_silently=False)
 ```
 
-You have the chance to either receive the email (in this case it will be sent to recipient@email.com) otherwise you will see the error. 
+You have the chance to either receive the email (in this case it will be sent to recipient@email.com) otherwise you will see the error.
 For example, while specifying wrong password for my Gmail account I get
 
 ```
 SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted. Learn more at\n5.7.8  https://support.google.com/mail/?p=BadCredentials d4sm12687785wrc.34 - gsmtp')
 ```
+
+## 16. Frequently Asked Questions
+Video is playing but preview thumbnails are not showing for large video files
+
+Chances are that the sprites file was not created correctly.
+The output of files.tasks.produce_sprite_from_video() function in this case is something like this
+
+```
+convert-im6.q16: width or height exceeds limit `/tmp/img001.jpg' @ error/cache.c/OpenPixelCache/3912.
+```
+
+Solution: edit file `/etc/ImageMagick-6/policy.xml` and set bigger values for the lines that contain width and height. For example
+
+```
+  <policy domain="resource" name="height" value="16000KP"/>
+  <policy domain="resource" name="width" value="16000KP"/>
+```
+
+Newly added video files now will be able to produce the sprites file needed for thumbnail previews. To re-run that task on existing videos, enter the Django shell
+
+
+```
+root@8433f923ccf5:/home/mediacms.io/mediacms# source  /home/mediacms.io/bin/activate
+root@8433f923ccf5:/home/mediacms.io/mediacms# python manage.py shell
+Python 3.8.14 (default, Sep 13 2022, 02:23:58)
+```
+
+and run
+
+```
+In [1]: from files.models import Media
+In [2]: from files.tasks import produce_sprite_from_video
+
+In [3]: for media in Media.objects.filter(media_type='video', sprites=''):
+   ...:     produce_sprite_from_video(media.friendly_token)
+```
+
+this will re-create the sprites for videos that the task failed.
+
+
+## 17. Cookie consent code
+On file `templates/components/header.html` you can find a simple cookie consent code. It is commented, so you have to remove the `{% comment %}` and `{% endcomment %}` lines in order to enable it. Or you can replace that part with your own code that handles cookie consent banners.
+
+![Simple Cookie Consent](images/cookie_consent.png)

BIN
docs/images/cookie_consent.png


+ 2 - 1
files/context_processors.py

@@ -6,9 +6,10 @@ from .methods import is_mediacms_editor, is_mediacms_manager
 def stuff(request):
     """Pass settings to the frontend"""
     ret = {}
-    ret["FRONTEND_HOST"] = request.build_absolute_uri('/')
+    ret["FRONTEND_HOST"] = request.build_absolute_uri('/').rstrip('/')
     ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
     ret["PORTAL_NAME"] = settings.PORTAL_NAME
+    ret["PORTAL_DESCRIPTION"] = settings.PORTAL_DESCRIPTION
     ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
     ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
     ret["CAN_REGISTER"] = settings.REGISTER_ALLOWED

+ 1 - 1
files/feeds.py

@@ -102,7 +102,7 @@ class SearchRSSFeed(Feed):
     description = "Latest Media RSS feed"
 
     def link(self, obj):
-        return f"/rss/search"
+        return "/rss/search"
 
     def get_object(self, request):
         category = request.GET.get("c", "")

+ 8 - 0
files/helpers.py

@@ -785,3 +785,11 @@ def clean_query(query):
         query = query.replace(char, "")
 
     return query.lower()
+
+
+def get_alphanumeric_only(string):
+    """Returns a query that contains only alphanumeric characters
+    This include characters other than the English alphabet too
+    """
+    string = "".join([char for char in string if char.isalnum()])
+    return string.lower()

+ 0 - 1
files/methods.py

@@ -305,7 +305,6 @@ def show_related_media_author(media, request, limit):
 
 
 def show_related_media_calculated(media, request, limit):
-
     """Return a list of related media based on ML recommendations
     A big todo!
     """

+ 0 - 1
files/migrations/0001_initial.py

@@ -10,7 +10,6 @@ import files.models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = []

+ 0 - 1
files/migrations/0002_auto_20201201_0712.py

@@ -8,7 +8,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = [

+ 0 - 1
files/migrations/0003_auto_20210927_1245.py

@@ -4,7 +4,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
         ('files', '0002_auto_20201201_0712'),
     ]

+ 24 - 11
files/models.py

@@ -1,3 +1,4 @@
+import glob
 import json
 import logging
 import os
@@ -15,7 +16,6 @@ from django.core.files import File
 from django.db import connection, models
 from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete
 from django.dispatch import receiver
-from django.template.defaultfilters import slugify
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.html import strip_tags
@@ -315,7 +315,6 @@ class Media(models.Model):
         self.__original_uploaded_poster = self.uploaded_poster
 
     def save(self, *args, **kwargs):
-
         if not self.title:
             self.title = self.media_file.path.split("/")[-1]
 
@@ -373,7 +372,6 @@ class Media(models.Model):
         # will run only when a poster is uploaded for the first time
         if self.uploaded_poster and self.uploaded_poster != self.__original_uploaded_poster:
             with open(self.uploaded_poster.path, "rb") as f:
-
                 # set this otherwise gets to infinite loop
                 self.__original_uploaded_poster = self.uploaded_poster
 
@@ -431,7 +429,6 @@ class Media(models.Model):
         Performs all related tasks, as check for media type,
         video duration, encode
         """
-
         self.set_media_type()
         if self.media_type == "video":
             self.set_thumbnail(force=True)
@@ -481,7 +478,10 @@ class Media(models.Model):
                 self.duration = int(round(float(ret.get("video_duration", 0))))
                 self.video_height = int(ret.get("video_height"))
                 if ret.get("video_info", {}).get("codec_name", {}) in ["mjpeg"]:
-                    audio_file_with_thumb = True
+                    # best guess that this is an audio file with a thumbnail
+                    # in other cases, it is not (eg it can be an AVI file)
+                    if ret.get("video_info", {}).get("avg_frame_rate", "") == '0/0':
+                        audio_file_with_thumb = True
 
             if ret.get("is_audio") or audio_file_with_thumb:
                 self.media_type = "audio"
@@ -581,9 +581,7 @@ class Media(models.Model):
 
         # attempt to break media file in chunks
         if self.duration > settings.CHUNKIZE_VIDEO_DURATION and chunkize:
-
             for profile in profiles:
-
                 if profile.extension == "gif":
                     profiles.remove(profile)
                     encoding = Encoding(media=self, profile=profile)
@@ -850,6 +848,7 @@ class Media(models.Model):
         """
 
         res = {}
+        valid_resolutions = [240, 360, 480, 720, 1080, 1440, 2160]
         if self.hls_file:
             if os.path.exists(self.hls_file):
                 hls_file = self.hls_file
@@ -861,11 +860,20 @@ class Media(models.Model):
                         uri = os.path.join(p, iframe_playlist.uri)
                         if os.path.exists(uri):
                             resolution = iframe_playlist.iframe_stream_info.resolution[1]
+                            # most probably video is vertical, getting the first value to
+                            # be the resolution
+                            if resolution not in valid_resolutions:
+                                resolution = iframe_playlist.iframe_stream_info.resolution[0]
+
                             res["{}_iframe".format(resolution)] = helpers.url_from_path(uri)
                     for playlist in m3u8_obj.playlists:
                         uri = os.path.join(p, playlist.uri)
                         if os.path.exists(uri):
                             resolution = playlist.stream_info.resolution[1]
+                            # same as above
+                            if resolution not in valid_resolutions:
+                                resolution = playlist.stream_info.resolution[0]
+
                             res["{}_playlist".format(resolution)] = helpers.url_from_path(uri)
         return res
 
@@ -1033,10 +1041,8 @@ class Tag(models.Model):
         return True
 
     def save(self, *args, **kwargs):
-        self.title = slugify(self.title[:99])
-        strip_text_items = ["title"]
-        for item in strip_text_items:
-            setattr(self, item, strip_tags(getattr(self, item, None)))
+        self.title = helpers.get_alphanumeric_only(self.title)
+        self.title = self.title[:99]
         super(Tag, self).save(*args, **kwargs)
 
     @property
@@ -1435,6 +1441,13 @@ def media_file_delete(sender, instance, **kwargs):
         helpers.rm_dir(p)
     instance.user.update_user_media()
 
+    # remove extra zombie thumbnails
+    if instance.thumbnail:
+        thumbnails_path = os.path.dirname(instance.thumbnail.path)
+        thumbnails = glob.glob(f'{thumbnails_path}/{instance.uid.hex}.*')
+        for thumbnail in thumbnails:
+            helpers.rm_file(thumbnail)
+
 
 @receiver(m2m_changed, sender=Media.category.through)
 def media_m2m(sender, instance, **kwargs):

+ 5 - 0
files/serializers.py

@@ -150,10 +150,14 @@ class SingleMediaSerializer(serializers.ModelSerializer):
 
 class MediaSearchSerializer(serializers.ModelSerializer):
     url = serializers.SerializerMethodField()
+    api_url = serializers.SerializerMethodField()
 
     def get_url(self, obj):
         return self.context["request"].build_absolute_uri(obj.get_absolute_url())
 
+    def get_api_url(self, obj):
+        return self.context["request"].build_absolute_uri(obj.get_absolute_url(api=True))
+
     class Meta:
         model = Media
         fields = (
@@ -167,6 +171,7 @@ class MediaSearchSerializer(serializers.ModelSerializer):
             "friendly_token",
             "duration",
             "url",
+            "api_url",
             "media_type",
             "preview_url",
             "categories_info",

+ 7 - 6
files/tasks.py

@@ -7,10 +7,11 @@ import tempfile
 from datetime import datetime, timedelta
 
 from celery import Task
-from celery.decorators import task
+from celery import shared_task as task
 from celery.exceptions import SoftTimeLimitExceeded
 from celery.signals import task_revoked
-from celery.task.control import revoke
+
+# from celery.task.control import revoke
 from celery.utils.log import get_task_logger
 from django.conf import settings
 from django.core.cache import cache
@@ -268,7 +269,6 @@ def encode_media(
     #    return False
 
     with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
-
         tf = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir)
         tfpass = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir)
         ffmpeg_commands = produce_ffmpeg_commands(
@@ -461,10 +461,11 @@ def check_running_states():
         if (now - encoding.update_date).seconds > settings.RUNNING_STATE_STALE:
             media = encoding.media
             profile = encoding.profile
-            task_id = encoding.task_id
+            # task_id = encoding.task_id
             # terminate task
-            if task_id:
-                revoke(task_id, terminate=True)
+            # TODO: not imported
+            # if task_id:
+            #    revoke(task_id, terminate=True)
             encoding.delete()
             media.encode(profiles=[profile])
             # TODO: allign with new code + chunksize...

+ 2 - 2
files/urls.py

@@ -1,7 +1,7 @@
 from django.conf import settings
-from django.conf.urls import include, re_path
+from django.conf.urls import include
 from django.conf.urls.static import static
-from django.urls import path
+from django.urls import path, re_path
 
 from . import management_views, views
 from .feeds import IndexRSSFeed, SearchRSSFeed

+ 5 - 5
files/views.py

@@ -1,6 +1,5 @@
 from datetime import datetime, timedelta
 
-from celery.task.control import revoke
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
@@ -9,7 +8,6 @@ from django.core.mail import EmailMessage
 from django.db.models import Q
 from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, render
-from django.template.defaultfilters import slugify
 from drf_yasg import openapi as openapi
 from drf_yasg.utils import swagger_auto_schema
 from rest_framework import permissions, status
@@ -30,7 +28,7 @@ from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor, user_allowed_to_u
 from users.models import User
 
 from .forms import ContactForm, MediaForm, SubtitleForm
-from .helpers import clean_query, produce_ffmpeg_commands
+from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands
 from .methods import (
     check_comment_for_mention,
     get_user_or_session,
@@ -182,7 +180,8 @@ def edit_media(request):
                 media.tags.remove(tag)
             if form.cleaned_data.get("new_tags"):
                 for tag in form.cleaned_data.get("new_tags").split(","):
-                    tag = slugify(tag)
+                    tag = get_alphanumeric_only(tag)
+                    tag = tag[:99]
                     if tag:
                         try:
                             tag = Tag.objects.get(title=tag)
@@ -1403,5 +1402,6 @@ class TaskDetail(APIView):
     permission_classes = (permissions.IsAdminUser,)
 
     def delete(self, request, uid, format=None):
-        revoke(uid, terminate=True)
+        # This is not imported!
+        # revoke(uid, terminate=True)
         return Response(status=status.HTTP_204_NO_CONTENT)

+ 32 - 26
frontend/src/static/js/components/comments/Comments.jsx

@@ -80,7 +80,7 @@ function CommentForm(props) {
 
   function onChangeWithMention(event, newValue, newPlainTextValue, mentions) {
     textareaRef.current.style.height = '';
-    
+
     setValue(newValue);
     setMadeChanges(true);
 
@@ -144,8 +144,8 @@ function CommentForm(props) {
         <UserThumbnail />
         <div className="form">
           <div className={'form-textarea-wrap' + (textareaFocused ? ' focused' : '')}>
-            { MediaCMS.features.media.actions.comment_mention ? 
-              <MentionsInput 
+            { MediaCMS.features.media.actions.comment_mention ?
+              <MentionsInput
                 inputRef={textareaRef}
                 className="form-textarea"
                 rows="1"
@@ -430,31 +430,37 @@ export default function CommentsList(props) {
 
   function onCommentsLoad() {
     const retrievedComments = [...MediaPageStore.get('media-comments')];
-    const video = videojs('vjs_video_3');
-    
-    if (MediaCMS.features.media.actions.timestampTimebar)
-    {
-      enableMarkers(video);
-    }
-
-    if (MediaCMS.features.media.actions.comment_mention === true)
-    {
-      retrievedComments.forEach(comment => {
-        comment.text = setMentions(comment.text);
-      });
-    }
 
-    video.one('loadedmetadata', () => {       
-      retrievedComments.forEach(comment => {          
-        comment.text = setTimestampAnchorsAndMarkers(comment.text, video);
-      });
-      
-      displayCommentsRelatedAlert();
-      setComments([...retrievedComments]);
-    });
     setComments([...retrievedComments]);
+
+    // TODO: this code is breaking, beed ti debug, until then removing the extra
+    // functionality related with video/timestamp/user mentions
+    //    const video = videojs('vjs_video_3');
+
+    // if (MediaCMS.features.media.actions.timestampTimebar)
+    //{
+    //  enableMarkers(video);
+    //}
+
+    //if (MediaCMS.features.media.actions.comment_mention === true)
+    //{
+    //  retrievedComments.forEach(comment => {
+    //    comment.text = setMentions(comment.text);
+    //  });
+    //}
+
+    // TODO: this code is breaking
+    // video.one('loadedmetadata', () => {
+    //  retrievedComments.forEach(comment => {
+    //    comment.text = setTimestampAnchorsAndMarkers(comment.text, video);
+    //  });
+
+    //  displayCommentsRelatedAlert();
+    //  setComments([...retrievedComments]);
+    //});
+    //setComments([...retrievedComments]);
   }
-  
+
   function setMentions(text)
   {
     let sanitizedComment = text.split('@(_').join("<a href=\"/user/");
@@ -464,7 +470,7 @@ export default function CommentsList(props) {
 
   function setTimestampAnchorsAndMarkers(text, videoPlayer)
   {
-    function wrapTimestampWithAnchor(match, string) 
+    function wrapTimestampWithAnchor(match, string)
     {
       let split = match.split(':'), s = 0, m = 1;
       let searchParameters = new URLSearchParams(window.location.search);

+ 1 - 1
frontend/src/static/js/components/video-player/VideoPlayer.jsx

@@ -192,7 +192,7 @@ export function VideoPlayer(props) {
       document.addEventListener('visibilitychange', initPlayer);
     }
 
-    player.player.one('loadedmetadata', () => {       
+    player && player.player.one('loadedmetadata', () => {
       const urlParams = new URLSearchParams(window.location.search);
       const paramT = Number(urlParams.get('t'));
       const timestamp = !isNaN(paramT) ? paramT : 0;

+ 3 - 1
frontend/src/static/js/utils/stores/MediaPageStore.js

@@ -194,7 +194,9 @@ class MediaPageStore extends EventEmitter {
     }
 
     this.loadPlaylists();
-    this.loadUsers();
+    if (MediaCMS.features.media.actions.comment_mention === true) {
+      this.loadUsers();
+    }
 
     if (this.mediacms_config.member.can.readComment) {
       this.loadComments();

+ 302 - 0
install-rhel.sh

@@ -0,0 +1,302 @@
+#!/bin/bash
+# should be run as root on a rhel8-like system
+
+function update_permissions
+{
+	# fix permissions of /srv/mediacms directory
+	chown -R nginx:root $1
+}
+
+echo "Welcome to the MediacMS installation!";
+
+if [ `id -u` -ne 0 ]; then
+	echo "Please run as root user"
+	exit
+fi
+
+
+while true; do
+    read -p "
+This script will attempt to perform a system update, install required dependencies, and configure PostgreSQL, NGINX, Redis and a few other utilities.
+It is expected to run on a new system **with no running instances of any these services**. Make sure you check the script before you continue. Then enter y or n
+" yn
+    case $yn in
+        [Yy]* ) echo "OK!"; break;;
+        [Nn]* ) echo "Have a great day"; exit;;
+        * ) echo "Please answer y or n.";;
+    esac
+done
+
+# update configuration files
+
+sed -i 's/\/home\/mediacms\.io\/mediacms\/Bento4-SDK-1-6-0-637\.x86_64-unknown-linux\/bin\/mp4hls/\/srv\/mediacms\/bento4\/bin\/mp4hls/g' cms/settings.py
+sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g' deploy/local_install/celery_*.service
+sed -i 's/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.io
+sed -i 's/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.service
+sed -i 's/\/home\/mediacms\.io\/mediacms/\/var\/log\/mediacms/g' deploy/local_install/mediacms_logrorate
+sed -i 's/www-data/nginx/g' deploy/local_install/nginx.conf
+sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io/\/srv\/mediacms\/virtualenv/g' deploy/local_install/uwsgi.ini
+
+osVersion=
+
+if [[ -f /etc/os-release ]]; then
+	osVersion=$(grep ^ID /etc/os-release)
+fi
+
+if [[ $osVersion == *"fedora"* ]] || [[ $osVersion == *"rhel"*  ]] || [[ $osVersion == *"centos"* ]] || [[ *"rocky"* ]]; then
+	dnf install -y epel-release https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm yum-utils
+	yum-config-manager --enable powertools
+	dnf install -y python3-virtualenv python39-devel redis postgresql postgresql-server nginx git gcc vim unzip ImageMagick python3-certbot-nginx certbot wget xz ffmpeg policycoreutils-devel cmake gcc gcc-c++ wget git bsdtar
+else
+    echo "unsupported or unknown os"
+    exit -1
+fi
+
+# fix permissions of /srv/mediacms directory
+update_permissions /srv/mediacms/
+
+read -p "Enter portal URL, or press enter for localhost : " FRONTEND_HOST
+read -p "Enter portal name, or press enter for 'MediaCMS : " PORTAL_NAME
+
+[ -z "$PORTAL_NAME" ] && PORTAL_NAME='MediaCMS'
+[ -z "$FRONTEND_HOST" ] && FRONTEND_HOST='localhost'
+
+echo "Configuring postgres"
+if [ ! command -v postgresql-setup > /dev/null 2>&1 ]; then
+        echo "Something went wrong, the command 'postgresql-setup' was not found in the system path."
+        exit -1
+fi
+
+postgresql-setup --initdb
+
+# set authentication method for mediacms user to scram-sha-256
+sed -i 's/.*password_encryption.*/password_encryption = scram-sha-256/' /var/lib/pgsql/data/postgresql.conf
+sed -i '/# IPv4 local connections:/a host\tmediacms\tmediacms\t127.0.0.1/32\tscram-sha-256' /var/lib/pgsql/data/pg_hba.conf
+
+systemctl enable postgresql.service --now
+
+su -c "psql -c \"CREATE DATABASE mediacms\"" postgres
+su -c "psql -c \"CREATE USER mediacms WITH ENCRYPTED PASSWORD 'mediacms'\"" postgres
+su -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE mediacms TO mediacms\"" postgres
+
+echo 'Creating python virtualenv on /srv/mediacms/virtualenv/'
+
+mkdir /srv/mediacms/virtualenv/
+cd /srv/mediacms/virtualenv/
+virtualenv . --python=python3
+source  /srv/mediacms/virtualenv/bin/activate
+cd /srv/mediacms/
+pip install -r requirements.txt
+
+systemctl enable redis.service --now
+
+SECRET_KEY=`python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`
+
+# remove http or https prefix
+FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/http:\/\///g'`
+FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/https:\/\///g'`
+
+FRONTEND_HOST_HTTP_PREFIX='http://'$FRONTEND_HOST
+
+echo 'FRONTEND_HOST='\'"$FRONTEND_HOST_HTTP_PREFIX"\' >> cms/local_settings.py
+echo 'PORTAL_NAME='\'"$PORTAL_NAME"\' >> cms/local_settings.py
+echo "SSL_FRONTEND_HOST = FRONTEND_HOST.replace('http', 'https')" >> cms/local_settings.py
+
+echo 'SECRET_KEY='\'"$SECRET_KEY"\' >> cms/local_settings.py
+echo "LOCAL_INSTALL = True" >> cms/local_settings.py
+
+mkdir /var/log/mediacms/
+mkdir pids
+
+update_permissions /var/log/mediacms/
+
+python manage.py migrate
+python manage.py loaddata fixtures/encoding_profiles.json
+python manage.py loaddata fixtures/categories.json
+python manage.py collectstatic --noinput
+
+ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"`
+echo "from users.models import User; User.objects.create_superuser('admin', 'admin@example.com', '$ADMIN_PASS')" | python manage.py shell
+
+echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
+
+update_permissions /srv/mediacms/
+
+cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
+cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
+cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
+cp deploy/local_install/mediacms.service /etc/systemd/system/mediacms.service
+
+mkdir -p /etc/letsencrypt/live/$FRONTEND_HOST
+mkdir -p /etc/nginx/sites-enabled
+mkdir -p /etc/nginx/sites-available
+mkdir -p /etc/nginx/dhparams/
+rm -rf /etc/nginx/conf.d/default.conf
+rm -rf /etc/nginx/sites-enabled/default
+cp deploy/local_install/mediacms.io_fullchain.pem /etc/letsencrypt/live/$FRONTEND_HOST/fullchain.pem
+cp deploy/local_install/mediacms.io_privkey.pem /etc/letsencrypt/live/$FRONTEND_HOST/privkey.pem
+cp deploy/local_install/mediacms.io /etc/nginx/sites-available/mediacms.io
+ln -s /etc/nginx/sites-available/mediacms.io /etc/nginx/sites-enabled/mediacms.io
+cp deploy/local_install/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
+cp deploy/local_install/nginx.conf /etc/nginx/
+
+# attempt to get a valid certificate for specified domain
+while true ; do
+        echo "Would you like to run [c]ertbot, or [s]kip?"
+        read -p " : " certbotConfig
+
+        case $certbotConfig in
+        [cC*] )
+		if [ "$FRONTEND_HOST" != "localhost" ]; then
+			systemctl start
+			echo 'attempt to get a valid certificate for specified url $FRONTEND_HOST'
+			certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
+			certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
+			# unfortunately for some reason it needs to be run two times in order to create the entries
+			# and directory structure!!!
+			systemctl stop nginx
+
+			# Generate individual DH params
+			openssl dhparam -out /etc/nginx/dhparams/dhparams.pem 4096
+		fi
+
+                break
+                ;;
+        [sS*] )
+		echo "will not call certbot utility to update ssl certificate for url 'localhost', using default ssl certificate"
+		cp deploy/local_install/dhparams.pem /etc/nginx/dhparams/dhparams.pem
+
+                break
+                ;;
+        * )
+                echo "Unknown option: $certbotConfig"
+                ;;
+        esac
+done
+
+# configure bento4 utility installation, for HLS
+while true ; do
+	echo "Configuring Bento4"
+	echo "Would you like to [d]ownload a pre-compiled bento4 binary, or [b]uild it now?"
+	read -p "b/d : " bentoConfig
+
+	case $bentoConfig in
+	[bB*] )
+		echo "Building bento4 from source"
+		git clone -b v1.6.0-640 https://github.com/axiomatic-systems/Bento4 /srv/mediacms/bento4
+		cd /srv/mediacms/bento4/
+		mkdir bin
+		cd /srv/mediacms/bento4/bin/
+		cmake -DCMAKE_BUILD_TYPE=Release ..
+		make -j$(nproc)
+
+		chmod +x ../Source/Python/utils/mp4-hls.py
+
+		echo -e '#!/bin/bash' >> mp4hls
+		echo -e 'BASEDIR=$(pwd)' >> mp4hls
+		echo -e 'exec python3 "$BASEDIR/../Source/Python/utils/mp4-hls.py"' >> mp4hls
+
+		chmod +x mp4hls
+
+		break
+		;;
+	[dD*] )
+		cd /srv/mediacms/
+		wget http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
+		bsdtar -xf Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -s '/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bento4/'
+
+		break
+		;;
+	* )
+		echo "Unknown option: $bentoConfig"
+		;;
+	esac
+done
+
+mkdir /srv/mediacms/media_files/hls
+
+# update permissions
+
+update_permissions /srv/mediacms/
+
+# configure selinux
+
+while true ; do
+        echo "Configuring SELinux"
+        echo "Would you like to [d]isable SELinux until next reboot, [c]onfigure our SELinux module, or [s]kip and not do any SELinux confgiguration?"
+        read -p "d/c/s : " seConfig
+
+        case $seConfig in
+        [Dd]* )
+                echo "Disabling SELinux until next reboot"
+                break
+                ;;
+        [Cc]* )
+                echo "Configuring custom mediacms selinux module"
+
+		semanage fcontext -a -t bin_t /srv/mediacms/virtualenv/bin/
+		semanage fcontext -a -t httpd_sys_content_t "/srv/mediacms(/.*)?"
+		restorecon -FRv /srv/mediacms/
+
+		sebools=(httpd_can_network_connect httpd_graceful_shutdown httpd_can_network_relay nis_enabled httpd_setrlimit domain_can_mmap_files)
+
+		for bool in "${sebools[@]}"
+		do
+			setsebool -P $bool 1
+		done
+
+		cd /srv/mediacms/deploy/local_install/
+		make -f /usr/share/selinux/devel/Makefile selinux-mediacms.pp
+		semodule -i selinux-mediacms.pp
+
+                break
+                ;;
+        [Ss]* )
+                echo "Skipping SELinux configuration"
+                break
+                ;;
+        * )
+                echo "Unknown option: $seConfig"
+                ;;
+        esac
+done
+
+# configure firewall
+if command -v firewall-cmd > /dev/null 2>&1 ; then
+	while true ; do
+	        echo "Configuring firewall"
+	        echo "Would you like to configure http, https, or skip and not do any firewall configuration?"
+	        read -p "http/https/skip : " fwConfig
+
+		case $fwConfig in
+	        http )
+	                echo "Opening port 80 until next reboot"
+			firewall-cmd --add-port=80/tcp
+	                break
+	                ;;
+	        https )
+			echo "Opening port 443 permanently"
+			firewall-cmd --add-port=443/tcp --permanent
+			firewall-cmd --reload
+	                break
+	                ;;
+	        skip )
+	                echo "Skipping firewall configuration"
+	                break
+	                ;;
+	        * )
+	                echo "Unknown option: $fwConfig"
+	                ;;
+	        esac
+	done
+
+fi
+
+systemctl daemon-reload
+systemctl start celery_long.service
+systemctl start celery_short.service
+systemctl start celery_beat.service
+systemctl start mediacms.service
+systemctl start nginx.service
+
+echo 'MediaCMS installation completed, open browser on http://'"$FRONTEND_HOST"' and login with user admin and password '"$ADMIN_PASS"''

+ 3 - 3
install.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
-# should be run as root and only on Ubuntu 18/20, Debian 10/11 (Buster/Bullseye) versions!
+# should be run as root and only on Ubuntu 20/22, Debian 10/11 (Buster/Bullseye) versions!
 echo "Welcome to the MediacMS installation!";
 
 if [ `id -u` -ne 0 ]
@@ -22,11 +22,11 @@ done
 
 
 osVersion=$(lsb_release -d)
-if [[ $osVersion == *"Ubuntu 20"* ]] || [[ $osVersion == *"Ubuntu 18"* ]] || [[ $osVersion == *"buster"* ]] || [[ $osVersion == *"bullseye"* ]]; then
+if [[ $osVersion == *"Ubuntu 20"* ]] || [[ $osVersion == *"Ubuntu 22"* ]] || [[ $osVersion == *"buster"* ]] || [[ $osVersion == *"bullseye"* ]]; then
     echo 'Performing system update and dependency installation, this will take a few minutes'
     apt-get update && apt-get -y upgrade && apt-get install python3-venv python3-dev virtualenv redis-server postgresql nginx git gcc vim unzip imagemagick python3-certbot-nginx certbot wget xz-utils -y
 else
-    echo "This script is tested for Ubuntu 18 and 20 versions only, if you want to try MediaCMS on another system you have to perform the manual installation"
+    echo "This script is tested for Ubuntu 20/22 versions only, if you want to try MediaCMS on another system you have to perform the manual installation"
     exit
 fi
 

+ 21 - 34
requirements.txt

@@ -1,35 +1,22 @@
-Django==3.1.12
-djangorestframework==3.12.2
-django-allauth==0.44.0
-
-psycopg2-binary==2.8.6
-
-uwsgi==2.0.19.1
-
-django-redis==4.12.1
-celery==4.4.7
-
-drf-yasg==1.20.0
-
-Pillow==8.2.0
-django-imagekit
-
-markdown
-django-filter
-
-filetype
-django-mptt
-
-django-crispy-forms
-
-requests==2.25.0
-
-django-celery-email
-m3u8
-
-django-ckeditor
-django-debug-toolbar==3.2.4
-
-django-login-required-middleware==0.6.1
-
+Django==4.2.2
+djangorestframework==3.14.0
+django-allauth==0.54.0
+psycopg==3.1.9
+uwsgi==2.0.21
+django-redis==5.3.0
+celery==5.3.1
+drf-yasg==1.21.6
+Pillow==9.5.0
+django-imagekit==4.1.0
+markdown==3.4.3
+django-filter==23.2
+filetype==1.2.0
+django-mptt==0.14.0
+django-crispy-forms==1.13.0
+requests==2.31.0
+django-celery-email==3.0.0
+m3u8==3.5.0
+django-ckeditor==6.6.1
+django-debug-toolbar==4.1.0
+django-login-required-middleware==0.9.0
 webvtt-py==0.4.6

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/js/_commons.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/js/embed.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/js/media.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/js/playlist.js


+ 2 - 0
templates/cms/index.html

@@ -5,6 +5,8 @@
 
 <link rel="canonical" href="{{FRONTEND_HOST}}{{media_object.get_absolute_url}}">
 
+<meta name="description" content="{{PORTAL_DESCRIPTION}}">
+
 <meta property="og:title" content="{{PORTAL_NAME}}">
 <meta property="og:url" content="{{FRONTEND_HOST}}">
 <meta property="og:type" content="website">

+ 20 - 1
templates/components/header.html

@@ -1 +1,20 @@
-<div id="app-header"></div>
+<div id="app-header"></div>
+
+{% comment %}
+Uncomment, or replace with your own cookie consent code.
+
+<script src="https://cdn.websitepolicies.io/lib/cookieconsent/cookieconsent.min.js" defer></script>
+<script>
+    window.addEventListener("load", function () {
+        window.wpcc.init({
+            border: "normal",
+            corners: "normal",
+            colors: { popup: { background: "#222222", text: "#ffffff", border: "#FF8000" }, button: { background: "#FF8000", text: "#000000" } },
+            position: "top-right",
+            content: { message: "Hi there, we are using cookies on this website. We don't use tracking or analytics here, just the essentials for the Website to work.\n", button: "Understood! Yum!", link: "Click here to learn more." },
+        });
+    });
+</script>
+
+
+{% endcomment %}

+ 1 - 1
templates/config/installation/site.html

@@ -2,7 +2,7 @@ MediaCMS.site = {
  	id: 'mediacms',
     title: "{{PORTAL_NAME}}",
     url: '{{FRONTEND_HOST}}',
-    api: '{{FRONTEND_HOST}}api/v1',
+    api: '{{FRONTEND_HOST}}/api/v1',
     theme: {
     	mode: '{{DEFAULT_THEME}}',   
 		switch: {

+ 1 - 1
uploader/urls.py

@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-from django.conf.urls import re_path
+from django.urls import re_path
 
 from . import views
 

+ 0 - 1
users/migrations/0001_initial.py

@@ -10,7 +10,6 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = [

+ 1 - 2
users/urls.py

@@ -1,5 +1,4 @@
-from django.conf.urls import re_path
-from django.urls import path
+from django.urls import path, re_path
 
 from . import views
 

+ 0 - 1
users/views.py

@@ -181,7 +181,6 @@ Sender email: %s\n
 
 
 class UserList(APIView):
-
     permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
     parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
 

+ 1 - 1
version.py

@@ -1 +1 @@
-1.6
+VERSION = "3.0.0"

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است