aboutsummaryrefslogtreecommitdiff
path: root/gn/services/mailman.scm
blob: d68924f7d8c6a9a2687855ee2803b5213ff0eaf9 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
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))))