summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbuckE <buckE@disroot.org>2020-05-01 01:15:56 +0000
committerbuckE <buckE@disroot.org>2020-05-01 01:15:56 +0000
commitb4b2db62b3d236f6a35f8eb882c93de50ca0e046 (patch)
treeacf5feaf8c16156d06e9ecdd720fc4189ce7a385
parentf56e22d36f7dab3d4342650c712053fafc8459d1 (diff)
downloaddeployment-b4b2db62b3d236f6a35f8eb882c93de50ca0e046.tar.gz
deployment-b4b2db62b3d236f6a35f8eb882c93de50ca0e046.tar.bz2
deployment-b4b2db62b3d236f6a35f8eb882c93de50ca0e046.zip
weblate setup docs and supporting files
-rw-r--r--weblate/SETUP.md121
-rw-r--r--weblate/settings.py865
-rw-r--r--weblate/systemd-nonpriv/README3
-rw-r--r--weblate/systemd-nonpriv/celery-weblate.service24
-rw-r--r--weblate/systemd-nonpriv/uwsgi-weblate.service17
5 files changed, 1030 insertions, 0 deletions
diff --git a/weblate/SETUP.md b/weblate/SETUP.md
new file mode 100644
index 0000000..7a698b4
--- /dev/null
+++ b/weblate/SETUP.md
@@ -0,0 +1,121 @@
+## Weblate.taler.net Setup Notes
+
+### The purpose of this is to document how the weblate.taler.net service was built, in the event that it needs to be repaired, changed, reproduced, etc.
+
+Always refer to https://docs.weblate.org/ if necessary, or just for a good laugh.
+
+### Upgrading
+
+See latest recommendations:
+https://docs.weblate.org/en/latest/admin/upgrade.html?highlight=upgrade#generic-upgrade-instructions
+
+### Basic structure:
+
+This weblate instance runs as a python3 virtualenv under non-priv `/home/weblate`.
+
+The generic instructions used to build the site are here, and were followed closely:
+https://docs.weblate.org/en/latest/admin/install/venv-debian.html
+
+#### Notes on general installation:
+
+* settings document is in `~/weblate-env/lib/python3.8/site-packages/weblate/settings.py` (see symlink in ~)
+* "data directory" or "DATA_DIR" is `~/weblate-env/lib/python3.8/site-packages/data/` (see symlink to `DATA_DIR/` in ~)
+* virtualenv is invoked anytime with `. ~/weblate-env/bin/activate`. This activates the weblate path to access weblate binaries without full paths.
+
+### Configuration
+
+#### `/home/weblate` shell account
+
+ non-priv user with access via SSH key
+ celery and uwsgi services run under this user (see *systemd* below)
+
+#### postgres
+
+ configured for `localhost` access only (ie - `$ psql`)
+ no password
+ Unix `weblate` user has full control over psql 'weblate' user
+ Must manually create 'weblate' database
+ DB Credentials added to `settings.py` "DATABASES" section
+
+#### e-mail
+
+ weblate@taler.net
+ * no password
+ * available via localhost using SMTP
+ * info added to `settings.py` in Django format:
+ `EMAIL_HOST_USER = 'weblate'`
+ (`EMAIL_HOST` and `EMAIL_PORT` not required re: defaults to localhost and 25)
+ * Test E-mail from `/home/weblate`:
+
+ ```
+ # telnet localhost 25
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ 220 gv.taler.net ESMTP Postfix
+ HELO localhost
+ 250 gv.taler.net
+ MAIL From: weblate@taler.net
+ 250 2.1.0 Ok
+ RCPT To: weblate@gnunet.org
+ 250 2.1.5 Ok
+ DATA
+ 354 End data with <CR><LF>.<CR><LF>
+ hello
+ .
+ 250 2.0.0 Ok: queued as xxxxx
+ quit
+ 221 2.0.0 Bye
+ Connection closed by foreign host.
+ ```
+
+#### Python modules
+
+From https://docs.weblate.org/en/latest/admin/install/venv-debian.html#python-modules
+
+All-day modules installed:
+
+`$ pip install Weblate aeidon psycopg2-binary`
+
+#### nginx
+
+This root-level service can be found in the `root` repository.
+
+#### uwsgi
+
+uwsgi: https://docs.weblate.org/en/latest/admin/install.html?highlight=uwsgi#sample-configuration-for-nginx-and-uwsgi
+
+See **systemd** below for customization information.
+
+#### celery
+
+Celery: https://docs.weblate.org/en/latest/admin/install.html#celery
+
+See **systemd** below for customization information.
+
+#### systemd
+
+Our needs require running weblate under `weblate` user as a non-priv service. This requires running celery-weblate and uwsgi-weblate services as user-level systemd services.
+
+* See service files under `systemd-nonpriv/` in this repo.
+* On server, non-priv `systemd` files live in `/home/weblate/.config/systemd/user/`
+* Commands:
+ `$ systemctl --user enable | disable | start | stop | status *servicename*`
+
+### Running Weblate
+
+After installing and customizing, log into `weblate` shell account, and invoke virtualenv:
+
+`$ . ~/weblate-env/bin/activate`
+
+Now run Django static files collection (required once):
+(https://docs.weblate.org/en/latest/admin/install.html?highlight=uwsgi#serving-static-files)
+
+`$ weblate collectstatic --noinput`
+
+Weblate server should now be set up and visible at the location specified in the nginx `weblate.site` file. If the site is visible but looks like it's missing CSS styles, that's a problem with the static files. Common causes:
+
+* static files were not collected
+* nginx is not pointing to the correct location
+ * maybe the `/home/weblate/DATA_DIR` symlink is broken?
+
diff --git a/weblate/settings.py b/weblate/settings.py
new file mode 100644
index 0000000..e83bfc9
--- /dev/null
+++ b/weblate/settings.py
@@ -0,0 +1,865 @@
+# /home/weblate/weblate-env/lib/python3.8/site-packages/weblate/settings.py
+
+# Copyright © 2012 - 2020 Michal Čihař <michal@cihar.com>
+#
+# This file is part of Weblate <https://weblate.org/>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+
+
+import os
+import platform
+from logging.handlers import SysLogHandler
+
+#
+# Django settings for Weblate project.
+#
+
+DEBUG = False
+
+ADMINS = (
+ ('Weblate Admin', 'weblate@taler.net'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ "default": {
+ # Use 'postgresql' or 'mysql'.
+ "ENGINE": "django.db.backends.postgresql",
+ # Database name.
+ "NAME": "weblate",
+ # Database user.
+ "USER": "weblate",
+ # Database password.
+ "PASSWORD": "Mei3ein7ooZieGhe",
+ # Set to empty string for localhost.
+ "HOST": "127.0.0.1",
+ # Set to empty string for default.
+ "PORT": "",
+ # Customizations for databases.
+ "OPTIONS": {
+ # In case of using an older MySQL server,
+ # which has MyISAM as a default storage
+ # 'init_command': 'SET storage_engine=INNODB',
+ # Uncomment for MySQL older than 5.7:
+ # 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
+ # Set emoji capable charset for MySQL:
+ # 'charset': 'utf8mb4',
+ # Change connection timeout in case you get MySQL gone away error:
+ # 'connect_timeout': 28800,
+ },
+ }
+}
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Data directory
+DATA_DIR = os.path.join(BASE_DIR, "data")
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# In a Windows environment this must be set to your system time zone.
+TIME_ZONE = "UTC"
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = "en-us"
+
+LANGUAGES = (
+ ("ar", "العربية"),
+ ("az", "Azərbaycan"),
+ ("be", "Беларуская"),
+ ("be@latin", "Biełaruskaja"),
+ ("bg", "Български"),
+ ("br", "Brezhoneg"),
+ ("ca", "Català"),
+ ("cs", "Čeština"),
+ ("da", "Dansk"),
+ ("de", "Deutsch"),
+ ("en", "English"),
+ ("el", "Ελληνικά"),
+ ("en-gb", "English (United Kingdom)"),
+ ("es", "Español"),
+ ("fi", "Suomi"),
+ ("fr", "Français"),
+ ("gl", "Galego"),
+ ("he", "עברית"),
+ ("hu", "Magyar"),
+ ("hr", "Hrvatski"),
+ ("id", "Indonesia"),
+ ("it", "Italiano"),
+ ("ja", "日本語"),
+ ("kab", "Taqbaylit"),
+ ("kk", "Қазақ тілі"),
+ ("ko", "한국어"),
+ ("nb", "Norsk bokmål"),
+ ("nl", "Nederlands"),
+ ("pl", "Polski"),
+ ("pt", "Português"),
+ ("pt-br", "Português brasileiro"),
+ ("ru", "Русский"),
+ ("sk", "Slovenčina"),
+ ("sl", "Slovenščina"),
+ ("sq", "Shqip"),
+ ("sr", "Српски"),
+ ("sv", "Svenska"),
+ ("tr", "Türkçe"),
+ ("uk", "Українська"),
+ ("zh-hans", "简体字"),
+ ("zh-hant", "正體字"),
+)
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = True
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# URL prefix to use, please see documentation for more details
+URL_PREFIX = ""
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+MEDIA_ROOT = os.path.join(DATA_DIR, "media")
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+MEDIA_URL = "{0}/media/".format(URL_PREFIX)
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+STATIC_ROOT = os.path.join(DATA_DIR, "static")
+
+# URL prefix for static files.
+STATIC_URL = "{0}/static/".format(URL_PREFIX)
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ "django.contrib.staticfiles.finders.FileSystemFinder",
+ "django.contrib.staticfiles.finders.AppDirectoriesFinder",
+ "compressor.finders.CompressorFinder",
+)
+
+# Make this unique, and don't share it with anybody.
+# You can generate it using weblate/examples/generate-secret-key
+SECRET_KEY = "+6e0m$7i_sr26pb4p%rhag9cd5zz&9))o+!d!s0_kht0rp3&6j"
+
+_TEMPLATE_LOADERS = [
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
+]
+if not DEBUG:
+ _TEMPLATE_LOADERS = [("django.template.loaders.cached.Loader", _TEMPLATE_LOADERS)]
+TEMPLATES = [
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [os.path.join(BASE_DIR, "weblate", "templates")],
+ "OPTIONS": {
+ "context_processors": [
+ "django.contrib.auth.context_processors.auth",
+ "django.template.context_processors.debug",
+ "django.template.context_processors.i18n",
+ "django.template.context_processors.request",
+ "django.template.context_processors.csrf",
+ "django.contrib.messages.context_processors.messages",
+ "weblate.trans.context_processors.weblate_context",
+ ],
+ "loaders": _TEMPLATE_LOADERS,
+ },
+ }
+]
+
+
+# GitHub username for sending pull requests.
+# Please see the documentation for more details.
+GITHUB_USERNAME = None
+
+# GitLab username for sending merge requests.
+# Please see the documentation for more details.
+GITLAB_USERNAME = None
+
+# Authentication configuration
+AUTHENTICATION_BACKENDS = (
+ "social_core.backends.email.EmailAuth",
+ # 'social_core.backends.google.GoogleOAuth2',
+ # 'social_core.backends.github.GithubOAuth2',
+ # 'social_core.backends.bitbucket.BitbucketOAuth',
+ # 'social_core.backends.suse.OpenSUSEOpenId',
+ # 'social_core.backends.ubuntu.UbuntuOpenId',
+ # 'social_core.backends.fedora.FedoraOpenId',
+ # 'social_core.backends.facebook.FacebookOAuth2',
+ "weblate.accounts.auth.WeblateUserBackend",
+)
+
+# Custom user model
+AUTH_USER_MODEL = "weblate_auth.User"
+
+# Social auth backends setup
+SOCIAL_AUTH_GITHUB_KEY = ""
+SOCIAL_AUTH_GITHUB_SECRET = ""
+SOCIAL_AUTH_GITHUB_SCOPE = ["user:email"]
+
+SOCIAL_AUTH_BITBUCKET_KEY = ""
+SOCIAL_AUTH_BITBUCKET_SECRET = ""
+SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY = True
+
+SOCIAL_AUTH_FACEBOOK_KEY = ""
+SOCIAL_AUTH_FACEBOOK_SECRET = ""
+SOCIAL_AUTH_FACEBOOK_SCOPE = ["email", "public_profile"]
+SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {"fields": "id,name,email"}
+SOCIAL_AUTH_FACEBOOK_API_VERSION = "3.1"
+
+SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ""
+SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ""
+
+# Social auth settings
+SOCIAL_AUTH_PIPELINE = (
+ "social_core.pipeline.social_auth.social_details",
+ "social_core.pipeline.social_auth.social_uid",
+ "social_core.pipeline.social_auth.auth_allowed",
+ "social_core.pipeline.social_auth.social_user",
+ "weblate.accounts.pipeline.store_params",
+ "weblate.accounts.pipeline.verify_open",
+ "social_core.pipeline.user.get_username",
+ "weblate.accounts.pipeline.require_email",
+ "social_core.pipeline.mail.mail_validation",
+ "weblate.accounts.pipeline.revoke_mail_code",
+ "weblate.accounts.pipeline.ensure_valid",
+ "weblate.accounts.pipeline.remove_account",
+ "social_core.pipeline.social_auth.associate_by_email",
+ "weblate.accounts.pipeline.reauthenticate",
+ "weblate.accounts.pipeline.verify_username",
+ "social_core.pipeline.user.create_user",
+ "social_core.pipeline.social_auth.associate_user",
+ "social_core.pipeline.social_auth.load_extra_data",
+ "weblate.accounts.pipeline.cleanup_next",
+ "weblate.accounts.pipeline.user_full_name",
+ "weblate.accounts.pipeline.store_email",
+ "weblate.accounts.pipeline.notify_connect",
+ "weblate.accounts.pipeline.password_reset",
+)
+SOCIAL_AUTH_DISCONNECT_PIPELINE = (
+ "social_core.pipeline.disconnect.allowed_to_disconnect",
+ "social_core.pipeline.disconnect.get_entries",
+ "social_core.pipeline.disconnect.revoke_tokens",
+ "weblate.accounts.pipeline.cycle_session",
+ "weblate.accounts.pipeline.adjust_primary_mail",
+ "weblate.accounts.pipeline.notify_disconnect",
+ "social_core.pipeline.disconnect.disconnect",
+ "weblate.accounts.pipeline.cleanup_next",
+)
+
+# Custom authentication strategy
+SOCIAL_AUTH_STRATEGY = "weblate.accounts.strategy.WeblateStrategy"
+
+# Raise exceptions so that we can handle them later
+SOCIAL_AUTH_RAISE_EXCEPTIONS = True
+
+SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = "weblate.accounts.pipeline.send_validation"
+SOCIAL_AUTH_EMAIL_VALIDATION_URL = "{0}/accounts/email-sent/".format(URL_PREFIX)
+SOCIAL_AUTH_LOGIN_ERROR_URL = "{0}/accounts/login/".format(URL_PREFIX)
+SOCIAL_AUTH_EMAIL_FORM_URL = "{0}/accounts/email/".format(URL_PREFIX)
+SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = "{0}/accounts/profile/#account".format(
+ URL_PREFIX
+)
+SOCIAL_AUTH_PROTECTED_USER_FIELDS = ("email",)
+SOCIAL_AUTH_SLUGIFY_USERNAMES = True
+SOCIAL_AUTH_SLUGIFY_FUNCTION = "weblate.accounts.pipeline.slugify_username"
+
+# Password validation configuration
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" # noqa: E501, pylint: disable=line-too-long
+ },
+ {
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+ "OPTIONS": {"min_length": 6},
+ },
+ {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
+ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
+ {"NAME": "weblate.accounts.password_validation.CharsPasswordValidator"},
+ {"NAME": "weblate.accounts.password_validation.PastPasswordsValidator"},
+ # Optional password strength validation by django-zxcvbn-password
+ # {
+ # 'NAME': 'zxcvbn_password.ZXCVBNValidator',
+ # 'OPTIONS': {
+ # 'min_score': 3,
+ # 'user_attributes': ('username', 'email', 'full_name')
+ # }
+ # },
+]
+
+# Allow new user registrations
+REGISTRATION_OPEN = True
+
+# Middleware
+MIDDLEWARE = [
+ "weblate.middleware.ProxyMiddleware",
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.locale.LocaleMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "weblate.accounts.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "social_django.middleware.SocialAuthExceptionMiddleware",
+ "weblate.accounts.middleware.RequireLoginMiddleware",
+ "weblate.middleware.SecurityMiddleware",
+]
+
+ROOT_URLCONF = "weblate.urls"
+
+# Django and Weblate apps
+INSTALLED_APPS = [
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.sites",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django.contrib.admin.apps.SimpleAdminConfig",
+ "django.contrib.admindocs",
+ "django.contrib.sitemaps",
+ "django.contrib.humanize",
+ "social_django",
+ "crispy_forms",
+ "compressor",
+ "rest_framework",
+ "rest_framework.authtoken",
+ "weblate.addons",
+ "weblate.auth",
+ "weblate.checks",
+ "weblate.formats",
+ "weblate.machinery",
+ "weblate.trans",
+ "weblate.lang",
+ "weblate.langdata",
+ "weblate.memory",
+ "weblate.screenshots",
+ "weblate.fonts",
+ "weblate.accounts",
+ "weblate.utils",
+ "weblate.vcs",
+ "weblate.wladmin",
+ "weblate",
+ # Optional: Git exporter
+ "weblate.gitexport",
+]
+
+# Path to locales
+LOCALE_PATHS = (os.path.join(BASE_DIR, "weblate", "locale"),)
+
+# Custom exception reporter to include some details
+DEFAULT_EXCEPTION_REPORTER_FILTER = "weblate.trans.debug.WeblateExceptionReporterFilter"
+
+# Default logging of Weblate messages
+# - to syslog in production (if available)
+# - otherwise to console
+# - you can also choose 'logfile' to log into separate file
+# after configuring it below
+
+# Detect if we can connect to syslog
+HAVE_SYSLOG = False
+if platform.system() != "Windows":
+ try:
+ handler = SysLogHandler(address="/dev/log", facility=SysLogHandler.LOG_LOCAL2)
+ handler.close()
+ HAVE_SYSLOG = True
+ except IOError:
+ HAVE_SYSLOG = False
+
+if DEBUG or not HAVE_SYSLOG:
+ DEFAULT_LOG = "console"
+else:
+ DEFAULT_LOG = "syslog"
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error when DEBUG=False.
+# See http://docs.djangoproject.com/en/stable/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ "version": 1,
+ "disable_existing_loggers": True,
+ "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
+ "formatters": {
+ "syslog": {"format": "weblate[%(process)d]: %(levelname)s %(message)s"},
+ "simple": {"format": "%(levelname)s %(message)s"},
+ "logfile": {"format": "%(asctime)s %(levelname)s %(message)s"},
+ "django.server": {
+ "()": "django.utils.log.ServerFormatter",
+ "format": "[%(server_time)s] %(message)s",
+ },
+ },
+ "handlers": {
+ "mail_admins": {
+ "level": "ERROR",
+ "filters": ["require_debug_false"],
+ "class": "django.utils.log.AdminEmailHandler",
+ "include_html": True,
+ },
+ "console": {
+ "level": "DEBUG",
+ "class": "logging.StreamHandler",
+ "formatter": "simple",
+ },
+ "django.server": {
+ "level": "INFO",
+ "class": "logging.StreamHandler",
+ "formatter": "django.server",
+ },
+ "syslog": {
+ "level": "DEBUG",
+ "class": "logging.handlers.SysLogHandler",
+ "formatter": "syslog",
+ "address": "/dev/log",
+ "facility": SysLogHandler.LOG_LOCAL2,
+ },
+ # Logging to a file
+ # 'logfile': {
+ # 'level':'DEBUG',
+ # 'class':'logging.handlers.RotatingFileHandler',
+ # 'filename': "/var/log/weblate/weblate.log",
+ # 'maxBytes': 100000,
+ # 'backupCount': 3,
+ # 'formatter': 'logfile',
+ # },
+ },
+ "loggers": {
+ "django.request": {
+ "handlers": ["mail_admins", DEFAULT_LOG],
+ "level": "ERROR",
+ "propagate": True,
+ },
+ "django.server": {
+ "handlers": ["django.server"],
+ "level": "INFO",
+ "propagate": False,
+ },
+ # Logging database queries
+ # 'django.db.backends': {
+ # 'handlers': [DEFAULT_LOG],
+ # 'level': 'DEBUG',
+ # },
+ "weblate": {"handlers": [DEFAULT_LOG], "level": "DEBUG"},
+ # Logging search operations
+ "weblate.search": {"handlers": [DEFAULT_LOG], "level": "INFO"},
+ # Logging VCS operations
+ "weblate.vcs": {"handlers": [DEFAULT_LOG], "level": "WARNING"},
+ # Python Social Auth
+ "social": {"handlers": [DEFAULT_LOG], "level": "DEBUG" if DEBUG else "WARNING"},
+ # Django Authentication Using LDAP
+ "django_auth_ldap": {
+ "level": "DEBUG" if DEBUG else "WARNING",
+ "handlers": [DEFAULT_LOG],
+ },
+ },
+}
+
+# Remove syslog setup if it's not present
+if not HAVE_SYSLOG:
+ del LOGGING["handlers"]["syslog"]
+
+# List of machine translations
+# MT_SERVICES = (
+# 'weblate.machinery.apertium.ApertiumAPYTranslation',
+# 'weblate.machinery.baidu.BaiduTranslation',
+# 'weblate.machinery.deepl.DeepLTranslation',
+# 'weblate.machinery.glosbe.GlosbeTranslation',
+# 'weblate.machinery.google.GoogleTranslation',
+# 'weblate.machinery.microsoft.MicrosoftCognitiveTranslation',
+# 'weblate.machinery.microsoftterminology.MicrosoftTerminologyService',
+# 'weblate.machinery.mymemory.MyMemoryTranslation',
+# 'weblate.machinery.netease.NeteaseSightTranslation',
+# 'weblate.machinery.tmserver.AmagamaTranslation',
+# 'weblate.machinery.tmserver.TMServerTranslation',
+# 'weblate.machinery.yandex.YandexTranslation',
+# 'weblate.machinery.weblatetm.WeblateTranslation',
+# 'weblate.machinery.saptranslationhub.SAPTranslationHub',
+# 'weblate.machinery.youdao.YoudaoTranslation',
+# 'weblate.memory.machine.WeblateMemory',
+# )
+
+# Machine translation API keys
+
+# URL of the Apertium APy server
+MT_APERTIUM_APY = None
+
+# DeepL API key
+MT_DEEPL_KEY = None
+
+# Microsoft Cognitive Services Translator API, register at
+# https://portal.azure.com/
+MT_MICROSOFT_COGNITIVE_KEY = None
+MT_MICROSOFT_REGION = None
+
+# MyMemory identification email, see
+# https://mymemory.translated.net/doc/spec.php
+MT_MYMEMORY_EMAIL = None
+
+# Optional MyMemory credentials to access private translation memory
+MT_MYMEMORY_USER = None
+MT_MYMEMORY_KEY = None
+
+# Google API key for Google Translate API
+MT_GOOGLE_KEY = None
+
+# Baidu app key and secret
+MT_BAIDU_ID = None
+MT_BAIDU_SECRET = None
+
+# Youdao Zhiyun app key and secret
+MT_YOUDAO_ID = None
+MT_YOUDAO_SECRET = None
+
+# Netease Sight (Jianwai) app key and secret
+MT_NETEASE_KEY = None
+MT_NETEASE_SECRET = None
+
+# API key for Yandex Translate API
+MT_YANDEX_KEY = None
+
+# tmserver URL
+MT_TMSERVER = None
+
+# SAP Translation Hub
+MT_SAP_BASE_URL = None
+MT_SAP_SANDBOX_APIKEY = None
+MT_SAP_USERNAME = None
+MT_SAP_PASSWORD = None
+MT_SAP_USE_MT = True
+
+# Title of site to use
+SITE_TITLE = "GNU Taler Weblate Translations"
+
+# Whether site uses https
+ENABLE_HTTPS = False
+
+# Use HTTPS when creating redirect URLs for social authentication, see
+# documentation for more details:
+# https://python-social-auth-docs.readthedocs.io/en/latest/configuration/settings.html#processing-redirects-and-urlopen
+SOCIAL_AUTH_REDIRECT_IS_HTTPS = ENABLE_HTTPS
+
+# Make CSRF cookie HttpOnly, see documentation for more details:
+# https://docs.djangoproject.com/en/1.11/ref/settings/#csrf-cookie-httponly
+CSRF_COOKIE_HTTPONLY = True
+CSRF_COOKIE_SECURE = ENABLE_HTTPS
+# Store CSRF token in session
+CSRF_USE_SESSIONS = True
+# Customize CSRF failure view
+CSRF_FAILURE_VIEW = "weblate.trans.views.error.csrf_failure"
+SESSION_COOKIE_SECURE = ENABLE_HTTPS
+# SSL redirect
+SECURE_SSL_REDIRECT = ENABLE_HTTPS
+# Sent referrrer only for same origin links
+SECURE_REFERRER_POLICY = "same-origin"
+# SSL redirect URL exemption list
+SECURE_REDIRECT_EXEMPT = (r"healthz/$",) # Allowing HTTP access to health check
+# Session cookie age (in seconds)
+SESSION_COOKIE_AGE = 1209600
+# Increase allowed upload size
+DATA_UPLOAD_MAX_MEMORY_SIZE = 50000000
+
+# Some security headers
+SECURE_BROWSER_XSS_FILTER = True
+X_FRAME_OPTIONS = "DENY"
+SECURE_CONTENT_TYPE_NOSNIFF = True
+
+# Optionally enable HSTS
+SECURE_HSTS_SECONDS = 0
+SECURE_HSTS_PRELOAD = False
+SECURE_HSTS_INCLUDE_SUBDOMAINS = False
+
+# URL of login
+LOGIN_URL = "{0}/accounts/login/".format(URL_PREFIX)
+
+# URL of logout
+LOGOUT_URL = "{0}/accounts/logout/".format(URL_PREFIX)
+
+# Default location for login
+LOGIN_REDIRECT_URL = "{0}/".format(URL_PREFIX)
+
+# Anonymous user name
+ANONYMOUS_USER_NAME = "anonymous"
+
+# Reverse proxy settings
+IP_PROXY_HEADER = "HTTP_X_FORWARDED_FOR"
+IP_BEHIND_REVERSE_PROXY = False
+IP_PROXY_OFFSET = 0
+
+# Sending HTML in mails
+EMAIL_SEND_HTML = True
+
+# Subject of emails includes site title
+EMAIL_SUBJECT_PREFIX = "[{0}] ".format(SITE_TITLE)
+
+# Enable remote hooks
+ENABLE_HOOKS = True
+
+# Number of nearby messages to show in each direction
+NEARBY_MESSAGES = 5
+
+# By default the length of a given translation is limited to the length of
+# the source string * 10 characters. Set this option to False to allow longer
+# translations (up to 10.000 characters)
+LIMIT_TRANSLATION_LENGTH_BY_SOURCE_LENGTH = True
+
+# Use simple language codes for default language/country combinations
+SIMPLIFY_LANGUAGES = True
+
+# Render forms using bootstrap
+CRISPY_TEMPLATE_PACK = "bootstrap3"
+
+# List of quality checks
+# CHECK_LIST = (
+# 'weblate.checks.same.SameCheck',
+# 'weblate.checks.chars.BeginNewlineCheck',
+# 'weblate.checks.chars.EndNewlineCheck',
+# 'weblate.checks.chars.BeginSpaceCheck',
+# 'weblate.checks.chars.EndSpaceCheck',
+# 'weblate.checks.chars.DoubleSpaceCheck',
+# 'weblate.checks.chars.EndStopCheck',
+# 'weblate.checks.chars.EndColonCheck',
+# 'weblate.checks.chars.EndQuestionCheck',
+# 'weblate.checks.chars.EndExclamationCheck',
+# 'weblate.checks.chars.EndEllipsisCheck',
+# 'weblate.checks.chars.EndSemicolonCheck',
+# 'weblate.checks.chars.MaxLengthCheck',
+# 'weblate.checks.chars.KashidaCheck',
+# 'weblate.checks.chars.PuctuationSpacingCheck',
+# 'weblate.checks.format.PythonFormatCheck',
+# 'weblate.checks.format.PythonBraceFormatCheck',
+# 'weblate.checks.format.PHPFormatCheck',
+# 'weblate.checks.format.CFormatCheck',
+# 'weblate.checks.format.PerlFormatCheck',
+# 'weblate.checks.format.JavaScriptFormatCheck',
+# 'weblate.checks.format.CSharpFormatCheck',
+# 'weblate.checks.format.JavaFormatCheck',
+# 'weblate.checks.format.JavaMessageFormatCheck',
+# "weblate.checks.format.PercentPlaceholdersCheck",
+# "weblate.checks.format.I18NextInterpolationCheck",
+# 'weblate.checks.angularjs.AngularJSInterpolationCheck',
+# 'weblate.checks.qt.QtFormatCheck',
+# 'weblate.checks.qt.QtPluralCheck',
+# 'weblate.checks.ruby.RubyFormatCheck',
+# 'weblate.checks.consistency.PluralsCheck',
+# 'weblate.checks.consistency.SamePluralsCheck',
+# 'weblate.checks.consistency.ConsistencyCheck',
+# 'weblate.checks.consistency.TranslatedCheck',
+# 'weblate.checks.chars.EscapedNewlineCountingCheck',
+# 'weblate.checks.chars.NewLineCountCheck',
+# 'weblate.checks.markup.BBCodeCheck',
+# 'weblate.checks.chars.ZeroWidthSpaceCheck',
+# 'weblate.checks.render.MaxSizeCheck',
+# 'weblate.checks.markup.XMLValidityCheck',
+# 'weblate.checks.markup.XMLTagsCheck',
+# 'weblate.checks.markup.MarkdownRefLinkCheck',
+# 'weblate.checks.markup.MarkdownLinkCheck',
+# 'weblate.checks.markup.MarkdownSyntaxCheck',
+# 'weblate.checks.markup.URLCheck',
+# 'weblate.checks.markup.SafeHTMLCheck',
+# 'weblate.checks.placeholders.PlaceholderCheck',
+# 'weblate.checks.placeholders.RegexCheck',
+# 'weblate.checks.source.OptionalPluralCheck',
+# 'weblate.checks.source.EllipsisCheck',
+# 'weblate.checks.source.MultipleFailingCheck',
+# )
+
+# List of automatic fixups
+# AUTOFIX_LIST = (
+# 'weblate.trans.autofixes.whitespace.SameBookendingWhitespace',
+# 'weblate.trans.autofixes.chars.ReplaceTrailingDotsWithEllipsis',
+# 'weblate.trans.autofixes.chars.RemoveZeroSpace',
+# 'weblate.trans.autofixes.chars.RemoveControlChars',
+# )
+
+# List of enabled addons
+# WEBLATE_ADDONS = (
+# 'weblate.addons.gettext.GenerateMoAddon',
+# 'weblate.addons.gettext.UpdateLinguasAddon',
+# 'weblate.addons.gettext.UpdateConfigureAddon',
+# 'weblate.addons.gettext.MsgmergeAddon',
+# 'weblate.addons.gettext.GettextCustomizeAddon',
+# 'weblate.addons.gettext.GettextAuthorComments',
+# 'weblate.addons.cleanup.CleanupAddon',
+# 'weblate.addons.consistency.LangaugeConsistencyAddon',
+# 'weblate.addons.discovery.DiscoveryAddon',
+# 'weblate.addons.flags.SourceEditAddon',
+# 'weblate.addons.flags.TargetEditAddon',
+# 'weblate.addons.flags.SameEditAddon',
+# "weblate.addons.flags.BulkEditAddon",
+# 'weblate.addons.generate.GenerateFileAddon',
+# 'weblate.addons.json.JSONCustomizeAddon',
+# 'weblate.addons.properties.PropertiesSortAddon',
+# 'weblate.addons.git.GitSquashAddon',
+# 'weblate.addons.removal.RemoveComments',
+# 'weblate.addons.removal.RemoveSuggestions',
+# 'weblate.addons.resx.ResxUpdateAddon',
+# 'weblate.addons.yaml.YAMLCustomizeAddon',
+# 'weblate.addons.autotranslate.AutoTranslateAddon',
+# )
+
+# E-mail address that error messages come from.
+SERVER_EMAIL = "weblate@taler.net"
+
+# Default email address to use for various automated correspondence from
+# the site managers. Used for registration emails.
+DEFAULT_FROM_EMAIL = "weblate@taler.net"
+
+# List of URLs your site is supposed to serve
+ALLOWED_HOSTS = ["*"]
+
+# Configuration for caching
+CACHES = {
+ "default": {
+ "BACKEND": "django_redis.cache.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379/1",
+ # If redis is running on same host as Weblate, you might
+ # want to use unix sockets instead:
+ # 'LOCATION': 'unix:///var/run/redis/redis.sock?db=1',
+ "OPTIONS": {
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
+ "PARSER_CLASS": "redis.connection.HiredisParser",
+ "PASSWORD": None,
+ "CONNECTION_POOL_KWARGS": {},
+ },
+ "KEY_PREFIX": "weblate",
+ },
+ "avatar": {
+ "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+ "LOCATION": os.path.join(DATA_DIR, "avatar-cache"),
+ "TIMEOUT": 86400,
+ "OPTIONS": {"MAX_ENTRIES": 1000},
+ },
+}
+
+# Store sessions in cache
+SESSION_ENGINE = "django.contrib.sessions.backends.cache"
+# Store messages in session
+MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
+
+# REST framework settings for API
+REST_FRAMEWORK = {
+ # Use Django's standard `django.contrib.auth` permissions,
+ # or allow read-only access for unauthenticated users.
+ "DEFAULT_PERMISSION_CLASSES": [
+ "rest_framework.permissions.IsAuthenticatedOrReadOnly"
+ # Use following with LOGIN_REQUIRED_URLS
+ # "rest_framework.permissions.IsAuthenticated"
+ ],
+ "DEFAULT_AUTHENTICATION_CLASSES": (
+ "rest_framework.authentication.TokenAuthentication",
+ "weblate.api.authentication.BearerAuthentication",
+ "rest_framework.authentication.SessionAuthentication",
+ ),
+ "DEFAULT_THROTTLE_CLASSES": (
+ "rest_framework.throttling.AnonRateThrottle",
+ "rest_framework.throttling.UserRateThrottle",
+ ),
+ "DEFAULT_THROTTLE_RATES": {"anon": "100/day", "user": "5000/hour"},
+ "DEFAULT_PAGINATION_CLASS": ("rest_framework.pagination.PageNumberPagination"),
+ "PAGE_SIZE": 20,
+ "VIEW_DESCRIPTION_FUNCTION": "weblate.api.views.get_view_description",
+ "UNAUTHENTICATED_USER": "weblate.auth.models.get_anonymous",
+}
+
+# Example for restricting access to logged in users
+# LOGIN_REQUIRED_URLS = (
+# r'/(.*)$',
+# )
+
+# In such case you will want to include some of the exceptions
+# LOGIN_REQUIRED_URLS_EXCEPTIONS = (
+# r'/accounts/(.*)$', # Required for login
+# r'/admin/login/(.*)$', # Required for admin login
+# r'/static/(.*)$', # Required for development mode
+# r'/widgets/(.*)$', # Allowing public access to widgets
+# r'/data/(.*)$', # Allowing public access to data exports
+# r'/hooks/(.*)$', # Allowing public access to notification hooks
+# r'/healthz/$', # Allowing public access to health check
+# r'/api/(.*)$', # Allowing access to API
+# r'/js/i18n/$', # JavaScript localization
+# r'/contact/$', # Optional for contact form
+# r'/legal/(.*)$', # Optional for legal app
+# )
+
+# Silence some of the Django system checks
+SILENCED_SYSTEM_CHECKS = [
+ # We have modified django.contrib.auth.middleware.AuthenticationMiddleware
+ # as weblate.accounts.middleware.AuthenticationMiddleware
+ "admin.E408"
+]
+
+# Celery worker configuration for testing
+# CELERY_TASK_ALWAYS_EAGER = True
+# CELERY_BROKER_URL = 'memory://'
+# CELERY_TASK_EAGER_PROPAGATES = True
+# Celery worker configuration for production
+CELERY_TASK_ALWAYS_EAGER = False
+CELERY_BROKER_URL = "redis://localhost:6379"
+CELERY_RESULT_BACKEND = CELERY_BROKER_URL
+
+# Celery settings, it is not recommended to change these
+CELERY_WORKER_MAX_MEMORY_PER_CHILD = 200000
+CELERY_BEAT_SCHEDULE_FILENAME = os.path.join(DATA_DIR, "celery", "beat-schedule")
+CELERY_TASK_ROUTES = {
+ "weblate.trans.tasks.auto_translate": {"queue": "translate"},
+ "weblate.memory.tasks.*": {"queue": "memory"},
+ "weblate.accounts.tasks.notify_*": {"queue": "notify"},
+ "weblate.accounts.tasks.send_mails": {"queue": "notify"},
+ "weblate.utils.tasks.settings_backup": {"queue": "backup"},
+ "weblate.utils.tasks.database_backup": {"queue": "backup"},
+ "weblate.wladmin.tasks.backup": {"queue": "backup"},
+ "weblate.wladmin.tasks.backup_service": {"queue": "backup"},
+}
+
+# Enable plain database backups
+DATABASE_BACKUP = "plain"
+
+# Enable auto updating
+AUTO_UPDATE = False
+
+# PGP commits signing
+WEBLATE_GPG_IDENTITY = None
+
+# Third party services integration
+MATOMO_SITE_ID = None
+MATOMO_URL = None
+GOOGLE_ANALYTICS_ID = None
+SENTRY_DSN = None
+AKISMET_API_KEY = None
+
+# E-mail settings from https://docs.weblate.org/en/latest/admin/install.html?highlight=email
+#EMAIL_HOST = 'localhost'
+EMAIL_HOST_USER = 'weblate'
+#EMAIL_PORT = '25'
diff --git a/weblate/systemd-nonpriv/README b/weblate/systemd-nonpriv/README
new file mode 100644
index 0000000..2330cf4
--- /dev/null
+++ b/weblate/systemd-nonpriv/README
@@ -0,0 +1,3 @@
+These files belong in /home/weblate/.config/systemd/user/ and are invoked with:
+
+systemctl --user start | stop *servicename*
diff --git a/weblate/systemd-nonpriv/celery-weblate.service b/weblate/systemd-nonpriv/celery-weblate.service
new file mode 100644
index 0000000..bf84a03
--- /dev/null
+++ b/weblate/systemd-nonpriv/celery-weblate.service
@@ -0,0 +1,24 @@
+[Unit]
+Description=Celery Service (Weblate)
+After=network.target
+
+[Service]
+Type=forking
+#User=weblate
+#Group=weblate
+EnvironmentFile=/home/weblate/celery-weblate
+WorkingDirectory=/home/weblate
+RuntimeDirectory=celery
+RuntimeDirectoryPreserve=restart
+LogsDirectory=celery
+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}'
+ExecStop=/bin/sh -c '${CELERY_BIN} 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}'
+
+[Install]
+WantedBy=multi-user.target
diff --git a/weblate/systemd-nonpriv/uwsgi-weblate.service b/weblate/systemd-nonpriv/uwsgi-weblate.service
new file mode 100644
index 0000000..e04ec35
--- /dev/null
+++ b/weblate/systemd-nonpriv/uwsgi-weblate.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=Weblate uWSGI Service
+After=multi-user.target
+
+[Service]
+Type=simple
+Restart=always
+RestartSec=1
+#User=weblate
+#Group=weblate
+ExecStart=/home/weblate/weblate-env/bin/uwsgi --home=/home/weblate/weblate-env --module weblate.wsgi:application -s /home/weblate/uwsgi.sock --chmod-socket=660
+PIDFile=/home/weblate/pid/weblate-uwsgi.pid
+WorkingDirectory=/home/weblate
+LogsDirectory=logs
+
+[Install]
+WantedBy=multi-user.target