Skip to content

Commit

Permalink
Merge pull request #1044 from Integreat/enhancement/production-setup
Browse files Browse the repository at this point in the history
Improve production setup
  • Loading branch information
timobrembeck committed Dec 8, 2021
2 parents 935a680 + a67dcc3 commit f51ba45
Show file tree
Hide file tree
Showing 94 changed files with 1,316 additions and 1,221 deletions.
9 changes: 6 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,11 @@ jobs:
- attach_workspace:
at: .
- run:
name: Create MANIFEST.in file
command: echo -e "graft integreat_cms\nprune integreat_cms/static/src" > MANIFEST.in
name: Fix dependency versions
command: ./dev-tools/fix_dependencies.sh
- run:
name: Use alternative README.md file
command: mv integreat_cms/README.md .
- run:
name: Build integreat-cms package
command: python setup.py sdist bdist_wheel
Expand All @@ -180,7 +183,7 @@ jobs:
at: .
- run:
name: Generate documentation
command: pipenv run ./dev-tools/generate_documentation.sh
command: ./dev-tools/generate_documentation.sh
- persist_to_workspace:
root: .
paths:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ UNRELEASED
* Fix page permissions
* Show recurrence in event list
* Only show upcoming events per default
* Allow configuration via /etc/integreat-cms.ini
* Fix dependency versions for production setup


2021.11.0-beta
Expand Down
24 changes: 24 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Include all non-python files in the integreat_cms directory
graft integreat_cms

# Exclude the static source files since we only need the compiled files in integreat_cms/static/dist
prune integreat_cms/static/src

# Exclude development settings
exclude integreat_cms/core/*_settings.py

# Exclude test data
exclude integreat_cms/cms/fixtures/test_*.json

# Exclude byte code cache files
global-exclude *.py[co]
global-exclude __pycache__

# Exclude existing log file
exclude integreat_cms/integreat-cms.log

# Exclude pyproject.toml
exclude pyproject.toml

# Exclude source of translation file
exclude integreat_cms/locale/de/LC_MESSAGES/django.po
1 change: 0 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ black = "==20.8b1"
bumpver = "*"
coverage = "*"
django-coverage-plugin = "*"
django-debug-toolbar = "*"
ipython = "*"
packaging = "*"
pre-commit = "*"
Expand Down
560 changes: 270 additions & 290 deletions Pipfile.lock

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions dev-tools/_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ function require_installed {
echo "✔ Integreat CMS is installed" | print_success
INTEGREAT_CMS_INSTALLED=1
export INTEGREAT_CMS_INSTALLED
# Check if script is running in CircleCI context and set DEBUG=True if not
if [[ -z "$CIRCLECI" ]]; then
# Set debug mode for
INTEGREAT_CMS_DEBUG=1
export INTEGREAT_CMS_DEBUG
fi
fi
}

Expand Down Expand Up @@ -317,13 +323,13 @@ function configure_redis_cache {
echo "Checking if local Redis server is running..." | print_info
if nc -z localhost 6379; then
# Enable redis cache if redis server is running
export DJANGO_REDIS_CACHE=1
export INTEGREAT_CMS_REDIS_CACHE=1
# Check if enhanced connection via unix socket is available (write the location into $REDIS_SOCKET_LOCATION)
if [[ -f "$REDIS_SOCKET_LOCATION" ]]; then
# Set location of redis unix socket
DJANGO_REDIS_UNIX_SOCKET=$(cat "$REDIS_SOCKET_LOCATION")
export DJANGO_REDIS_UNIX_SOCKET
echo "✔ Running Redis server on socket $DJANGO_REDIS_UNIX_SOCKET detected. Caching enabled." | print_success
INTEGREAT_CMS_REDIS_UNIX_SOCKET=$(cat "$REDIS_SOCKET_LOCATION")
export INTEGREAT_CMS_REDIS_UNIX_SOCKET
echo "✔ Running Redis server on socket $INTEGREAT_CMS_REDIS_UNIX_SOCKET detected. Caching enabled." | print_success
else
echo "✔ Running Redis server on port 6379 detected. Caching enabled." | print_success
fi
Expand Down
23 changes: 23 additions & 0 deletions dev-tools/fix_dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

# This script fixes the dependencies in setup.cfg according to the Pipenv lock file.

# Import utility functions
# shellcheck source=./dev-tools/_functions.sh
source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh"

echo "Extracting dependency versions..." | print_info
# Use pipenv to extract the exact dependency versions from the Pipfile.lock and remove all lines with start with a space, # or -
DEPENDENCY_VERSIONS=$(pipenv lock -r | sed "/^[#-]/d")
echo "${DEPENDENCY_VERSIONS}"

# Use python to write the dependencies into the setup.cfg config file
python3 << EOF
import configparser
setup_cfg = configparser.ConfigParser()
setup_cfg.read("${BASE_DIR}/setup.cfg")
setup_cfg["options"]["install_requires"] = """${DEPENDENCY_VERSIONS}"""
with open("${BASE_DIR}/setup.cfg", "w") as setup_cfg_file:
setup_cfg.write(setup_cfg_file)
EOF
echo -e "✔ Fixed dependency versions in setup.cfg" | print_success
2 changes: 1 addition & 1 deletion dev-tools/loadtestdata.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/_functions.sh"
require_installed
require_database

export LINKCHECK_DISABLE_LISTENERS=1
export INTEGREAT_CMS_LINKCHECK_DISABLE_LISTENERS=1

deescalate_privileges pipenv run integreat-cms-cli loaddata "${PACKAGE_DIR}/cms/fixtures/test_data.json"
echo "✔ Imported test data" | print_success
27 changes: 8 additions & 19 deletions example-configs/apache2-integreat-vhost.conf
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,25 @@
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

<Directory /opt/integreat-cms/>
<Files wsgi.py>
Require all granted
</Files>
</Directory>

Alias /media/ /var/www/cms/media/
Alias /static/ /var/www/cms/static/
Alias /xliff/ /var/www/cms/xliff/download/
Alias /media/ /var/www/integreat-cms/media/
Alias /static/ /var/www/integreat-cms/static/
Alias /xliff/ /var/www/integreat-cms/xliff/download/

# Configure the number of Django processes
WSGIDaemonProcess example.com processes=8 threads=1 python-home=/opt/integreat-cms/.venv/ python-path=/opt/integreat-cms
WSGIDaemonProcess example.com processes=8 threads=1 python-home=/opt/integreat-cms/.venv/
WSGIProcessGroup example.com
WSGIScriptAlias / /opt/integreat-cms/integreat_cms/core/wsgi.py

# Integreat CMS Settings
SetEnv DJANGO_SECRET_KEY SECRET
SetEnv DJANGO_DEBUG False
SetEnv DJANGO_BASE_URL "https://example.com"
SetEnv DJANGO_WEBAPP_URL "https://integreat.app"
SetEnv DJANGO_DB_HOST localhost
SetEnv DJANGO_DB_PORT 5432
SetEnv DJANGO_DB_USER integreat
SetEnv DJANGO_DB_NAME integreat
SetEnv DJANGO_DB_PASSWORD SECRET
SetEnv DJANGO_STATIC_ROOT /var/www/cms/static
SetEnv DJANGO_MEDIA_ROOT /var/www/cms/media
SetEnv DJANGO_XLIFF_ROOT /var/www/cms/xliff
WSGIScriptAlias / /opt/integreat-cms/wsgi.py

RewriteEngine On
# Ping response for app online check
RewriteRule "^ping$" - [R=204]

CustomLog /var/log/apache2/access.log vhost_combined
</VirtualHost>
88 changes: 88 additions & 0 deletions example-configs/integreat-cms.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# This is the configuration file for integreat-cms.
# It should be located at "/etc/integreat-cms.ini".
# If you want to place the file at a different location, pass the
# absolute path via the environment variable "INTEGREAT_CMS_CONFIG".
# All these settings can also be configured via environment variables
# with the prefix "INTEGREAT_CMS_", e.g. "INTEGREAT_CMS_SECRET_KEY".
# The sections are just for better readability, they can be renamed
# or rearranged as desired.

[base]
# Whether to debug mode is enabled [optional, defaults to False]
DEBUG = False
# The timezone of the server [optional, defaults to "Europe/Berlin"]
CURRENT_TIME_ZONE = Europe/Berlin

[secrets]
# The secret key for this installation [required]
SECRET_KEY = <your-secret-key>
# If you want to send push notification to your app users, set your firebase key here [optional, defaults to None]
FCM_KEY = <your-firebase-key>

[logging]
# The path to your log file [optional, defaults to "integreat-cms.log" in the application directory]
LOGFILE = /var/integreat-cms.log
# Logging level of integreat-cms [optional, defaults to "INFO"]
LOG_LEVEL = INFO
# Logging level of all dependencies [optional, defaults to "WARN"]
DEPS_LOG_LEVEL = WARN

[urls]
# The url to your installation [optional, defaults to "http://localhost:8000"]
BASE_URL = https://cms.integreat-app.de
# The url to the web app [optional, defaults to "https://integreat.app"]
WEBAPP_URL = https://integreat.app
# The url to the statistics server [optional, defaults to "https://statistics.integreat-app.de"]
MATOMO_URL = https://statistics.integreat-app.de
# The url to the blog website [optional, defaults to "https://integreat-app.de"]
WEBSITE_URL = https://integreat-app.de
# The url to the wiki [optional, defaults to "https://wiki.integreat-app.de"]
WIKI_URL = https://wiki.integreat-app.de

[static-files]
# The directory for static files [required]
STATIC_ROOT = /var/www/integreat-cms/static
# The directory for media files [optional, defaults to "media" in the application directory]
MEDIA_ROOT = /var/www/integreat-cms/media
# The directory for xliff files [optional, defaults to "xliff" in the application directory]
XLIFF_ROOT = /var/www/integreat-cms/xliff
# Enable the possibility to upload legacy file formats [optional, defaults to False]
LEGACY_FILE_UPLOAD = False

[database]
# Database name [optional, defaults to "integreat"]
DB_NAME = <your-database>
# Database username [optional, defaults to "integreat"]
DB_USER = <your-username>
# Database password [required]
DB_PASSWORD = <your-password>
# Database host [optional, defaults to "localhost"]
DB_HOST = <database-host>
# Database port [optional, defaults to 5432]
DB_PORT = <port>

[cache]
# Whether redis database should be used for caching [optional, defaults to False]
REDIS_CACHE = True
# Set this if you want to connect to redis via socket [optional, defaults to None]
REDIS_UNIX_SOCKET = /var/run/redis/redis-server.sock

[email]
# Sender email [optional, defaults to "keineantwort@integreat-app.de"]
SERVER_EMAIL = <your-email-address>
# SMTP server [optional, defaults to localhost]
EMAIL_HOST = <your-smtp-server>
# SMTP username [required]
EMAIL_HOST_USER = <your-username>
# SMTP password [required]
EMAIL_HOST_PASSWORD = <your-password>
# SMTP port [optional, defaults to 25]
EMAIL_PORT = <your-port>

[linkcheck]
# Whether link check should be disabled [optional, defaults to False]
LINKCHECK_DISABLE_LISTENERS = False

[xliff]
# Which XLIFF version to use for export [optional, defaults to "xliff-1.2"]
XLIFF_EXPORT_VERSION = xliff-1.2
10 changes: 5 additions & 5 deletions integreat_cms/api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,19 @@ def json_response(function):

@wraps(function)
def wrap(request, *args, **kwargs):
"""
r"""
The inner function for this decorator.
It tries to execute the decorated view function and returns the unaltered result with the exception of a
:class:`~django.http.Http404` error, which is converted into JSON format.
:param request: Django request
:type request: ~django.http.HttpRequest
:param args: The supplied arguments
:type args: list
:param \*args: The supplied arguments
:type \*args: list
:param kwargs: The supplied kwargs
:type kwargs: dict
:param \**kwargs: The supplied kwargs
:type \**kwargs: dict
:return: The response of the given function or an 404 :class:`~django.http.JsonResponse`
:rtype: ~django.http.JsonResponse
Expand Down
36 changes: 9 additions & 27 deletions integreat_cms/cms/apps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import logging
import sys

from django.conf import settings
from django.apps import AppConfig
from django.contrib.auth.signals import (
user_logged_in,
Expand All @@ -25,30 +23,14 @@ class CmsConfig(AppConfig):

name = "integreat_cms.cms"

def ready(self):
"""
This function gets executed exactly once each time the cms starts. We use it to check wether the secret key was
not changed in production mode and show an error message if this is the case.
See :meth:`django.apps.AppConfig.ready` for more information.
"""
if (
settings.SECRET_KEY == "-!v282$zj815_q@htaxcubylo)(l%a+k*-xi78hw*#s2@i86@_"
and not settings.DEBUG
):
logger.critical(
"You are running the Integreat CMS in production mode. Change the SECRET_KEY in the settings.py!"
)
sys.exit(1)


authlog = logging.getLogger("auth")


# pylint: disable=unused-argument
@receiver(user_logged_in)
def user_logged_in_callback(sender, request, user, **kwargs):
"""
r"""
Log a successful login event
:param sender: The class of the user that just logged in.
Expand All @@ -60,8 +42,8 @@ def user_logged_in_callback(sender, request, user, **kwargs):
:param user: The user instance that just logged in.
:type user: ~django.contrib.auth.models.User
:param kwargs: The supplied keyword arguments
:type kwargs: dict
:param \**kwargs: The supplied keyword arguments
:type \**kwargs: dict
"""
ip = request.META.get("REMOTE_ADDR")
authlog.info("login user=%s, ip=%s", user, ip)
Expand All @@ -70,7 +52,7 @@ def user_logged_in_callback(sender, request, user, **kwargs):
# pylint: disable=unused-argument
@receiver(user_logged_out)
def user_logged_out_callback(sender, request, user, **kwargs):
"""
r"""
Log a logout event
:param sender: The class of the user that just logged out or ``None`` if the user was not authenticated.
Expand All @@ -82,8 +64,8 @@ def user_logged_out_callback(sender, request, user, **kwargs):
:param user: The user instance that just logged out or ``None`` if the user was not authenticated.
:type user: ~django.contrib.auth.models.User
:param kwargs: The supplied keyword arguments
:type kwargs: dict
:param \**kwargs: The supplied keyword arguments
:type \**kwargs: dict
"""
ip = request.META.get("REMOTE_ADDR")
authlog.info("logout user=%s, ip=%s", user, ip)
Expand All @@ -92,7 +74,7 @@ def user_logged_out_callback(sender, request, user, **kwargs):
# pylint: disable=unused-argument
@receiver(user_login_failed)
def user_login_failed_callback(sender, credentials, request, **kwargs):
"""
r"""
Log a failed login event
:param sender: The name of the module used for authentication.
Expand All @@ -106,8 +88,8 @@ def user_login_failed_callback(sender, credentials, request, **kwargs):
:param request: The current request
:type request: ~django.http.HttpRequest
:param kwargs: The supplied keyword arguments
:type kwargs: dict
:param \**kwargs: The supplied keyword arguments
:type \**kwargs: dict
"""
ip = request.META.get("REMOTE_ADDR")
authlog.warning("login failed user=%s, ip=%s", credentials["username"], ip)

0 comments on commit f51ba45

Please sign in to comment.