(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 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 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 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 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 (($ 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 ; (($ 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 ) system target) (match file (($ 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 (($ 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 (($ 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 (($ 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))))