diff options
-rw-r--r-- | gn/services/mailman-container.scm | 94 | ||||
-rw-r--r-- | gn/services/mailman.scm | 352 |
2 files changed, 446 insertions, 0 deletions
diff --git a/gn/services/mailman-container.scm b/gn/services/mailman-container.scm new file mode 100644 index 0000000..1afa08a --- /dev/null +++ b/gn/services/mailman-container.scm @@ -0,0 +1,94 @@ +(define-module (gn services mailman-container)) + +(use-modules (gnu) + (gn packages mailman) + (guix records) + (ice-9 match)) +(use-service-modules base mcron networking shepherd) + +(define-record-type* <mailman-configuration> + mailman-configuration + make-mailman-configuration + mailman-configuration? + (package mailman-configuration-package ; package + (default mailman)) + ) + +(define mailman-cronjobs + (match-lambda + (($ <mailman-configuration> package) + (list + #~(job '(next-hour '(0)) + (string-append #$package "/bin/mailman digests --periodic")) + #~(job '(next-hour '(8)) + (string-append #$package "/bin/mailman notify")))))) + +(define mailman-activation + (match-lambda + (($ <mailman-configuration> package) + #~(begin + (use-modules (guix build utils)) + (let ((%user (getpwnam "mailman"))) + ;; Prepare the environment for mailman: + ;; https://docs.mailman.io/en-us/install-from-binary/ + (unless (directory-exists? #$work-dir) + (mkdir-p #$work-dir) + ;; These two are supposed to be recursive. + (chown #$work-dir (passwd:uid %user) (passwd:gid %user)) + (chmod #$work-dir #o750))))))) + +(define mailman-config-file + (plain-file "mailman.cfg" + "[mailman] +layout: local +\n")) + +(define mailman-shepherd-service + (match-lambda + (($ <mailman-configuration> package) + (list (shepherd-service + (documentation "Run the Mailman server.") + (requirement '(networking)) + (provision '(mailman)) + (start #~(make-forkexec-constructor + (list + #$(file-append package "/bin/mailman") + "start") + #:environment-variables + (list (string-append "MAILMAN_CONFIG_FILE" mailman-config-file) + ) + )) + (stop #~(make-kill-destructor + (list + #$(file-append package "/bin/mailman") + "stop")))))))) + +(define mailman-service-type + (service-type + (name 'mailman) + (extensions + (list (service-extension shepherd-root-service-type + mailman-shepherd-service) + (service-extension activation-service-type + mailman-activation) + (service-extension mcron-service-type + mailman-cronjobs))) + (description + "Run a Mailman server.") + (default-value (mailman-configuration)))) + + +(operating-system + (host-name "mailman") + (timezone "Etc/UTC") + (locale "en_US.utf8") + + (bootloader (bootloader-configuration + (bootloader grub-bootloader) + (target "does-not-matter"))) + (file-systems %base-file-systems) + ;; No firmware for VMs or containers. + (firmware '()) + + (services (list (service dhcp-client-service-type) + (service mailman-service-type)))) diff --git a/gn/services/mailman.scm b/gn/services/mailman.scm new file mode 100644 index 0000000..d68924f --- /dev/null +++ b/gn/services/mailman.scm @@ -0,0 +1,352 @@ +(define-module (gn services mailman) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services mcron) + #:use-module (gnu services networking) + #:use-module (gnu services shepherd) + #:use-module (gnu services web) + #:use-module (gn packages mailman) + #:use-module (ice-9 match) + #:export (mailman-service-type)) + +(define-record-type* <mailman-database-configuration> + mailman-database-configuration + make-mailman-database-configuration + mailman-database-configuration? + (engine mailman-database-configuration-engine + (default "django.db.backends.postgresql_psycopg2")) + (name mailman-database-configuration-name + (default "mailman")) + (user mailman-database-configuration-user + (default "mailman")) + (password mailman-database-configuration-password + (default "")) + (host mailman-database-configuration-host + (default "")) + (port mailman-database-configuration-port + (default ""))) + +(define-record-type* <mailman-settings-module> + mailman-settings-module make-mailman-settings-module + mailman-settings-module? + (database-configuration mailman-settings-module-database-configuration + (default (mailman-database-configuration))) + (secret-key-file mailman-settings-module-secret-key-file + (default "/etc/mailman/django-secret-key")) + (allowed-hosts mailman-settings-module-allowed-hosts) + (default-from-email mailman-settings-module-default-from-email) + (static-url mailman-settings-module-static-url + (default "/static/")) + (admins mailman-settings-module-admins + (default '())) + (debug? mailman-settings-module-debug? + (default #f)) + (enable-rest-api? mailman-settings-module-enable-rest-api? + (default #t)) + (enable-xmlrpc? mailman-settings-module-enable-xmlrpc? + (default #t)) + (force-https-links? mailman-settings-module-force-https-links? + (default #t)) + (extra-settings mailman-settings-module-extra-settings + (default ""))) + +(define-record-type* <mailman-rest-api-configuration> + mailman-rest-api-configuration + make-mailman-rest-api-configuration + mailman-rest-api-configuration? + (url mailman-rest-api-configuration-url + (default "http://localhost:8001")) + (user mailman-rest-api-configuration-user + (default "restadmin")) + (pass mailman-rest-api-configuration-pass + (default "restpass")) + (archiver-key mailman-rest-api-configuration-archiver-key + (default "SecretArchiverAPIKey")) + ) + ;;MAILMAN_REST_API_URL = 'http://localhost:8001' MAILMAN_REST_API_USER = 'restadmin' MAILMAN_REST_API_PASS = 'restpass' MAILMAN_ARCHIVER_KEY = 'SecretArchiverAPIKey' MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1') + +(define-record-type* <mailman-configuration> + mailman-configuration + make-mailman-configuration + mailman-configuration? + (package mailman-configuration-package ; package + (default mailman)) + (database mailman-configuration-database) + (settings-module mailman-configuration-settings-module) + (static-path mailman-configuration-static-url + (default "/static/")) + ) + +(define mailman-cronjobs + (match-lambda + (($ <mailman-configuration> package) + (list + #~(job '(next-hour '(0)) + (string-append #$package "/bin/mailman digests --periodic")) + #~(job '(next-hour '(8)) + (string-append #$package "/bin/mailman notify")))))) + +;(define mailman-activation +; (match-lambda +; (($ <mailman-configuration> package postgresql user) +; #~(begin +; (use-modules (guix build utils)) +; (let ((%user (getpwnam "mailman"))) +; ;; Prepare the environment for mailman: +; ;; https://docs.mailman.io/en-us/install-from-binary/ +; (unless (directory-exists? #$work-dir) +; (mkdir-p #$work-dir) +; ;; These two are supposed to be recursive. +; (chown #$work-dir (passwd:uid %user) (passwd:gid %user)) +; (chmod #$work-dir #o750))))))) + +;; Django uses a Python module for configuration, so this compiler generates a +;; Python module from the configuration record. +(define-gexp-compiler (mailman-settings-module-compiler + (file <mailman-settings-module>) system target) + (match file + (($ <mailman-settings-module> database-configuration secret-key-file + allowed-hosts default-from-email + static-url admins debug? enable-rest-api? + enable-xmlrpc? force-https-links? + extra-configuration) + (gexp->derivation + "mailman-settings" + (with-imported-modules '((guix build utils)) + #~(let ((output #$output)) + (define (create-__init__.py filename) + (call-with-output-file filename + (lambda (port) (display "" port)))) + + (use-modules (guix build utils) + (srfi srfi-1)) + + (mkdir-p (string-append output "/guix/mailman")) + (create-__init__.py + (string-append output "/guix/__init__.py")) + (create-__init__.py + (string-append output "/guix/mailman/__init__.py")) + + (call-with-output-file + (string-append output "/guix/mailman/settings.py") + (lambda (port) + (display + (string-append "import os +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Configuration from Guix +with open('" #$secret-key-file "') as f: + SECRET_KEY = f.read().strip() + +DEBUG = " #$(if debug? "True" "False") " + + +ADMINS = [ +" #$(string-concatenate + (map (match-lambda + ((name email-address) + (string-append + "('" name "','" email-address "'),"))) + admins)) +"] + +SITE_ID = 1 + +ALLOWED_HOSTS = [ +" #$(string-concatenate + (map (lambda (allowed-host) + (string-append " '" allowed-host "'\n")) + allowed-hosts)) +"] + +# Mailman API credentials +" #$(if enable-rest-api? + (match rest-api-configuration + (($ <mailman-rest-api-configuration> + url user pass archiver-key) + (string-append +"MAILMAN_REST_API_URL = '"url"'\n" +"MAILMAN_REST_API_USER = '"user"'\n" +"MAILMAN_REST_API_PASS = '"pass"'\n" +"MAILMAN_ARCHIVER_KEY = '"archiver-key"'\n" +"MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1')\n"))) + "") " + +INSTALLED_APPS = ( 'hyperkitty', 'postorius', 'django_mailman3', # Uncomment the next line to enable the admin: 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'django_gravatar', 'compressor', 'haystack', 'django_extensions', 'django_q', 'allauth', 'allauth.account', 'allauth.socialaccount', 'django_mailman3.lib.auth.fedora', 'allauth.socialaccount.providers.openid', 'allauth.socialaccount.providers.github', 'allauth.socialaccount.providers.gitlab', 'allauth.socialaccount.providers.google', # 'allauth.socialaccount.providers.facebook', 'allauth.socialaccount.providers.twitter', 'allauth.socialaccount.providers.stackexchange', ) MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django_mailman3.middleware.TimezoneMiddleware', 'postorius.middleware.PostoriusMiddleware', ) ROOT_URLCONF = 'urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.template.context_processors.csrf', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django_mailman3.context_processors.common', 'hyperkitty.context_processors.common', 'postorius.context_processors.postorius', ], }, }, ] WSGI_APPLICATION = 'wsgi.application' + +DATABASES = { + 'default': { +" #$(match database-configuration + (($ <mailman-database-configuration> + engine name user password host port) + (string-append + " 'ENGINE': '" engine "',\n" + " 'NAME': '" name "',\n" + " 'USER': '" user "',\n" + " 'PASSWORD': '" password "',\n" + " 'HOST': '" host "',\n" + " 'PORT': '" port "',\n"))) " + }, +} + +AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] + +LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True + +" #$(if debug? + #~(string-append "STATIC_ROOT = '" + #$(file-append patchwork "/share/patchwork/htdocs") + "'") + #~(string-append "STATIC_URL = '" #$static-url "'")) " + +STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', + 'compressor.finders.CompressorFinder', ) +# Django 1.6+ defaults to a JSON serializer, but it won't work with +# django-openid, see +# https://bugs.launchpad.net/django-openid-auth/+bug/1252826 +SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' +LOGIN_URL = 'account_login' +LOGIN_REDIRECT_URL = 'list_index' +LOGOUT_URL = 'account_logout' + +DEFAULT_FROM_EMAIL = 'postorius@localhost.local' +SERVER_EMAIL = 'root@localhost.local' +# Change this when you have a real email backend +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# Compatibility with Bootstrap 3 +from django.contrib.messages import constants as messages # flake8: noqa +MESSAGE_TAGS = { messages.ERROR: 'danger' } + +#Social Auth +AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', ) +#Django allauth +ACCOUNT_AUTHENTICATION_METHOD = \"username_email\" +ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_EMAIL_VERIFICATION = \"mandatory\" +ACCOUNT_DEFAULT_HTTP_PROTOCOL = \"http\" +ACCOUNT_UNIQUE_EMAIL = True +SOCIALACCOUNT_PROVIDERS = { 'openid': { 'SERVERS': [ dict(id='yahoo', name='Yahoo', openid_url='http://me.yahoo.com'), ], }, 'google': { 'SCOPE': ['profile', 'email'], 'AUTH_PARAMS': {'access_type': 'online'}, }, 'facebook': { 'METHOD': 'oauth2', 'SCOPE': ['email'], 'FIELDS': [ 'email', 'name', 'first_name', 'last_name', 'locale', 'timezone', ], 'VERSION': 'v2.4', }, } + +COMPRESS_PRECOMPILERS = ( ('text/less', 'lessc {infile} {outfile}'), ('text/x-scss', 'sassc -t compressed {infile} {outfile}'), ('text/x-sass', 'sassc -t compressed {infile} {outfile}'), ) +HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'PATH': os.path.join(BASE_DIR, \"fulltext_index\"), }, } + +Q_CLUSTER = { 'timeout': 300, 'save_limit': 100, 'orm': 'default', } + +LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' }, 'file':{ 'level': 'INFO', #'class': 'logging.handlers.RotatingFileHandler', 'class': 'logging.handlers.WatchedFileHandler', 'filename': os.path.join(BASE_DIR, 'logs', 'mailmansuite.log'), 'formatter': 'verbose', }, 'console': { 'class': 'logging.StreamHandler', 'formatter': 'simple', }, }, 'loggers': { 'django.request': { 'handlers': ['mail_admins', 'file'], 'level': 'ERROR', 'propagate': True, }, 'django': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': True, }, 'hyperkitty': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, 'postorius': { 'handlers': ['console', 'file'], 'level': 'INFO', }, }, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s' }, 'simple': { 'format': '%(levelname)s %(message)s' }, }, #'root': { # 'handlers': ['file'], # 'level': 'INFO', #}, } + +if DEBUG == True: EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' EMAIL_FILE_PATH = os.path.join(BASE_DIR, 'emails') +FILTER_VHOST = False +POSTORIUS_TEMPLATE_BASE_URL = 'http://localhost:8000' +try: from settings_local import * except ImportError: pass + +")))))))))) + +(define mailman-config-file + (plain-file "mailman.cfg" + "[mailman] +layout: local +[mta] +# https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html#exim +# For all Exim4 installations. +incoming: mailman.mta.exim4.LMTP +outgoing: mailman.mta.deliver.deliver +# Typical single host with MTA and Mailman configuration. +# Adjust to your system's configuration. +# Exim happily works with the \"localhost\" alias rather than IP address. +lmtp_host: localhost +smtp_host: localhost +# Mailman should not be run as root. +# Use any convenient port > 1024. 8024 is a convention, but can be +# changed if there is a conflict with other software using that port. +lmtp_port: 8024 +# smtp_port rarely needs to be set. +smtp_port: 25 +# Exim4-specific configuration parameter defaults. Currently empty. +configuration: python:mailman.config.exim4 +\n")) + +;; https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html#exim4-configuration +(define exim-config-file + (plain-file "exim.conf" "# /etc/exim4/conf.d/main/25_mm3_macros +# The colon-separated list of domains served by Mailman. +domainlist mm_domains=list.example.net + +MM3_LMTP_PORT=8024 + +# MM3_HOME must be set to mailman's var directory, wherever it is +# according to your installation. +MM3_HOME=/opt/mailman/var +MM3_UID=list +MM3_GID=list + +################################################################ +# The configuration below is boilerplate: +# you should not need to change it. + +# The path to the list receipt (used as the required file when +# matching list addresses) +MM3_LISTCHK=MM3_HOME/lists/${local_part}.${domain} + +# /etc/exim4/conf.d/router/455_mm3_router +mailman3_router: +driver = accept +domains = +mm_domains +require_files = MM3_LISTCHK +local_part_suffix_optional +local_part_suffix = \ +-bounces : -bounces+* : \ +-confirm : -confirm+* : \ +-join : -leave : \ +-owner : -request : \ +-subscribe : -unsubscribe +transport = mailman3_transport + +# /etc/exim4/conf.d/transport/55_mm3_transport +mailman3_transport: +driver = smtp +protocol = lmtp +allow_localhost +hosts = localhost +port = MM3_LMTP_PORT +rcpt_include_affixes = true +\n") + +(define mailman-shepherd-service + (match-lambda + (($ <mailman-configuration> package) + (list (shepherd-service + (documentation "Run the Mailman server.") + (requirement '(networking)) + (provision '(mailman)) + (start #~(make-forkexec-constructor + (list + #$(file-append package "/bin/mailman") + "start") + #:environment-variables + (list (string-append "MAILMAN_CONFIG_FILE" mailman-config-file) + ) + )) + (stop #~(make-kill-destructor + (list + #$(file-append package "/bin/mailman") + "stop")))))))) + +(define mailman-service-type + (service-type + (name 'mailman) + (extensions + (list (service-extension shepherd-root-service-type + mailman-shepherd-service) + (service-extension activation-service-type + mailman-activation) + (service-extension mcron-service-type + mailman-cronjobs))) + (description + "Run a Mailman server.") + (default-value (mailman-configuration)))) + |