Add a mailman3 list server

This should now be a largely functional deployment of mailman 3. There
are still some bits that need testing but we'll use followup changes to
force failure and hold nodes.

This deployment of mailman3 uses upstream docker container images. We
currently hack up uids and gids to accomodate that. We also hack up the
settings file and bind mount it over the upstream file in order to use
host networking. We override the hyperkitty index type to xapian. All
list domains are hosted in a single installation and we use native
vhosting to handle that.

We'll deploy this to a new server and migrate one mailing list domain at
a time. This will allow us to start with lists.opendev.org and test
things like dmarc settings before expanding to the remaining lists.

A migration script is also included, which has seen extensive
testing on held nodes for importing copies of the production data
sets.

Change-Id: Ic9bf5cfaf0b87c100a6ce003a6645010a7b50358
changes/48/851248/83
Clark Boylan 6 months ago committed by Jeremy Stanley
parent 220a3ced6d
commit c1c91886b4

@ -0,0 +1,89 @@
exim_queue_interval: '1m'
exim_queue_run_max: '50'
exim_smtp_accept_max: '100'
exim_smtp_accept_max_per_host: '10'
iptables_extra_public_tcp_ports:
- 25
- 80
- 443
- 465
letsencrypt_certs:
lists-opendev-org-main:
- "{{ inventory_hostname }}"
- lists.opendev.org
- lists.airshipit.org
- lists.katacontainers.io
- lists.openinfra.dev
- lists.openstack.org
- lists.starlingx.io
- lists.zuul-ci.org
borg_backup_excludes_extra:
# db is backed up in dumps, don't capture live files
- /var/lib/mailman/database
# backed up by streaming backup
- /var/backups/mailman-mariadb
# Can regenerate indexes from source email files
- /var/lib/mailman/web-data/fulltext_index
exim_routers:
- mailman_verp_router: |
{% raw -%}
driver = dnslookup
condition = ${if or{{eq{$sender_host_address}{127.0.0.1}}\
{eq{$sender_host_address}{::1}}}{yes}{no}}
{% endraw %}
domains = !+local_domains
ignore_target_hosts = <; 0.0.0.0; \
127.0.0.0/8; \
::1/128;fe80::/10;fe \
c0::/10;ff00::/8
senders = "*-bounces@*"
transport = mailman_verp_smtp
- dnslookup: '{{ exim_dnslookup_router }}'
- system_aliases: '{{ exim_system_aliases_router }}'
- domain_aliases: |
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain}lsearch{/etc/aliases.domain}}
file_transport = address_file
pipe_transport = address_pipe
- localuser: '{{ exim_localuser_router }}'
- mailman_copy: |
driver = accept
domains = lists.openstack.org
local_parts = openstack-discuss
transport = local_copy
unseen
- mailman_router: |
driver = accept
domains = {{ mm_domains }}
local_part_suffix = -admin : \
-bounces : -bounces+* : \
-confirm : -confirm+* : \
-join : -leave : \
-owner : -request : \
-subscribe : -unsubscribe
local_part_suffix_optional
require_files = /var/lib/mailman/core/var/lists/${local_part}.${domain}
transport = mailman_transport
exim_transports:
- local_copy: |
driver = appendfile
file = /var/mail/$local_part
group = mail
mode = 0660
- mailman_transport: |
debug_print = "Email for mailman"
driver = smtp
protocol = lmtp
allow_localhost
hosts = localhost
port = 8024
rcpt_include_affixes = true
- mailman_verp_smtp: |
driver = smtp
headers_add = Errors-To: ${return_path}
headers_remove = Errors-To
max_rcpt = 1
return_path = ${local_part:$return_path}+$local_part=$domain@${domain:$return_path}
mailman_multihost: true

@ -24,11 +24,13 @@ groups:
- kdc03.openstack.org
- eavesdrop01.opendev.org
- paste01.opendev.org
- lists01.opendev.org
# These are test specific hosts that we add to the backup
# group to mimic as much as possible what their prod version
# end up doing.
- gitea99.opendev.org
- review99.opendev.org
- lists99.opendev.org
# All these servers are "special-cased" in specifically
# as they are puppet and should be replaced "soon"
- lists.openstack.org
@ -89,6 +91,7 @@ groups:
- keycloak[0-9]*.opendev.org
- lists.katacontainers.io
- lists.openstack.org
- lists[0-9]*.opendev.org
- meetpad[0-9]*.opendev.org
- mirror[0-9]*.opendev.org
- nb[0-9]*.opendev.org
@ -100,8 +103,10 @@ groups:
- translate[0-9]*.open*.org
- zuul[0-9]*.opendev.org
mailman:
- lists*.katacontainers.io
- lists*.open*.org
- lists.katacontainers.io
- lists.openstack.org
mailman3:
- lists[0-9]*.opendev.org
meetpad:
- meetpad[0-9]*.opendev.org
mirror:

@ -0,0 +1,225 @@
mm_domains: 'lists.openstack.org:lists.zuul-ci.org:lists.airshipit.org:lists.starlingx.io:lists.opendev.org:lists.openinfra.dev:lists.katacontainers.io'
exim_local_domains: "@:{{ mm_domains }}"
exim_enable_spf: true
exim_aliases:
root: "{{ ','.join(listadmins|default([])) }}"
interop-wg: openstack-discuss
openstack: openstack-discuss
openstack-dev: openstack-discuss
openstack-infra: openstack-discuss
openstack-operators: openstack-discuss
openstack-security: openstack-discuss
openstack-sigs: openstack-discuss
openstack-tc: openstack-discuss
user-committee: openstack-discuss
airship-discuss-owner: spam
community-owner: spam
edge-computing-owner: spam
foundation-board-confidential-owner: spam
foundation-board-owner: spam
foundation-owner: spam
legal-discuss-owner: spam
mailman-owner: spam
marketing-owner: spam
openstack-announce-owner: spam
openstack-docs-owner: spam
openstack-fr-owner: spam
openstack-i18n-owner: spam
openstack-infra-owner: spam
openstack-ko-owner: spam
openstack-qa-owner: spam
product-wg-owner: spam
user-committee-owner: spam
spam: ':fail: delivery temporarily disabled due to ongoing spam flood'
# TODO It would be better to bypass verification for postorius@listdomain
# and set a :fail: rule for anyone trying to send email to this addr.
# But that requires updating our main exim config so that needs more thought.
postorius: ':blackhole: outgoing email only from this address'
exim_domain_aliases:
community@lists.openstack.org: community@lists.openinfra.dev
edge-computing@lists.openstack.org: edge-computing@lists.opendev.org
foundation@lists.openstack.org: foundation@lists.openinfra.dev
foundation-board@lists.openstack.org: foundation-board@lists.openinfra.dev
foundation-board-confidential@lists.openstack.org: foundation-board-confidential@lists.openinfra.dev
goldmembers@lists.openstack.org: goldmembers@lists.openinfra.dev
marketing@lists.openstack.org: marketing@lists.openinfra.dev
staff@lists.openstack.org: staff@lists.openinfra.dev
summit-programming-committee@lists.openinfra.dev: summit-track-chairs@lists.openinfra.dev
summitsponsors@lists.openstack.org: summitsponsors@lists.openinfra.dev
mailman_sites:
# First entry in this list is the primary web domain
- listdomain: lists.opendev.org
install_languages: ['en']
lists:
- name: computing-force-network
description: 'Organizing efforts around Computing Force Network related area'
owner: 'niujie@outlook.com'
- name: edge-computing
description: 'Organizing efforts around the edge-computing focus area.'
owner: 'ildiko@openinfra.dev'
- name: floss-mooc
description: 'Discussions & Coordination around the FLOSS MOOC being collaboratively developed here: https://gitlab.com/mooc-floss/mooc-floss'
owner: 'knelson@openinfra.dev'
- name: nbmp-discuss
description: 'Collaborating on Network Based Media Processing related platform and infrastructure systems usage and development.'
owner: 'ildiko@openstack.org'
- name: openinfralabs
description: 'Discussion of the OpenInfra Labs academic and research resource sharing effort'
owner: 'mnaser@vexxhost.com'
- name: rust-vmm
description: 'Collaborating on Rust-based virtual machine monitors.'
owner: 'claire@openstack.org'
- name: rustyk8s
description: 'Collaborating on Rust-based Kubernetes API.'
owner: 'allison@lohutok.net'
- name: service-announce
description: 'Announcement list for OpenDev services.'
owner: 'cboylan@sapwetik.org'
- name: service-discuss
description: 'Discussion list for OpenDev services.'
owner: 'cboylan@sapwetik.org'
- name: service-incident
description: 'Private list for OpenDev incident coordination.'
owner: 'cboylan@sapwetik.org'
private: true
# The domains and lists below are currently commented out as we intend on
# deploying a single domain and its lists at a time starting with
# lists.opendev.org. As we deploy other domains we can uncomment these
# blocks. Double check no new lists are been added or removed first.
#- listdomain: lists.airshipit.org
# install_languages: ['en']
# lists:
# - name: airship-announce
# description: 'Announcements of Airship releases and other important information.'
# owner: 'jonathan@openstack.org'
# - name: airship-discuss
# description: 'Discussion of Airship usage and development.'
# owner: 'jonathan@openstack.org'
# - name: airship-embargo-notice
# description: 'Embargoed security vulnerability announcements for Airship consumers.'
# owner: 'andrew.walters@att.com'
# private: true
# - name: airship-job-failures
# description: 'Notification messages for failures from CICD jobs.'
# owner: 'roman.gorshunov@att.com'
# - name: airship-security
# description: 'Public Airship security advisories.'
# owner: 'andrew.walters@att.com'
#- listdomain: lists.katacontainers.io
# install_languages: ['en']
# lists:
# - name: embargo-notice
# description: 'Announcements of embargoed notices for the Kata Containers project'
# owner: 'jonathan@openstack.org'
# private: true
# - name: kata-dev
# description: 'Kata Containers Development Mailing List (not for usage questions)'
# owner: 'jonathan@openstack.org'
# - name: kata-hypervisor
# description: 'Discussion of security and virtualization targeted at container use cases'
# owner: 'jonathan@openstack.org'
#- listdomain: lists.openinfra.dev
# install_languages: ['en']
# lists:
# - name: community
# description: 'The OpenInfra Community team is the main contact point for anybody running a local OpenInfra Group.'
# owner: 'allison@openinfra.dev'
# - name: foundation
# description: 'General discussion list for activities of the OpenInfra Foundation'
# owner: 'jonathan@openinfra.dev'
# - name: foundation-board
# description: 'OpenInfra Foundation Board of Directors'
# owner: 'jonathan@openinfra.dev'
# - name: foundation-board-confidential
# description: 'OpenInfra Foundation Board of Directors'
# owner: 'jonathan@openinfra.dev'
# private: true
# - name: goldmembers
# description: 'The discussion list for Gold Members of the OpenInfra Foundation'
# owner: 'jonathan@openinfra.dev'
# private: true
# - name: marketing
# description: 'The OpenInfra Marketing list is the meant to facilitate discussion and best practice sharing among marketers and event organizers in the OpenInfra community.'
# owner: 'allison@openinfra.dev'
# - name: staff
# description: 'Private list for OpenInfra Foundation staff members'
# owner: 'mark@openinfra.dev'
# private: true
# - name: summit-track-chairs
# description: 'OpenInfra Summit track chair communications'
# owner: 'erin@openinfra.dev'
# private: true
# - name: summitsponsors
# description: 'Coordination among OpenInfra Summit event sponsors'
# owner: 'erin@openinfra.dev'
# private: true
#- listdomain: lists.openstack.org
# install_languages: ['de', 'fr', 'it', 'ko', 'ru', 'vi', 'zh_TW']
# lists:
# - name: embargo-notice
# description: 'Announcements to stakeholders for embargoed security vulnerabilities.'
# owner: 'fungi@yuggoth.org'
# private: true
# - name: legal-discuss
# description: 'Discussions on legal matters related to the project'
# owner: 'thierry@openinfra.dev'
# - name: openstack-announce
# description: 'Key announcements about OpenStack & Security advisories'
# owner: 'fungi@yuggoth.org'
# - name: openstack-discuss
# description: 'Discussion of OpenStack usage and development.'
# owner: 'fungi@yuggoth.org'
# - name: openstack-es
# description: 'Lista de correo acerca de OpenStack en español'
# owner: 'flavio@redhat.com'
# - name: openstack-fr
# description: 'List of the OpenStack french user group'
# owner: 'erwan@erwan.com'
# - name: openstack-hpc
# description: 'High-Performance Computing OpenStack List'
# owner: 'brian.schott@nimbisservices.com'
# - name: openstack-i18n
# description: 'List of the OpenStack Internationalization team.'
# owner: 'guoyingc@cn.ibm.com'
# - name: openstack-it
# description: 'Discussioni su OpenStack in italiano'
# owner: 'stefano@openstack.org'
# - name: openstack-ko
# description: 'OpenStack Korea Community Discussions in Korean (오픈스택 한국 커뮤니티 메일링리스트)'
# owner: 'ianyrchoi@gmail.com'
# - name: openstack-mentoring
# description: 'List to coordinate interactions between mentors and mentees of the OpenStack mentoring program. Also for questions about the mentoring program (i.e. how to get involved, how it works, etc.'
# owner: 'amy@demarco.com'
# - name: openstack-stable-maint
# description: 'A mailing list for the OpenStack Stable Branch test reports.'
# owner: 'tony@bakeyournoodle.com'
# - name: openstack-zh
# description: 'OpenStack社区中文讨论群组'
# owner: 'yeluaiesec@gmail.com'
# - name: release-announce
# description: 'Announcement of official OpenStack releases.'
# owner: 'thierry@openstack.org'
# - name: release-job-failures
# description: 'Notification messages for failures from release-related build jobs.'
# owner: 'doug@doughellmann.com'
#- listdomain: lists.starlingx.io
# install_languages: ['en']
# lists:
# - name: starlingx-announce
# description: 'Announcements of StarlingX releases and other important information.'
# owner: 'jonathan@openstack.org'
# - name: starlingx-discuss
# description: 'Discussion of StarlingX usage and development.'
# owner: 'jonathan@openstack.org'
#- listdomain: lists.zuul-ci.org
# install_languages: ['en']
# lists:
# - name: zuul-announce
# description: 'Announcements of Zuul releases and other important information.'
# owner: 'corvus@inaugust.com'
# - name: zuul-discuss
# description: 'Discussion of Zuul usage and development.'
# owner: 'corvus@inaugust.com'
# - name: zuul-jobs-failures
# description: 'Gets notifications about zuul-jobs periodic job failures.'
# owner: 'ssbarnea@redhat.com'

@ -45,6 +45,9 @@
- name: letsencrypt updated lists-openstack-org-main
include_tasks: roles/letsencrypt-create-certs/handlers/restart_apache.yaml
- name: letsencrypt updated lists-opendev-org-main
include_tasks: roles/letsencrypt-create-certs/handlers/restart_apache.yaml
# Static
- name: letsencrypt updated static-opendev-org-main
include_tasks: roles/letsencrypt-create-certs/handlers/restart_apache.yaml

@ -0,0 +1 @@
Role to configure mailman3.

@ -0,0 +1,10 @@
[mysqldump]
# Default is 24MB which is not large enough for all mailman attachments.
# This affects clients including mysqldump. It is larger than the server
# side because mysqldump apparently can do larger packets to insert blobs.
max_allowed_packet=256M
[mysqld]
# Default is 16MB which is not large enough for all mailman attachments.
# This affects the server side.
max_allowed_packet=128M

@ -0,0 +1,429 @@
# This file has been copied from:
# https://github.com/maxking/docker-mailman/blob/2693386453ff3865b7c106c6aa456b683bd3bf08/web/mailman-web/settings.py
# In order to override the ALLOWED_HOSTS setting.
#
# -*- coding: utf-8 -*-
# Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
#
# This file is part of Mailman Suite.
#
# Mailman Suite is free sofware: 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.
#
# Mailman Suite 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 Mailman Suite. If not, see <http://www.gnu.org/licenses/>.
"""
Django Settings for Mailman Suite (hyperkitty + postorius)
For more information on this file, see
https://docs.djangoproject.com/en/1.8/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.8/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import dj_database_url
import sys
from socket import gethostbyname
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ADMINS = (
('Mailman Suite Admin', 'root@localhost'),
)
SITE_ID = 1
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/3.1/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
"localhost", # Archiving API from Mailman, keep it.
"127.0.0.1", # Archiving API from Mailman, keep it. OpenDev edit
# "lists.your-domain.org",
# Add here all production URLs you may have.
# The next two entries are commented out to prevent name resolution
# problems. This is an opendev local edit.
# Note we cannot use settings_local.py as this entry is evaluated at
# import time.
#"mailman-web",
#gethostbyname("mailman-web"),
os.environ.get('SERVE_FROM_DOMAIN'),
#os.environ.get('DJANGO_ALLOWED_HOSTS'),
]
# We have modified handling of DJANGO_ALLOWED_HOSTS here to deserialize a
# list of hosts into a python list of strings.
django_allowed_hosts = os.environ.get('DJANGO_ALLOWED_HOSTS')
if django_allowed_hosts:
ALLOWED_HOSTS.extend(django_allowed_hosts.split(':'))
# Mailman API credentials
MAILMAN_REST_API_URL = os.environ.get('MAILMAN_REST_URL', 'http://mailman-core:8001')
MAILMAN_REST_API_USER = os.environ.get('MAILMAN_REST_USER', 'restadmin')
MAILMAN_REST_API_PASS = os.environ.get('MAILMAN_REST_PASSWORD', 'restpass')
MAILMAN_ARCHIVER_KEY = os.environ.get('HYPERKITTY_API_KEY')
MAILMAN_ARCHIVER_FROM = (os.environ.get('MAILMAN_HOST_IP', gethostbyname(os.environ.get('MAILMAN_HOSTNAME', 'mailman-core'))),)
# Application definition
INSTALLED_APPS = []
DEFAULT_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',
]
MAILMAN_WEB_SOCIAL_AUTH = [
'django_mailman3.lib.auth.fedora',
'allauth.socialaccount.providers.openid',
'allauth.socialaccount.providers.github',
'allauth.socialaccount.providers.gitlab',
'allauth.socialaccount.providers.google',
]
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'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
# dj_database_url uses $DATABASE_URL environment variable to create a
# django-style-config-dict.
# https://github.com/kennethreitz/dj-database-url
DATABASES = {
'default': dj_database_url.config(conn_max_age=600)
}
# If you're behind a proxy, use the X-Forwarded-Host header
# See https://docs.djangoproject.com/en/1.8/ref/settings/#use-x-forwarded-host
USE_X_FORWARDED_HOST = True
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
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',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_ROOT = '/opt/mailman-web-data/static'
STATIC_URL = '/static/'
# Additional locations of static files
# 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',
)
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
LOGIN_URL = 'account_login'
LOGIN_REDIRECT_URL = 'list_index'
LOGOUT_URL = 'account_logout'
# Use SERVE_FROM_DOMAIN as the default domain in the email.
hostname = os.environ.get('SERVE_FROM_DOMAIN', 'localhost.local')
DEFAULT_FROM_EMAIL = 'postorius@{}'.format(hostname)
SERVER_EMAIL = 'root@{}'.format(hostname)
# Change this when you have a real email backend
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('SMTP_HOST', '')
EMAIL_PORT = os.environ.get('SMTP_PORT', 25)
EMAIL_HOST_USER = os.environ.get('SMTP_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('SMTP_HOST_PASSWORD', '')
EMAIL_USE_TLS = os.environ.get('SMTP_USE_TLS', False)
EMAIL_USE_SSL = os.environ.get('SMTP_USE_SSL', False)
# 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"
# You probably want https in production, but this is a dev setup file
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
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',
},
}
# django-compressor
# https://pypi.python.org/pypi/django_compressor
#
COMPRESS_PRECOMPILERS = (
('text/less', 'lessc {infile} {outfile}'),
('text/x-scss', 'sassc -t compressed {infile} {outfile}'),
('text/x-sass', 'sassc -t compressed {infile} {outfile}'),
)
# On a production setup, setting COMPRESS_OFFLINE to True will bring a
# significant performance improvement, as CSS files will not need to be
# recompiled on each requests. It means running an additional "compress"
# management command after each code upgrade.
# http://django-compressor.readthedocs.io/en/latest/usage/#offline-compression
# COMPRESS_OFFLINE = True
#
# Full-text search engine
#
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': "/opt/mailman-web-data/fulltext_index",
# You can also use the Xapian engine, it's faster and more accurate,
# but requires another library.
# http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian
# Example configuration for Xapian:
#'ENGINE': 'xapian_backend.XapianEngine'
},
}
import sys
# 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/dev/topics/logging for
# more details on how to customize your logging configuration.
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.environ.get('DJANGO_LOG_URL','/opt/mailman-web-data/logs/mailmanweb.log'),
'formatter': 'verbose',
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
'level': 'INFO',
'stream': sys.stdout,
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins', 'file'],
'level': 'INFO',
'propagate': True,
},
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
'hyperkitty': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
'postorius': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True
},
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
}
if os.environ.get('LOG_TO_CONSOLE') == 'yes':
LOGGING['loggers']['django']['handlers'].append('console')
LOGGING['loggers']['django.request']['handlers'].append('console')
# HyperKitty-specific
#
# Only display mailing-lists from the same virtual host as the webserver
FILTER_VHOST = False
Q_CLUSTER = {
'timeout': 300,
'retry': 300,
'save_limit': 100,
'orm': 'default',
}
POSTORIUS_TEMPLATE_BASE_URL = os.environ.get('POSTORIUS_TEMPLATE_BASE_URL', 'http://mailman-web:8000')
DISKCACHE_PATH = os.environ.get('DISKCACHE_PATH', '/opt/mailman-web-data/diskcache')
DISKCACHE_SIZE = os.environ.get('DISKCACHE_SIZE', 2 ** 30) # 1 gigabyte
CACHES = {
'default': {
'BACKEND': 'diskcache.DjangoCache',
'LOCATION': DISKCACHE_PATH,
'OPTIONS': {
'size_limit': DISKCACHE_SIZE,
},
},
}
try:
from settings_local import *
except ImportError:
pass
# Compatibility for older installs that override INSTALLED_APPS
if not INSTALLED_APPS:
INSTALLED_APPS = DEFAULT_APPS + MAILMAN_WEB_SOCIAL_AUTH

@ -0,0 +1,21 @@
# Override default index system. Xapian will be the default in the next
# major release of mailman3, but is currently recommended.
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'xapian_backend.XapianEngine',
'PATH': "/opt/mailman-web-data/fulltext_index",
},
}
# Disable Gravatar integration since it violates privacy expectations
HYPERKITTY_ENABLE_GRAVATAR = False
# This disables web auth using Google, GitHub, Gitlab, Yahoo,
# Fedora, and generic OpenID.
# TODO: In the future we will want to enable this specifically for
# our keycloak server only.
MAILMAN_WEB_SOCIAL_AUTH = []
FILTER_VHOST = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -0,0 +1,9 @@
- name: mailman restart apache2
service:
name: apache2
state: restarted
- name: mailman reload apache2
service:
name: apache2
state: reloaded

@ -0,0 +1,114 @@
- name: Check if domain exists
uri:
url: 'http://localhost:8001/3.1/domains/{{ mm_site.listdomain }}'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: GET
body_format: json
status_code: [200, 404]
register: domain_exists
no_log: true
- name: Create list domain in mm3
when: domain_exists.status == 404
uri:
url: 'http://localhost:8001/3.1/domains'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: POST
body_format: json
body:
mail_host: "{{ mm_site.listdomain }}"
status_code: [201]
no_log: true
- name: Check if list exists
uri:
url: 'http://localhost:8001/3.1/lists/{{ mm_list.name }}@{{ mm_site.listdomain }}'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: GET
body_format: json
status_code: [200, 404]
register: list_exists
loop: "{{ mm_site.lists }}"
loop_control:
loop_var: mm_list
no_log: true
- name: Create lists in mm3
when: list_exists.results[exists_idx].status == 404
uri:
url: 'http://localhost:8001/3.1/lists'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: POST
body_format: json
body:
fqdn_listname: "{{ mm_list.name }}@{{ mm_site.listdomain }}"
style_name: "{{ mm_list.private | default('false') | bool | ternary('private-default', 'legacy-default') }}"
status_code: [201]
loop: "{{ mm_site.lists }}"
loop_control:
loop_var: mm_list
index_var: exists_idx
no_log: true
- name: Set list properties in mm3
when: list_exists.results[exists_idx].status == 404
uri:
url: 'http://localhost:8001/3.1/lists/{{ mm_list.name }}@{{ mm_site.listdomain }}/config'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: PATCH
body_format: json
body:
description: "{{ mm_list.description }}"
advertised: "{{ mm_list.private | default('false') | bool | ternary('false', 'true') }}"
# TODO enable this when lynx is present on the container images
# convert_html_to_plaintext: "true"
process_bounces: "false"
filter_extensions:
- "exe"
- "bat"
- "cmd"
- "com"
- "pif"
- "scr"
- "vbs"
- "cpl"
pass_types:
- "multipart/mixed"
- "multipart/alternative"
- "text/plain"
status_code: [204]
loop: "{{ mm_site.lists }}"
loop_control:
loop_var: mm_list
index_var: exists_idx
no_log: true
- name: Set list owner in mm3
when: list_exists.results[exists_idx].status == 404
uri:
url: 'http://localhost:8001/3.1/members'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: POST
body_format: json
body:
list_id: "{{ mm_list.name }}.{{ mm_site.listdomain }}"
subscriber: "{{ mm_list.owner }}"
role: "owner"
status_code: [201]
loop: "{{ mm_site.lists }}"
loop_control:
loop_var: mm_list
index_var: exists_idx
no_log: true

@ -0,0 +1,278 @@
# The old mailman2 exim config refers to this file. Write it out
# to make basic testing happy, but we may need to clean it up or
# modify it for mailman3.
- name: Write /etc/aliases.domain
template:
src: "domain_aliases.j2"
dest: "/etc/aliases.domain"
mode: '0444'
- name: Create Mailman Group
group:
name: mailman
gid: 10010
system: yes
- name: Create Mailman User
user:
name: mailman
uid: 10010
comment: Mailman User
shell: /bin/bash
home: /var/lib/mailman
group: mailman
create_home: yes
system: yes
#### Install Mailman ####
- name: Ensure Mailman core volume directory exists
file:
state: directory
path: "/var/lib/mailman/core"
# TODO: undo for https://github.com/maxking/docker-mailman/issues/550
owner: 100
group: 65533
mode: '0755'
- name: Ensure Mailman database volume directory exists
file:
state: directory
path: "/var/lib/mailman/database"
# TODO: undo for https://github.com/maxking/docker-mailman/issues/550
owner: 999
group: 999
mode: '0755'
- name: Ensure Mailman web volume directories exist
file:
state: directory
path: "/var/lib/mailman/{{ item }}"
# TODO: undo for https://github.com/maxking/docker-mailman/issues/550
owner: 100
group: 101
mode: '0755'
loop:
- import
- web
- web-data
- web-data/fulltext_index
- web-data/mm2archives
- name: Copy our overridden settings.py for mailman-web
copy:
src: web-settings.py
dest: /var/lib/mailman/web/settings.py
# TODO: undo for https://github.com/maxking/docker-mailman/issues/550
owner: 100
group: 101
mode: '0644'
- name: Copy our settings_local.py for mailman-web
copy:
src: web-settings_local.py
dest: /var/lib/mailman/web-data/settings_local.py
# TODO: undo for https://github.com/maxking/docker-mailman/issues/550
owner: 100
group: 101
mode: '0644'
- name: Copy our max_allowed_packet override config
copy:
src: 99-max_allowed_packet.cnf
dest: /var/lib/mailman/99-max_allowed_packet.cnf
owner: 999
group: 999
mode: '0644'
- name: Ensure /etc/mailman-compose directory
file:
state: directory
path: /etc/mailman-compose
mode: '0755'
- name: Put docker-compose file in place
template:
src: docker-compose.yaml.j2
dest: /etc/mailman-compose/docker-compose.yaml
mode: '0600'
- name: Run docker-compose pull
shell:
cmd: docker-compose pull
chdir: /etc/mailman-compose/
- name: Run docker-compose up
shell:
cmd: docker-compose up -d
chdir: /etc/mailman-compose/
- name: Run docker prune to cleanup unneeded images
shell:
cmd: docker image prune -f
- name: Install apache2
package:
name:
- apache2
- apache2-utils
state: present
- name: Apache modules
apache2_module:
state: present
name: "{{ a2_mod }}"
loop:
- authz_host
- proxy
- proxy_uwsgi
- ssl
- rewrite
loop_control:
loop_var: a2_mod
notify: mailman restart apache2
- name: Make sure packaged default site disabled
command: a2dissite 000-default.conf
args:
removes: /etc/apache2/sites-enabled/000-default.conf
- name: Create mailman vhost config
template:
src: mailman.vhost.j2
dest: "/etc/apache2/sites-enabled/50-{{ mailman_sites.0.listdomain }}.conf"
owner: root
group: root
mode: '0644'
notify: mailman reload apache2
- name: Enable apache2 server
service:
name: "apache2"
enabled: yes
#### Configure Mailman Services ####
- name: Wait for mm3 REST API to be up and running
uri:
url: 'http://localhost:8001/3.1/domains'
url_username: restadmin
url_password: "{{ mailman3_rest_password }}"
force_basic_auth: yes
method: GET
register: mm_rest_api_up
delay: 1
retries: 300
until: mm_rest_api_up and mm_rest_api_up.status == 200
no_log: true
# It has been difficult to nail down a reliable mathod for determining
# when the database is sufficiently populated that we can create the django
# admin user. We apply a number of approaches in response to this. If we
# can identify a single method that is reliable this list can be trimmed.
- name: Wait for DB to be populated
command: >
docker exec mailman-compose_database_1 bash -c
'mysql -u mailman -p"$MYSQL_PASSWORD" -D mailmandb -e
"SHOW TABLES LIKE \"auth_user\";"'
register: django_db_exists
delay: 1
retries: 300
until: django_db_exists.stdout_lines | length > 1 and django_db_exists.stdout_lines[1] == "auth_user"
- name: Wait for DB to be populated second approach
command: >
docker exec mailman-core alembic -c /usr/lib/python3.9/site-packages/mailman/config/alembic.cfg current
register: alembic_version
delay: 1
retries: 300
until: alembic_version.stdout_lines | length > 0 and "(head)" in alembic_version.stdout_lines[0]
- name: Wait for DB to be populated third approach
shell: >
docker exec mailman-web bash -c
'python3 manage.py showmigrations' |
grep -q '^ \[ \] [0-9]\+_.*'
register: django_db_migrations
delay: 1
retries: 300
failed_when: false
# When grep stops matching the empty '[ ]' that indicates all migrations
# are marked with '[X]' and are complete. Grep returns non zero when we
# reach this point.
until: django_db_migrations.rc != 0
- name: Check if django admin user exists
command: >
docker exec mailman-compose_database_1 bash -c
'mysql -u mailman -p"$MYSQL_PASSWORD" -D mailmandb -e
"SELECT COUNT(id) FROM auth_user WHERE id = 1 AND is_superuser = 1;"'
register: django_admin_exists
- name: Create django admin user
when: django_admin_exists.stdout_lines[1] == "0"
command: >
docker exec mailman-web bash -c
"DJANGO_SUPERUSER_PASSWORD={{ mailman3_admin_password }}
python3 manage.py createsuperuser --no-input
--username {{ mailman3_admin_user }}
--email '{{ mailman3_admin_email }}'"
no_log: true
- name: Create lists in mm3
include_tasks: create_lists.yaml
loop: "{{ mailman_sites }}"
loop_control:
loop_var: mm_site
#### Logrotate for service logs ####
- name: Rotate mailman logs
include_role:
name: logrotate
vars:
logrotate_rotate: 90
logrotate_file_name: '/var/lib/mailman/web-data/logs/*.log'
#### Database Backups ####
- name: Create db backup dest
file:
state: directory
path: /var/backups/mailman-mariadb
mode: 0700
owner: root
group: root
- name: Set up cron job to backup the database
cron:
name: mailman-db-backup
state: present
user: root
job: >
/usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T database
bash -c '/usr/bin/mysqldump --opt --databases mailmandb --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"' |
gzip -9 > /var/backups/mailman-mariadb/mailman-mariadb.sql.gz
minute: 14
hour: 5
- name: Rotate db backups
include_role:
name: logrotate
vars:
logrotate_file_name: /var/backups/mailman-mariadb/mailman-mariadb.sql.gz
logrotate_compress: false
- name: Setup db backup streaming job
block:
- name: Create backup streaming config dir
file:
path: /etc/borg-streams
state: directory
- name: Create db streaming file
copy:
content: >-
/usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T mariadb
bash -c '/usr/bin/mysqldump --skip-extended-insert --databases mailmandb --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"'
dest: /etc/borg-streams/mysql

@ -0,0 +1,71 @@
# Adapted from https://github.com/maxking/docker-mailman/blob/2693386453ff3865b7c106c6aa456b683bd3bf08/docker-compose-mysql.yaml
# which is an MIT licensed repo.
version: '2'
services:
mailman-core:
image: docker.io/maxking/mailman-core:0.4
restart: always
container_name: mailman-core
volumes:
- /var/lib/mailman/core:/opt/mailman/
- /var/lib/mailman/import:/opt/import
stop_grace_period: 30s
depends_on:
- database
environment:
- DATABASE_URL=mysql+pymysql://mailman:{{ mailman3_db_password }}@127.0.0.1:3306/mailmandb?charset=utf8mb4&use_unicode=1
- DATABASE_TYPE=mysql
- DATABASE_CLASS=mailman.database.mysql.MySQLDatabase
- HYPERKITTY_URL=http://127.0.0.1:8000/hyperkitty
- HYPERKITTY_API_KEY={{ mailman3_hyperkitty_api_key }}
- SMTP_HOST=localhost
- MM_HOSTNAME=localhost
- MAILMAN_REST_USER=restadmin
- MAILMAN_REST_PASSWORD={{ mailman3_rest_password }}
network_mode: host
#user: mailman
mailman-web:
image: docker.io/maxking/mailman-web:0.4
restart: always
container_name: mailman-web
depends_on:
- database
volumes:
- /var/lib/mailman/import:/opt/import
- /var/lib/mailman/web-data:/opt/mailman-web-data
- /var/lib/mailman/web/settings.py:/opt/mailman-web/settings.py
environment:
# Testing to see if these are really necessary
#- MAILMAN_ADMIN_USER={{ mailman3_admin_user }}
#- MAILMAN_ADMIN_EMAIL={{ mailman3_admin_email }}
- SERVE_FROM_DOMAIN=lists.opendev.org
- DJANGO_ALLOWED_HOSTS={{ mm_domains }}
- DATABASE_TYPE=mysql
- DATABASE_URL=mysql://mailman:{{ mailman3_db_password }}@127.0.0.1:3306/mailmandb?charset=utf8mb4
- HYPERKITTY_API_KEY={{ mailman3_hyperkitty_api_key }}
- SECRET_KEY={{ mailman3_django_secret_key }}
- DYLD_LIBRARY_PATH=/usr/local/mysql/lib/
- MAILMAN_HOSTNAME=localhost
- MAILMAN_REST_URL=http://127.0.0.1:8001
- MAILMAN_REST_USER=restadmin
- MAILMAN_REST_PASSWORD={{ mailman3_rest_password }}
- POSTORIUS_TEMPLATE_BASE_URL=http://127.0.0.1:8000
- SMTP_HOST=localhost
network_mode: host
#user: mailman
database:
environment:
MYSQL_DATABASE: mailmandb
MYSQL_USER: mailman
MYSQL_PASSWORD: {{ mailman3_db_password }}
MYSQL_ROOT_PASSWORD: {{ mailman3_db_root_password }}
image: docker.io/library/mariadb:10.6
restart: always
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- /var/lib/mailman/database:/var/lib/mysql
- /var/lib/mailman/99-max_allowed_packet.cnf:/etc/mysql/conf.d/99-max_allowed_packet.cnf:ro
network_mode: host

@ -0,0 +1,6 @@
# /etc/aliases.domain
{% for k, v in exim_domain_aliases|dictsort %}
{% if v %}
{{ k }}: {{ v }}
{% endif %}
{% endfor %}

@ -0,0 +1,69 @@
<VirtualHost *:80>
ServerName {{ mailman_sites.0.listdomain }}
{% for site in mailman_sites[1:] -%}
ServerAlias {{ site.listdomain }}
{% endfor -%}
ErrorLog ${APACHE_LOG_DIR}/{{ mailman_sites.0.listdomain }}-error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/{{ mailman_sites.0.listdomain }}-access.log combined
# Use mod rewrite to redirect as we want to preserve the FQDN for each
# mm3 vhost.
RewriteEngine On
RewriteRule "/(.*)" "https://%{HTTP_HOST}/$1" [R=301]
</VirtualHost>
<VirtualHost *:443>
ServerName {{ mailman_sites.0.listdomain }}
{% for site in mailman_sites[1:] -%}
ServerAlias {{ site.listdomain }}
{% endfor -%}
ServerAdmin webmaster@openstack.org
ErrorLog ${APACHE_LOG_DIR}/{{ mailman_sites.0.listdomain }}-ssl-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/{{ mailman_sites.0.listdomain }}-ssl-access.log combined
SSLEngine on
SSLProtocol All -SSLv2 -SSLv3
# Note: this list should ensure ciphers that provide forward secrecy
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!AES256:!aNULL:!eNULL:!MD5:!DSS:!PSK:!SRP
SSLHonorCipherOrder on
SSLCertificateFile /etc/letsencrypt-certs/{{ inventory_hostname }}/{{ inventory_hostname }}.cer
SSLCertificateKeyFile /etc/letsencrypt-certs/{{ inventory_hostname }}/{{ inventory_hostname }}.key
SSLCertificateChainFile /etc/letsencrypt-certs/{{ inventory_hostname }}/ca.cer
Alias /static /var/lib/mailman/web-data/static
Alias /favicon.ico /var/lib/mailman/web-data/static/hyperkitty/img/favicon.ico
<Location "/admin">
Require local
</Location>
RewriteEngine On
RewriteRule "/pipermail/(.*)" "/var/lib/mailman/web-data/mm2archives/%{HTTP_HOST}/public/$1"
RewriteRule "/cgi-bin/mailman/listinfo/(.*)" "https://%{HTTP_HOST}/postorius/lists/$1.%{HTTP_HOST}/"
RewriteRule "/cgi-bin/mailman/listinfo" "https://%{HTTP_HOST}/postorius/lists/"
ProxyPassMatch ^/static/ !
ProxyPass "/" "uwsgi://localhost:8080/"
<Directory /var/lib/mailman/web-data/static/>
AllowOverride None
Order allow,deny
Allow from all
Require all granted
</Directory>
<Directory /var/lib/mailman/web-data/mm2archives/>
AllowOverride None
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>

@ -0,0 +1,10 @@
# Maintaing a todo list here as a central accounting file
# TODO Test mailman dmarc settings
# TODO fix container uid/gid mismatch with bind mounted contents
# this breaks xapian
- hosts: "mailman3:!disabled"
name: "Configure mailman3 servers"
roles:
- iptables
- install-docker
- mailman3

@ -0,0 +1,294 @@
mailman_list_password: notarealpassword
mailman3_db_password: Eith5vii5beezohc
mailman3_db_root_password: eiloh9Edohngaeri
mailman3_hyperkitty_api_key: Thosai4Xomeque9e
mailman3_django_secret_key: ohki3ohWusai8tee
mailman3_rest_password: OhTo3doh5ohsuope
mailman3_admin_user: admin
mailman3_admin_email: infra-root@openstack.org
mailman3_admin_password: AeNie8vegeiquei1
mm_domains: 'lists.openstack.org:lists.zuul-ci.org:lists.airshipit.org:lists.starlingx.io:lists.opendev.org:lists.openinfra.dev:lists.katacontainers.io'
exim_local_domains: "@:{{ mm_domains }}"
exim_enable_spf: true
exim_aliases:
root: "{{ ','.join(listadmins|default([])) }}"
interop-wg: openstack-discuss
openstack: openstack-discuss
openstack-dev: openstack-discuss
openstack-infra: openstack-discuss
openstack-operators: openstack-discuss
openstack-security: openstack-discuss
openstack-sigs: openstack-discuss
openstack-tc: openstack-discuss
user-committee: openstack-discuss
airship-discuss-owner: spam
community-owner: spam
edge-computing-owner: spam
foundation-board-confidential-owner: spam
foundation-board-owner: spam
foundation-owner: spam
legal-discuss-owner: spam
mailman-owner: spam
marketing-owner: spam
openstack-announce-owner: spam
openstack-docs-owner: spam
openstack-fr-owner: spam
openstack-i18n-owner: spam
openstack-infra-owner: spam
openstack-ko-owner: spam
openstack-qa-owner: spam
product-wg-owner: spam
user-committee-owner: spam
spam: ':fail: delivery temporarily disabled due to ongoing spam flood'
# TODO It would be better to bypass verification for postorius@listdomain
# and set a :fail: rule for anyone trying to send email to this addr.
# But that requires updating our main exim config so that needs more thought.
postorius: ':blackhole: outgoing email only from this address'
exim_domain_aliases:
community@lists.openstack.org: community@lists.openinfra.dev
edge-computing@lists.openstack.org: edge-computing@lists.opendev.org
foundation@lists.openstack.org: foundation@lists.openinfra.dev
foundation-board@lists.openstack.org: foundation-board@lists.openinfra.dev
foundation-board-confidential@lists.openstack.org: foundation-board-confidential@lists.openinfra.dev
goldmembers@lists.openstack.org: goldmembers@lists.openinfra.dev
marketing@lists.openstack.org: marketing@lists.openinfra.dev
staff@lists.openstack.org: staff@lists.openinfra.dev
summit-programming-committee@lists.openinfra.dev: summit-track-chairs@lists.openinfra.dev
summitsponsors@lists.openstack.org: summitsponsors@lists.openinfra.dev
exim_routers:
- mailman_verp_router: |
{% raw -%}
driver = dnslookup
condition = ${if or{{eq{$sender_host_address}{127.0.0.1}}\
{eq{$sender_host_address}{::1}}}{yes}{no}}
{% endraw %}
domains = !+local_domains
ignore_target_hosts = <; 0.0.0.0; \
64.94.110.11; \
127.0.0.0/8; \
::1/128;fe80::/10;fe \
c0::/10;ff00::/8
senders = "*-bounces@*"
transport = mailman_verp_smtp
- dnslookup: '{{ exim_dnslookup_router }}'
- system_aliases: '{{ exim_system_aliases_router }}'
- domain_aliases: |
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain}lsearch{/etc/aliases.domain}}
file_transport = address_file
pipe_transport = address_pipe
- localuser: '{{ exim_localuser_router }}'
- mailman_copy: |
driver = accept
domains = lists.openstack.org
local_parts = openstack-discuss
transport = local_copy
unseen
- mailman_router: |
driver = accept
domains = {{ mm_domains }}
local_part_suffix = -admin : \
-bounces : -bounces+* : \
-confirm : -confirm+* : \
-join : -leave : \
-owner : -request : \
-subscribe : -unsubscribe
local_part_suffix_optional
require_files = /var/lib/mailman/core/var/lists/${local_part}.${domain}
transport = mailman_transport
exim_transports:
- local_copy: |
driver = appendfile
file = /var/mail/$local_part
group = mail
mode = 0660
- mailman_transport: |
debug_print = "Email for mailman"
driver = smtp
protocol = lmtp
allow_localhost
hosts = localhost
port = 8024
rcpt_include_affixes = true
- mailman_verp_smtp: |
driver = smtp
headers_add = Errors-To: ${return_path}
headers_remove = Errors-To
max_rcpt = 1
return_path = ${local_part:$return_path}+$local_part=$domain@${domain:$return_path}
mailman_multihost: true
mailman_sites:
# First entry in this list is the primary web domain
- listdomain: lists.opendev.org
install_languages: ['en']
lists:
- name: computing-force-network
description: 'Organizing efforts around Computing Force Network related area'
owner: 'niujie@outlook.com'
- name: edge-computing
description: 'Organizing efforts around the edge-computing focus area.'
owner: 'ildiko@openinfra.dev'
- name: floss-mooc
description: 'Discussions & Coordination around the FLOSS MOOC being collaboratively developed here: https://gitlab.com/mooc-floss/mooc-floss'
owner: 'knelson@openinfra.dev'
- name: nbmp-discuss
description: 'Collaborating on Network Based Media Processing related platform and infrastructure systems usage and development.'
owner: 'ildiko@openstack.org'
- name: openinfralabs
description: 'Discussion of the OpenInfra Labs academic and research resource sharing effort'
owner: 'mnaser@vexxhost.com'
- name: rust-vmm
description: 'Collaborating on Rust-based virtual machine monitors.'
owner: 'claire@openstack.org'
- name: rustyk8s
description: 'Collaborating on Rust-based Kubernetes API.'
owner: 'allison@lohutok.net'
- name: service-announce
description: 'Announcement list for OpenDev services.'
owner: 'cboylan@sapwetik.org'
- name: service-discuss
description: 'Discussion list for OpenDev services.'
owner: 'cboylan@sapwetik.org'
- name: service-incident
description: 'Private list for OpenDev incident coordination.'
owner: 'cboylan@sapwetik.org'
private: true
- listdomain: lists.airshipit.org
install_languages: ['en']
lists:
- name: airship-announce
description: 'Announcements of Airship releases and other important information.'
owner: 'jonathan@openstack.org'
- name: airship-discuss
description: 'Discussion of Airship usage and development.'
owner: 'jonathan@openstack.org'
- name: airship-embargo-notice
description: 'Embargoed security vulnerability announcements for Airship consumers.'