├── core
├── VERSION
├── requirements.txt
├── assets
│ ├── exim
│ │ ├── 55_mm3_transport
│ │ ├── 455_mm3_router
│ │ └── 25_mm3_macros
│ ├── mailman-hyperkitty.cfg
│ └── mailman.cfg
├── Dockerfile.dev
├── Dockerfile
├── README.md
└── docker-entrypoint.sh
├── docs
├── news.md
├── index.md
├── web.md
└── core.md
├── web
├── VERSION
├── mailman-web
│ ├── __init__.py
│ ├── manage.py
│ ├── wsgi.py
│ ├── urls.py
│ ├── uwsgi.ini
│ └── settings.py
├── requirements.txt
├── Dockerfile
├── Dockerfile.dev
├── docker-entrypoint.sh
└── README.md
├── .github
├── workflows
│ ├── stale.yml
│ ├── main.workflow
│ ├── publish_docs.yml
│ └── main.yml
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── dependabot.yml
├── postorius
├── mailman-web
│ ├── __init__.py
│ ├── manage.py
│ ├── uwsgi.ini
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── Dockerfile
├── Dockerfile.dev
└── docker-entrypoint.sh
├── .gitignore
├── tests
├── docker-test.yaml
└── test.sh
├── mkdocs.yml
├── LICENSE
├── docker-compose-postorius.yaml
├── .circleci
└── config.yml
├── docker-compose-mysql.yaml
├── docker-compose.yaml
├── NEWS.md
├── deploy.py
└── README.md
/core/VERSION:
--------------------------------------------------------------------------------
1 | 0.1.1
2 |
--------------------------------------------------------------------------------
/docs/news.md:
--------------------------------------------------------------------------------
1 | ../NEWS.md
--------------------------------------------------------------------------------
/web/VERSION:
--------------------------------------------------------------------------------
1 | 0.1.1
2 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/docs/web.md:
--------------------------------------------------------------------------------
1 | ../web/README.md
--------------------------------------------------------------------------------
/web/mailman-web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/core.md:
--------------------------------------------------------------------------------
1 | ../core/README.md
--------------------------------------------------------------------------------
/postorius/mailman-web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/requirements.txt:
--------------------------------------------------------------------------------
1 | mailmanclient==3.3.5
2 | postorius==1.3.13
3 | hyperkitty==1.3.12
4 | django-mailman3==1.3.15
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | */.sass-cache/*
2 | *.log
3 | *.log
4 | *.sass-cache/
5 | /web/mailman-web/settings_local.py
6 | pythonenv3.8/*
7 | .venv/*
8 |
--------------------------------------------------------------------------------
/core/requirements.txt:
--------------------------------------------------------------------------------
1 | # This is a separate file from Dockerfile so that we can use dependabot
2 | # for version updates that isn't supported for contents inside the
3 | # Dockerfile.
4 | mailman==3.3.10
5 | mailman-hyperkitty==1.2.1
--------------------------------------------------------------------------------
/.github/workflows/main.workflow:
--------------------------------------------------------------------------------
1 | workflow "Add PR to release notes" {
2 | on = "pull_request"
3 | resolves = ["Chronicler"]
4 | }
5 |
6 | action "Chronicler" {
7 | uses = "crosscompile/chronicler-action@v1.0.0"
8 | secrets = ["GITHUB_TOKEN"]
9 | }
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Tag/version of Container Images**
11 | Choose from: rolling, 0.3, 0.3.*, 0.4.*
12 |
--------------------------------------------------------------------------------
/tests/docker-test.yaml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | mailman-core:
5 | image: maxking/mailman-core:rolling
6 |
7 | mailman-web:
8 | image: maxking/mailman-web:rolling
9 | environment:
10 | - SECRET_KEY=abcdefghijklmnopqrstuv
11 | - SERVE_FROM_DOMAIN=araj.me
12 |
--------------------------------------------------------------------------------
/web/mailman-web/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/postorius/mailman-web/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/core/assets/exim/55_mm3_transport:
--------------------------------------------------------------------------------
1 | # Place this file at
2 | # /etc/exim4/conf.d/transport/55_mm3_transport
3 |
4 | mailman3_transport:
5 | debug_print = "Email for mailman"
6 | driver = smtp
7 | protocol = lmtp
8 | allow_localhost
9 | hosts = MM3_LMTP_HOST
10 | port = MM3_LMTP_PORT
11 | rcpt_include_affixes = true
12 |
--------------------------------------------------------------------------------
/core/assets/mailman-hyperkitty.cfg:
--------------------------------------------------------------------------------
1 | [general]
2 | # This is your HyperKitty installation, preferably on the localhost. This
3 | # address will be used by Mailman to forward incoming emails to HyperKitty
4 | # for archiving. It does not need to be publicly available, in fact it's
5 | # better if it is not.
6 | base_url: http://mailman-web:8000/hyperkitty/
7 | # Shared API key, must be the identical to the value in HyperKitty's
8 | # settings.
9 | api_key: ASmallAPIKey
10 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: docker-mailman
2 | site_description: Container images for GNU Mailman
3 |
4 | theme:
5 | name: material
6 |
7 | repo_name: maxking/docker-mailman
8 | repo_url: https://github.com/maxking/docker-mailman
9 | use_directory_urls: true
10 | nav:
11 | - Home: "index.md"
12 | - Mailman Core: "core.md"
13 | - Mailman Web: "web.md"
14 | - News: "news.md"
15 |
16 | markdown_extensions:
17 | - toc:
18 | permalink: true
19 | toc_depth: 3
20 |
--------------------------------------------------------------------------------
/core/assets/exim/455_mm3_router:
--------------------------------------------------------------------------------
1 | # Place this file at
2 | # /etc/exim4/conf.d/router/455_mm3_router
3 |
4 | mailman3_router:
5 | driver = accept
6 | domains = +mm3_domains
7 | require_files = MM3_LISTCHK
8 | local_part_suffix_optional
9 | local_part_suffix = -admin : \
10 | -bounces : -bounces+* : \
11 | -confirm : -confirm+* : \
12 | -join : -leave : \
13 | -owner : -request : \
14 | -subscribe : -unsubscribe
15 | transport = mailman3_transport
16 |
--------------------------------------------------------------------------------
/core/assets/exim/25_mm3_macros:
--------------------------------------------------------------------------------
1 | # Place this file at
2 | # /etc/exim4/conf.d/main/25_mm3_macros
3 |
4 | domainlist mm3_domains=MY_DOMAIN_NAME
5 | # Depending on your network configuration
6 | #MM3_LMTP_HOST=mailman-core
7 | MM3_LMTP_HOST=localhost
8 | MM3_LMTP_PORT=8024
9 | MM3_HOME=/opt/mailman/core/var
10 |
11 | ################################################################
12 | # The configuration below is boilerplate:
13 | # you should not need to change it.
14 |
15 | # The path to the list receipt (used as the required file when
16 | # matching list addresses)
17 | MM3_LISTCHK=MM3_HOME/lists/${local_part}.${domain}
18 |
--------------------------------------------------------------------------------
/.github/workflows/publish_docs.yml:
--------------------------------------------------------------------------------
1 |
2 |
3 | name: Publish docs via GitHub Pages
4 | on:
5 | push:
6 | branches:
7 | - main
8 | - master
9 |
10 | jobs:
11 | build:
12 | name: Deploy docs
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout main
16 | uses: actions/checkout@v5
17 |
18 | - name: Deploy docs
19 | uses: mhausenblas/mkdocs-deploy-gh-pages@master
20 | # Or use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to build without the mkdocs-material theme
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | CUSTOM_DOMAIN: asynchronous.in
24 | CONFIG_FILE: mkdocs.yml
25 |
--------------------------------------------------------------------------------
/postorius/mailman-web/uwsgi.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | # Port on which uwsgi will be listening.
3 | uwsgi-socket = 0.0.0.0:8080
4 | http-socket = 0.0.0.0:8000
5 |
6 | # Move to the directory where the django files are.
7 | chdir = /opt/mailman-web
8 |
9 | # Use the wsgi file provided with the django project.
10 | wsgi-file = wsgi.py
11 |
12 | # Setup default number of processes and threads per process.
13 | master = true
14 | processes = 2
15 | threads = 2
16 |
17 | # Drop privileges and don't run as root.
18 | uid = mailman
19 | gid = mailman
20 |
21 | # Setup the request log.
22 | req-logger = file:/opt/mailman-web-data/logs/uwsgi.log
23 |
24 | # Last log and it logs the rest of the stuff.
25 | logger = file:/opt/mailman-web-data/logs/uwsgi-error.log
26 |
--------------------------------------------------------------------------------
/core/assets/mailman.cfg:
--------------------------------------------------------------------------------
1 | [mta]
2 | incoming: mailman.mta.exim4.LMTP
3 | outgoing: mailman.mta.deliver.deliver
4 | lmtp_host: mailman-core
5 | lmtp_port: 8024
6 | smtp_host: 172.19.199.1
7 | smtp_port: 25
8 | configuration: python:mailman.config.exim4
9 |
10 | # [archiver.mhonarc]
11 | # enable: yes
12 |
13 | # [archiver.mail_archive]
14 | # enable: yes
15 |
16 | # [archiver.prototype]
17 | # enable: yes
18 |
19 | [runner.retry]
20 | sleep_time: 10s
21 |
22 | [shell]
23 | use_ipython: yes
24 |
25 | [webservice]
26 | hostname: mailman-core
27 |
28 | [archiver.hyperkitty]
29 | class: mailman_hyperkitty.Archiver
30 | enable: yes
31 | configuration: /opt/mailman/mailman-hyperkitty.cfg
32 |
33 | [database]
34 | class: mailman.database.postgresql.PostgreSQLDatabase
35 | url: postgres://mailman:mailmanpass@database/mailmandb
36 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the action will run.
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | # This workflow contains a single job called "build"
19 | build:
20 | # The type of runner that the job will run on
21 | runs-on: ubuntu-latest
22 |
23 | # Steps represent a sequence of tasks that will be executed as part of the job
24 | steps:
25 | - name: Chronicler Action
26 | # You may pin to the exact commit or the version.
27 | # uses: crosscompile/chronicler-action@5c25dbce26b0789724a92902c69217b46f023b51
28 | uses: crosscompile/chronicler-action@v1.0.1
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Abhilash Raj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/web/mailman-web/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for HyperKitty project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | # import sys
13 | # import site
14 |
15 | # For some unknown reason, sometimes mod_wsgi fails to set the python paths to
16 | # the virtualenv, with the 'python-path' option. You can do it here too.
17 | #
18 | # # Remember original sys.path.
19 | # prev_sys_path = list(sys.path)
20 | # # Add here, for the settings module
21 | # site.addsitedir(os.path.abspath(os.path.dirname(__file__)))
22 | # # Add the virtualenv
23 | # venv = os.path.join(os.path.abspath(os.path.dirname(__file__)),
24 | # '..', 'lib', 'python2.6', 'site-packages')
25 | # site.addsitedir(venv)
26 | # # Reorder sys.path so new directories at the front.
27 | # new_sys_path = []
28 | # for item in list(sys.path):
29 | # if item not in prev_sys_path:
30 | # new_sys_path.append(item)
31 | # sys.path.remove(item)
32 | # sys.path[:0] = new_sys_path
33 |
34 | from django.core.wsgi import get_wsgi_application
35 |
36 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
37 |
38 | application = get_wsgi_application()
39 |
--------------------------------------------------------------------------------
/postorius/mailman-web/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for HyperKitty project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | # import sys
13 | # import site
14 |
15 | # For some unknown reason, sometimes mod_wsgi fails to set the python paths to
16 | # the virtualenv, with the 'python-path' option. You can do it here too.
17 | #
18 | # # Remember original sys.path.
19 | # prev_sys_path = list(sys.path)
20 | # # Add here, for the settings module
21 | # site.addsitedir(os.path.abspath(os.path.dirname(__file__)))
22 | # # Add the virtualenv
23 | # venv = os.path.join(os.path.abspath(os.path.dirname(__file__)),
24 | # '..', 'lib', 'python2.6', 'site-packages')
25 | # site.addsitedir(venv)
26 | # # Reorder sys.path so new directories at the front.
27 | # new_sys_path = []
28 | # for item in list(sys.path):
29 | # if item not in prev_sys_path:
30 | # new_sys_path.append(item)
31 | # sys.path.remove(item)
32 | # sys.path[:0] = new_sys_path
33 |
34 | from django.core.wsgi import get_wsgi_application
35 |
36 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
37 |
38 | application = get_wsgi_application()
39 |
--------------------------------------------------------------------------------
/tests/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | # If the DB environment variable is not set, use postgres.x
5 | if [ "$DB" = "postgres" ] || [ -z $DB ]
6 | then
7 | docker compose -f docker-compose.yaml -f tests/docker-test.yaml up -d
8 | elif [ "$DB" = "mysql" ]
9 | then
10 | docker compose -f docker-compose-mysql.yaml -f tests/docker-test.yaml up -d
11 | fi
12 |
13 | # Print the IP Addresses of the Containers.
14 | docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mailman-core
15 | docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mailman-web
16 |
17 | # Make sure all the containers are running.
18 | docker compose ps
19 |
20 | # Sleep for a while and check again if the containers are up.
21 | sleep 60
22 | docker ps
23 |
24 | # Check if there is anything interesting in the logs.
25 | docker logs mailman-web
26 | docker logs mailman-core
27 |
28 |
29 | # Check to see if the core is working as expected.
30 | docker exec mailman-core curl -u restadmin:restpass http://mailman-core:8001/3.1/system | grep "GNU Mailman"
31 |
32 | # Check to see if postorius is working.
33 | docker exec mailman-web curl -L http://mailman-web:8000/postorius/lists | grep "Mailing List"
34 |
35 | # Check to see if hyperkitty is working.
36 | docker exec mailman-web curl -L http://mailman-web:8000/hyperkitty/ | grep "Available lists"
37 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "docker" # See documentation for possible values
9 | directory: "/core" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | - package-ecosystem: "docker" # See documentation for possible values
13 | directory: "/web" # Location of package manifests
14 | schedule:
15 | interval: "weekly"
16 | - package-ecosystem: "docker" # See documentation for possible values
17 | directory: "/postorius" # Location of package manifests
18 | schedule:
19 | interval: "weekly"
20 | # Enable version updates for Actions
21 | - package-ecosystem: "github-actions"
22 | # Look for `.github/workflows` in the `root` directory
23 | directory: "/"
24 | # Check for updates once a week
25 | schedule:
26 | interval: "weekly"
27 | - package-ecosystem: "pip"
28 | directory: "/core"
29 | schedule:
30 | interval: "daily"
31 | - package-ecosystem: "pip"
32 | directory: "/web"
33 | schedule:
34 | interval: "daily"
--------------------------------------------------------------------------------
/postorius/mailman-web/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
3 | #
4 | # This file is part of Postorius.
5 | #
6 | # Postorius is free software: you can redistribute it and/or modify it under
7 | # the terms of the GNU General Public License as published by the Free
8 | # Software Foundation, either version 3 of the License, or (at your option)
9 | # any later version.
10 | #
11 | # Postorius is distributed in the hope that it will be useful, but WITHOUT
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 | # more details.
15 | #
16 | # You should have received a copy of the GNU General Public License along with
17 | # Postorius. If not, see .
18 |
19 | from django.conf.urls import include
20 | from django.contrib import admin
21 | from django.urls import re_path, reverse_lazy
22 | from django.views.generic import RedirectView
23 |
24 | urlpatterns = [
25 | re_path(r'^$', RedirectView.as_view(
26 | url=reverse_lazy('list_index'),
27 | permanent=True)),
28 | re_path(r'postorius/', include('postorius.urls')),
29 | re_path(r'', include('django_mailman3.urls')),
30 | re_path(r'accounts/', include('allauth.urls')),
31 | # Django admin
32 | re_path(r'^admin/', admin.site.urls),
33 | ]
34 |
--------------------------------------------------------------------------------
/core/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | # Use 3.15 for Core since it has Python 3.9
3 | FROM alpine:3.22
4 |
5 | # Set the commits that we are building.
6 | ARG CORE_REF
7 | ARG MM3_HK_REF
8 |
9 | #Install all required packages, add user for executing mailman and set execution
10 | #rights for startup script
11 | RUN --mount=type=cache,target=/root/.cache \
12 | apk update \
13 | && apk add --no-cache --virtual build-deps gcc python3-dev musl-dev \
14 | postgresql-dev git libffi-dev g++ \
15 | && apk add --no-cache bash su-exec postgresql-client mysql-client \
16 | curl python3 py3-pip linux-headers py-cryptography mariadb-connector-c tzdata \
17 | && python3 -m pip install -U --break-system-packages psycopg2 pymysql setuptools wheel \
18 | && python3 -m pip install --break-system-packages \
19 | git+https://gitlab.com/mailman/mailman \
20 | git+https://gitlab.com/mailman/mailman-hyperkitty \
21 | && apk del build-deps \
22 | && adduser -S mailman
23 |
24 | #Add startup script to container
25 | COPY docker-entrypoint.sh /usr/local/bin/
26 |
27 | # Change the working directory.
28 | WORKDIR /opt/mailman
29 |
30 | #Expose the ports for the api (8001) and lmtp (8024)
31 | EXPOSE 8001 8024
32 |
33 | # Set the default configuration file.
34 | ENV MAILMAN_CONFIG_FILE /etc/mailman.cfg
35 |
36 | ENTRYPOINT ["docker-entrypoint.sh"]
37 |
38 | CMD ["master"]
39 |
--------------------------------------------------------------------------------
/core/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | # Use 3.15 for Core since it has Python 3.9
3 | FROM alpine:3.22
4 |
5 | # Add requirements file.
6 | COPY requirements.txt /tmp/
7 |
8 | #Install all required packages, add user for executing mailman and set execution rights for startup script
9 | RUN --mount=type=cache,target=/root/.cache \
10 | apk update \
11 | && apk add --virtual build-deps gcc python3-dev musl-dev postgresql-dev \
12 | libffi-dev \
13 | # Mailman html to plaintext conversion uses lynx.
14 | # psutil needs linux-headers to compile on musl c library.
15 | && apk add --no-cache bash su-exec postgresql-client mysql-client curl python3 py3-pip linux-headers py-cryptography mariadb-connector-c lynx tzdata \
16 | && python3 -m pip install --break-system-packages -U pip setuptools wheel \
17 | && python3 -m pip install --break-system-packages psycopg2 \
18 | pymysql \
19 | -r /tmp/requirements.txt \
20 | 'importlib-resources<6.0.0' \
21 | && apk del build-deps \
22 | && adduser -S mailman
23 |
24 | #Add startup script to container
25 | COPY docker-entrypoint.sh /usr/local/bin/
26 |
27 | # Change the working directory.
28 | WORKDIR /opt/mailman
29 |
30 | #Expose the ports for the api (8001) and lmtp (8024)
31 | EXPOSE 8001 8024
32 |
33 | ENV MAILMAN_CONFIG_FILE /etc/mailman.cfg
34 |
35 | ENTRYPOINT ["docker-entrypoint.sh"]
36 | CMD ["master", "--force"]
37 |
--------------------------------------------------------------------------------
/web/mailman-web/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (C) 2023 by the Free Software Foundation, Inc.
3 | #
4 | # This file is part of mailman-web.
5 | #
6 | # Postorius is free software: you can redistribute it and/or modify it under
7 | # the terms of the GNU General Public License as published by the Free
8 | # Software Foundation, either version 3 of the License, or (at your option)
9 | # any later version.
10 | #
11 | # Postorius is distributed in the hope that it will be useful, but WITHOUT
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 | # more details.
15 | #
16 | # You should have received a copy of the GNU General Public License along with
17 | # Postorius. If not, see .
18 |
19 |
20 | from django.conf.urls import include
21 | from django.contrib import admin
22 | from django.urls import path, reverse_lazy
23 | from django.views.generic import RedirectView
24 |
25 | urlpatterns = [
26 | path(
27 | "",
28 | RedirectView.as_view(url=reverse_lazy("list_index"), permanent=True),
29 | ),
30 | # Include alternate Postorius and HyperKitty URLs.
31 | path("postorius/", include("postorius.urls")),
32 | path("hyperkitty/", include("hyperkitty.urls")),
33 | # Order counts for various links. Put the above first and the following
34 | # after so the suggested Apache config still works.
35 | path("mailman3/", include("postorius.urls")),
36 | path("archives/", include("hyperkitty.urls")),
37 | path("", include("django_mailman3.urls")),
38 | path("accounts/", include("allauth.urls")),
39 | path("admin/", admin.site.urls),
40 | ]
41 |
--------------------------------------------------------------------------------
/postorius/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | FROM alpine:3.22.2
3 |
4 | # Install packages and dependencies for postorius and hyperkitty Add user for
5 | # executing apps, change ownership for uwsgi+django files and set execution
6 | # rights for management script
7 | RUN --mount=type=cache,target=/root/.cache \
8 | set -ex \
9 | && apk add --no-cache --virtual .build-deps gcc libc-dev linux-headers \
10 | postgresql-dev mariadb-dev mariadb-connector-c python3-dev libffi-dev openldap-dev cargo rust \
11 | && apk add --no-cache --virtual .mailman-rundeps bash sassc tzdata libldap \
12 | postgresql-client mysql-client py3-mysqlclient curl mailcap gettext \
13 | python3 py3-pip libffi libuuid pcre-dev py-cryptography \
14 | && python3 -m pip install --break-system-packages -U 'Django<4.3' pip setuptools wheel \
15 | && python3 -m pip install --break-system-packages postorius==1.3.10 \
16 | uwsgi \
17 | psycopg2 \
18 | dj-database-url \
19 | mysqlclient \
20 | typing \
21 | django-auth-ldap \
22 | python-memcached \
23 | tzdata \
24 | && apk del .build-deps \
25 | && addgroup -S mailman \
26 | && adduser -S -G mailman mailman
27 |
28 | # Add needed files for uwsgi server + settings for django
29 | COPY mailman-web /opt/mailman-web
30 | # Add startup script to container
31 | COPY docker-entrypoint.sh /usr/local/bin/
32 |
33 | RUN chown -R mailman /opt/mailman-web/ \
34 | && chmod u+x /opt/mailman-web/manage.py
35 |
36 | WORKDIR /opt/mailman-web
37 |
38 | # Expose port 8000 for http and port 8080 for uwsgi
39 | # (see web/mailman-web/uwsgi.ini#L2-L4)
40 | EXPOSE 8000 8080
41 |
42 | # Use stop signal for uwsgi server
43 | STOPSIGNAL SIGINT
44 |
45 | ENTRYPOINT ["docker-entrypoint.sh"]
46 | CMD ["uwsgi", "--ini", "/opt/mailman-web/uwsgi.ini"]
47 |
--------------------------------------------------------------------------------
/web/mailman-web/uwsgi.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | # Port on which uwsgi will be listening.
3 | uwsgi-socket = 0.0.0.0:8080
4 | http-socket = 0.0.0.0:8000
5 |
6 | # Enable threading for python
7 | enable-threads = true
8 |
9 | # Setting uwsgi buffer size to what Apache2 supports.
10 | buffer-size = 8190
11 |
12 | # Move to the directory where the django files are.
13 | chdir = /opt/mailman-web
14 |
15 | # Use the wsgi file provided with the django project.
16 | wsgi-file = wsgi.py
17 |
18 | # Setup default number of processes and threads per process.
19 | master = true
20 | processes = 2
21 | threads = 2
22 |
23 | # Drop privileges and don't run as root.
24 | uid = mailman
25 | gid = mailman
26 |
27 | # Setup the django_q related worker processes.
28 | attach-daemon = ./manage.py qcluster
29 |
30 | # Setup hyperkitty's cron jobs.
31 | # 'minutely' jobs are run hourly for perf reasons.
32 | # See https://github.com/maxking/docker-mailman/issues/327
33 | unique-cron = 0 -1 -1 -1 -1 ./manage.py runjobs minutely
34 | unique-cron = -15 -1 -1 -1 -1 ./manage.py runjobs quarter_hourly
35 | unique-cron = 0 -1 -1 -1 -1 ./manage.py runjobs hourly
36 | unique-cron = 0 0 -1 -1 -1 ./manage.py runjobs daily
37 | unique-cron = 0 0 1 -1 -1 ./manage.py runjobs monthly
38 | unique-cron = 0 0 -1 -1 0 ./manage.py runjobs weekly
39 | unique-cron = 0 0 1 1 -1 ./manage.py runjobs yearly
40 |
41 | # Setup the request log.
42 | req-logger = file:/opt/mailman-web-data/logs/uwsgi.log
43 |
44 | # Log cron seperately.
45 | logger = cron file:/opt/mailman-web-data/logs/uwsgi-cron.log
46 | log-route = cron uwsgi-cron
47 |
48 | # Log qcluster commands seperately.
49 | logger = qcluster file:/opt/mailman-web-data/logs/uwsgi-qcluster.log
50 | log-route = qcluster uwsgi-daemons
51 |
52 | # Last log and it logs the rest of the stuff.
53 | logger = file:/opt/mailman-web-data/logs/uwsgi-error.log
54 |
--------------------------------------------------------------------------------
/docker-compose-postorius.yaml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | mailman-core:
5 | image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
6 | container_name: mailman-core
7 | hostname: mailman-core
8 | restart: unless-stopped
9 | volumes:
10 | - /opt/mailman/core:/opt/mailman/
11 | stop_grace_period: 30s
12 | links:
13 | - database:database
14 | depends_on:
15 | - database
16 | environment:
17 | - DATABASE_URL=postgres://mailman:mailmanpass@database/mailmandb
18 | - DATABASE_TYPE=postgres
19 | - DATABASE_CLASS=mailman.database.postgresql.PostgreSQLDatabase
20 | ports:
21 | - "127.0.0.1:8001:8001" # API
22 | - "127.0.0.1:8024:8024" # LMTP - incoming emails
23 | networks:
24 | mailman:
25 |
26 | mailman-web:
27 | image: maxking/postorius:0.4 # Use a specific version tag (tag latest is not published)
28 | container_name: mailman-web
29 | hostname: mailman-web
30 | restart: unless-stopped
31 | depends_on:
32 | - database
33 | links:
34 | - mailman-core:mailman-core
35 | - database:database
36 | volumes:
37 | - /opt/mailman/web:/opt/mailman-web-data
38 | environment:
39 | - DATABASE_TYPE=postgres
40 | - DATABASE_URL=postgres://mailman:mailmanpass@database/mailmandb
41 | - SECRET_KEY=ksjdbaksdba
42 | - UWSGI_STATIC_MAP=/static=/opt/mailman-web-data/static
43 | ports:
44 | - "127.0.0.1:8000:8000" # HTTP
45 | - "127.0.0.1:8080:8080" # uwsgi
46 | networks:
47 | mailman:
48 |
49 | database:
50 | environment:
51 | POSTGRES_DB: mailmandb
52 | POSTGRES_USER: mailman
53 | POSTGRES_PASSWORD: mailmanpass
54 | restart: always
55 | image: postgres:9.6-alpine
56 | volumes:
57 | - /opt/mailman/database:/var/lib/postgresql/data
58 | networks:
59 | mailman:
60 |
61 | networks:
62 | mailman:
63 | driver: bridge
64 | ipam:
65 | driver: default
66 | config:
67 | -
68 | subnet: 172.19.199.0/24
69 |
--------------------------------------------------------------------------------
/postorius/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | FROM alpine:3.22.2
3 |
4 | ARG POSTORIUS_REF
5 | ARG DJ_MM3_REF
6 | ARG CLIENT_REF
7 |
8 | # Install packages and dependencies for postorius and hyperkitty Add user for
9 | # executing apps, change ownership for uwsgi+django files and set execution
10 | # rights for management script
11 | RUN --mount=type=cache,target=/root/.cache \
12 | set -ex \
13 | && apk add --no-cache --virtual .build-deps gcc libc-dev linux-headers \
14 | postgresql-dev mariadb-dev mariadb-connector-c python3-dev libffi-dev git cargo rust \
15 | && apk add --no-cache --virtual .mailman-rundeps bash sassc tzdata libldap \
16 | postgresql-client mysql-client py3-mysqlclient curl mailcap \
17 | python3 py3-pip libffi gettext py-cryptography \
18 | && python3 -m pip install --break-system-packages -U pip setuptools wheel \
19 | && python3 -m pip install --break-system-packages -U \
20 | git+https://gitlab.com/mailman/mailmanclient \
21 | git+https://gitlab.com/mailman/postorius \
22 | uwsgi \
23 | psycopg2 \
24 | dj-database-url \
25 | mysqlclient \
26 | typing \
27 | django-utils-six \
28 | && python3 -m pip install --break-system-packages -U 'Django<4.3' \
29 | && python3 -m pip install --break-system-packages -U \
30 | git+https://gitlab.com/mailman/django-mailman3 \
31 | && apk del .build-deps \
32 | && addgroup -S mailman \
33 | && adduser -S -G mailman mailman
34 |
35 | # Add needed files for uwsgi server + settings for django
36 | COPY mailman-web /opt/mailman-web
37 | # Add startup script to container
38 | COPY docker-entrypoint.sh /usr/local/bin/
39 |
40 | RUN chown -R mailman /opt/mailman-web/ \
41 | && chmod u+x /opt/mailman-web/manage.py
42 |
43 | WORKDIR /opt/mailman-web
44 |
45 | # Expose port 8000 for http and port 8080 for uwsgi
46 | # (see web/mailman-web/uwsgi.ini#L2-L4)
47 | EXPOSE 8000 8080
48 |
49 | # Use stop signal for uwsgi server
50 | STOPSIGNAL SIGINT
51 |
52 | ENTRYPOINT ["docker-entrypoint.sh"]
53 |
54 | CMD ["uwsgi", "--ini", "/opt/mailman-web/uwsgi.ini"]
55 |
--------------------------------------------------------------------------------
/web/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | FROM alpine:3.21.3
3 |
4 | # Add requirements file.
5 | COPY requirements.txt /tmp/
6 |
7 | # Install packages and dependencies for postorius and hyperkitty Add user for
8 | # executing apps, change ownership for uwsgi+django files and set execution
9 | # rights for management script
10 | RUN --mount=type=cache,target=/root/.cache \
11 | set -ex \
12 | && apk add --no-cache --virtual .build-deps gcc libc-dev linux-headers \
13 | postgresql-dev mariadb-dev mariadb-connector-c python3-dev libffi-dev openldap-dev cargo rust \
14 | && apk add --no-cache --virtual .mailman-rundeps bash sassc tzdata libldap \
15 | postgresql-client mysql-client py3-mysqlclient curl mailcap gettext \
16 | python3 py3-pip xapian-core xapian-bindings-python3 libffi pcre-dev py-cryptography \
17 | && python3 -m pip install --break-system-packages -U 'Django<4.3' pip setuptools wheel \
18 | && pip install --break-system-packages -r /tmp/requirements.txt \
19 | whoosh \
20 | # later builds of uwsgi don't compile on aarch64
21 | uwsgi==2.0.25 \
22 | psycopg2 \
23 | dj-database-url \
24 | mysqlclient \
25 | typing \
26 | xapian-haystack \
27 | django-auth-ldap \
28 | pymemcache \
29 | diskcache \
30 | django-utils-six \
31 | tzdata \
32 | pytz \
33 | 'django-allauth[socialaccount,openid]' \
34 | && apk del .build-deps \
35 | && addgroup -S mailman \
36 | && adduser -S -G mailman mailman
37 |
38 | # Add needed files for uwsgi server + settings for django
39 | COPY mailman-web /opt/mailman-web
40 | # Add startup script to container
41 | COPY docker-entrypoint.sh /usr/local/bin/
42 |
43 | RUN chown -R mailman /opt/mailman-web/ \
44 | && chmod u+x /opt/mailman-web/manage.py
45 |
46 | WORKDIR /opt/mailman-web
47 |
48 | # Expose port 8000 for http and port 8080 for uwsgi
49 | # (see web/mailman-web/uwsgi.ini#L2-L4)
50 | EXPOSE 8000 8080
51 |
52 | # Use stop signal for uwsgi server
53 | STOPSIGNAL SIGINT
54 |
55 | ENTRYPOINT ["docker-entrypoint.sh"]
56 |
57 | CMD ["uwsgi", "--ini", "/opt/mailman-web/uwsgi.ini"]
58 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: '2.1'
2 |
3 | jobs:
4 | build:
5 | parameters:
6 | rolling:
7 | type: string
8 | default: "no"
9 | machine:
10 | image: default
11 | environment:
12 | DOCKER_BUILDKIT: 1
13 | BUILDKIT_PROGRESS: plain
14 | BUILD_ROLLING: << parameters.rolling >>
15 | steps:
16 | - checkout
17 | - run:
18 | name: Install Python dependencies
19 | command: python3 -m pip install packaging
20 | - restore_cache:
21 | keys:
22 | - python-deps-cache-v1
23 | - run:
24 | name: Building Container Images
25 | command: ./build.sh << parameters.rolling >>
26 | - save_cache:
27 | key: python-deps-cache-v1
28 | paths:
29 | - /root/.cache
30 | - run:
31 | environment:
32 | DB: postgres
33 | name: Postgres Tests
34 | command: bash tests/test.sh
35 | - run:
36 | environment:
37 | DB: mysql
38 | name: MySQL Test
39 | command: bash tests/test.sh
40 | - run:
41 | name: Run version
42 | command: |
43 | python3 --version
44 | python3 deploy.py
45 | - store_artifacts:
46 | path: /opt/mailman/web/logs/
47 |
48 | - store_artifacts:
49 | path: /opt/mailman/core/var/logs
50 |
51 | workflows:
52 | version: 2
53 | test-stable:
54 | jobs:
55 | - build:
56 | rolling: "no"
57 | filters:
58 | tags:
59 | only: /^v\d+\.\d+\.\d+$/
60 |
61 | cron-builds:
62 | triggers:
63 | - schedule:
64 | cron: "0 0 * * *"
65 | filters:
66 | branches:
67 | only: main
68 | jobs:
69 | - build:
70 | rolling: "yes"
71 | context: org-global
72 |
73 | test-rolling:
74 | jobs:
75 | - build:
76 | rolling: "yes"
77 | context: org-global
78 | filters:
79 | branches:
80 | # Forked pull requests have CIRCLE_BRANCH set to pull/XXX
81 | ignore: /pull\/[0-9]+/
82 |
--------------------------------------------------------------------------------
/docker-compose-mysql.yaml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | mailman-core:
5 | image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
6 | container_name: mailman-core
7 | hostname: mailman-core
8 | restart: unless-stopped
9 | volumes:
10 | - /opt/mailman/core:/opt/mailman/
11 | stop_grace_period: 30s
12 | links:
13 | - database:database
14 | depends_on:
15 | - database
16 | environment:
17 | - DATABASE_URL=mysql+pymysql://mailman:mailmanpass@database/mailmandb?charset=utf8mb4&use_unicode=1 # Do use mysql+pymysql:// here
18 | - DATABASE_TYPE=mysql
19 | - HYPERKITTY_API_KEY=someapikey
20 | ports:
21 | - "127.0.0.1:8001:8001" # API
22 | - "127.0.0.1:8024:8024" # LMTP - incoming emails
23 | networks:
24 | mailman:
25 |
26 | mailman-web:
27 | image: maxking/mailman-web:0.4 # Use a specific version tag (tag latest is not published)
28 | container_name: mailman-web
29 | hostname: mailman-web
30 | restart: unless-stopped
31 | depends_on:
32 | - database
33 | links:
34 | - mailman-core:mailman-core
35 | - database:database
36 | volumes:
37 | - /opt/mailman/web:/opt/mailman-web-data
38 | environment:
39 | - DATABASE_TYPE=mysql
40 | - DATABASE_URL=mysql://mailman:mailmanpass@database/mailmandb?charset=utf8mb4 # Do use mysql:// here
41 | - HYPERKITTY_API_KEY=someapikey
42 | - SECRET_KEY=thisisaverysecretkey
43 | - DYLD_LIBRARY_PATH=/usr/local/mysql/lib/
44 | ports:
45 | - "127.0.0.1:8000:8000" # HTTP
46 | - "127.0.0.1:8080:8080" # uwsgi
47 | networks:
48 | mailman:
49 |
50 | database:
51 | environment:
52 | MYSQL_DATABASE: mailmandb
53 | MYSQL_USER: mailman
54 | MYSQL_PASSWORD: mailmanpass
55 | MYSQL_RANDOM_ROOT_PASSWORD: "yes"
56 | image: mariadb:10.5
57 | command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
58 | volumes:
59 | - /opt/mailman/database:/var/lib/mysql
60 | networks:
61 | mailman:
62 |
63 | networks:
64 | mailman:
65 | driver: bridge
66 | ipam:
67 | driver: default
68 | config:
69 | -
70 | subnet: 172.19.199.0/24
71 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | mailman-core:
5 | image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
6 | container_name: mailman-core
7 | hostname: mailman-core
8 | restart: unless-stopped
9 | volumes:
10 | - /opt/mailman/core:/opt/mailman/
11 | stop_grace_period: 30s
12 | links:
13 | - database:database
14 | depends_on:
15 | database:
16 | condition: service_healthy
17 | environment:
18 | - DATABASE_URL=postgresql://mailman:mailmanpass@database/mailmandb
19 | - DATABASE_TYPE=postgres
20 | - DATABASE_CLASS=mailman.database.postgresql.PostgreSQLDatabase
21 | - HYPERKITTY_API_KEY=someapikey
22 | ports:
23 | - "127.0.0.1:8001:8001" # API
24 | - "127.0.0.1:8024:8024" # LMTP - incoming emails
25 | networks:
26 | mailman:
27 |
28 | mailman-web:
29 | image: maxking/mailman-web:0.4 # Use a specific version tag (tag latest is not published)
30 | container_name: mailman-web
31 | hostname: mailman-web
32 | restart: unless-stopped
33 | depends_on:
34 | database:
35 | condition: service_healthy
36 | links:
37 | - mailman-core:mailman-core
38 | - database:database
39 | volumes:
40 | - /opt/mailman/web:/opt/mailman-web-data
41 | environment:
42 | - DATABASE_TYPE=postgres
43 | - DATABASE_URL=postgresql://mailman:mailmanpass@database/mailmandb
44 | - HYPERKITTY_API_KEY=someapikey
45 | ports:
46 | - "127.0.0.1:8000:8000" # HTTP
47 | - "127.0.0.1:8080:8080" # uwsgi
48 | networks:
49 | mailman:
50 |
51 | database:
52 | environment:
53 | - POSTGRES_DB=mailmandb
54 | - POSTGRES_USER=mailman
55 | - POSTGRES_PASSWORD=mailmanpass
56 | image: postgres:12-alpine
57 | volumes:
58 | - /opt/mailman/database:/var/lib/postgresql/data
59 | healthcheck:
60 | test: ["CMD-SHELL", "pg_isready --dbname mailmandb --username mailman"]
61 | interval: 10s
62 | timeout: 5s
63 | retries: 5
64 | networks:
65 | mailman:
66 |
67 | networks:
68 | mailman:
69 | driver: bridge
70 | ipam:
71 | driver: default
72 | config:
73 | -
74 | subnet: 172.19.199.0/24
75 |
--------------------------------------------------------------------------------
/web/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.3
2 | FROM alpine:3.21.3
3 |
4 | ARG POSTORIUS_REF
5 | ARG HYPERKITTY_REF
6 | ARG DJ_MM3_REF
7 | ARG CLIENT_REF
8 |
9 | # Install packages and dependencies for postorius and hyperkitty Add user for
10 | # executing apps, change ownership for uwsgi+django files and set execution
11 | # rights for management script
12 | RUN --mount=type=cache,target=/root/.cache \
13 | set -ex \
14 | && apk add --no-cache --virtual .build-deps gcc libc-dev linux-headers git \
15 | postgresql-dev mariadb-dev mariadb-connector-c python3-dev libffi-dev openldap-dev cargo rust \
16 | && apk add --no-cache --virtual .mailman-rundeps bash sassc pcre-dev tzdata libldap \
17 | python3 py3-pip postgresql-client mysql-client py3-mysqlclient \
18 | curl mailcap xapian-core xapian-bindings-python3 libffi gettext py-cryptography \
19 | && python3 -m pip install --break-system-packages -U pip setuptools wheel \
20 | && python3 -m pip install --break-system-packages -U \
21 | git+https://gitlab.com/mailman/mailmanclient \
22 | git+https://gitlab.com/mailman/postorius \
23 | git+https://gitlab.com/mailman/hyperkitty \
24 | whoosh \
25 | uwsgi \
26 | psycopg2 \
27 | dj-database-url \
28 | mysqlclient \
29 | xapian-haystack \
30 | django-auth-ldap \
31 | pymemcache \
32 | tzdata \
33 | diskcache \
34 | django-utils-six \
35 | 'django-allauth[socialaccount,openid]' \
36 | && python3 -m pip install --break-system-packages -U 'Django<4.3' \
37 | && python3 -m pip install --break-system-packages -U \
38 | git+https://gitlab.com/mailman/django-mailman3 \
39 | && apk del .build-deps \
40 | && addgroup -S mailman \
41 | && adduser -S -G mailman mailman
42 |
43 | # Add needed files for uwsgi server + settings for django
44 | COPY mailman-web /opt/mailman-web
45 | # Add startup script to container
46 | COPY docker-entrypoint.sh /usr/local/bin/
47 |
48 | RUN chown -R mailman /opt/mailman-web/ \
49 | && chmod u+x /opt/mailman-web/manage.py
50 |
51 | WORKDIR /opt/mailman-web
52 |
53 | # Expose port 8000 for http and port 8080 for uwsgi
54 | # (see web/mailman-web/uwsgi.ini#L2-L4)
55 | EXPOSE 8000 8080
56 |
57 | # Use stop signal for uwsgi server
58 | STOPSIGNAL SIGINT
59 |
60 | ENTRYPOINT ["docker-entrypoint.sh"]
61 |
62 | CMD ["uwsgi", "--ini", "/opt/mailman-web/uwsgi.ini"]
63 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # NEWS
2 |
3 | ## Upgrading to 0.4.0 Release
4 |
5 | Some configurations in the release are backwards incompatible with
6 | what was working before.
7 |
8 | ### Web server configuration
9 |
10 | With 0.4.0 version, we added Port mapping from host's Port 8000/8080
11 | to mailman-web container's port 8000/8080. Make sure you have this
12 | in your docker-compose.yaml
13 |
14 | ```yaml
15 | mailman-web:
16 | ports:
17 | - "127.0.0.1:8000:8000" # HTTP
18 | - "127.0.0.1:8080:8080" # uwsgi
19 | ```
20 |
21 | You should update your web server to proxy 127.0.0.1:8000.
22 |
23 | #### Nginx
24 |
25 | Update the Nginx configuration to look like this, notice the
26 | actual **URL for `proxy_pass` is the only thing that has changed**
27 | along with some options like `uwsgi_read_timeout` and `include uwsgi_params`
28 | that have been removed from the previous version.
29 |
30 | ```
31 | location / {
32 | proxy_pass http://127.0.0.1:8000;
33 | proxy_set_header Host $host;
34 | proxy_set_header X-Forwarded-For $remote_addr;
35 | }
36 | ```
37 |
38 | For other web servers like Apache2, update the URL accordingly.
39 |
40 | **Note** that if you are using `uwsgi_pass` instead of `proxy_pass`
41 | then you should update the URL accordingly to `https://127.0.0.1:8080`.
42 |
43 | ### MTA configuration
44 |
45 | MTA configuration needs updating to ensure that all IPs from the
46 | `172.19.199.0/24` subnet is added to `mynetworks` in Postfix configs.
47 |
48 | Please verify that the network configuration generated by the containers
49 | look like this:
50 |
51 | ```bash
52 | $ docker exec mailman-core cat /etc/mailman.cfg
53 | # This file is autogenerated at container startup.
54 | [database]
55 | class: mailman.database.postgresql.PostgreSQLDatabase
56 | url: postgres://mailman:mailmanpass@database/mailmandb
57 | [runner.retry]
58 | sleep_time: 10s
59 |
60 | [webservice]
61 | hostname: 172.19.199.3
62 | port: 8001
63 | admin_user: restadmin
64 | admin_pass: restpass
65 | configuration: /etc/gunicorn.cfg
66 |
67 | [mta]
68 | incoming: mailman.mta.postfix.LMTP
69 | outgoing: mailman.mta.deliver.deliver
70 | lmtp_host: 172.19.199.3
71 | lmtp_port: 8024
72 | smtp_host: 172.19.199.1
73 | smtp_port: 25
74 | configuration: /etc/postfix-mailman.cfg
75 |
76 | [archiver.hyperkitty]
77 | class: mailman_hyperkitty.Archiver
78 | enable: yes
79 | configuration: /etc/mailman-hyperkitty.cfg
80 | ```
81 |
82 | **Note that lmtp_host and webserver hostname can be different than
83 | before since new containers don't have static IP addresses. They
84 | are automatically parsed from the output of "ip route" command
85 | from inside mailman-core container.**
86 |
87 | You can verify that the IP address of the containers by running the
88 | following commands, note that the **output can be different** and it is
89 | fine if that is the case.
90 |
91 | ```bash
92 | $ docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mailman-core
93 | 172.19.199.3
94 | $ docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mailman-web
95 | 172.19.199.4
96 | ```
97 |
98 | ----
99 | ## Mailman Core
100 |
101 | ### v1.1.1 (released Aug 9 2017)
102 |
103 | - The MM_HOSTNAME now defaults to output of `hostname -i` instead of `mailman-core`. This
104 | is the hostname Core binds to for Webservice.
105 | - Added pymysql to the image to use MySQL as database.
106 | - The default settings for using SQLITE are now more sane.
107 | - Postfix's transport maps are generated at the container startup now even when
108 | there is no lists exist.
109 |
110 |
111 | ## Mailman Web
112 |
113 | ### v1.1.1 (released Aug 9 2017)
114 |
115 | - The default search_index for whoosh now exists on persistent storage at
116 | `/opt/mailman-web-data`
117 | - Move to using Alpine instead of Debian for this image, python2.7:alpine-3.6
118 | image is now the base image
119 | - Django compressor is now using `sassc` from alpine repo.
120 | - Default value of SECRET_KEY is now removed. It is MUST to set SECRET_KEY
121 | environment variable to run this image now.
122 | - If a SERVE_FROM_DOMAIN environment variable is defined, the default Django's
123 | example.com site is renamed to this domain. The SITE_ID remains same so there
124 | is no change required to serve this domain.
125 | - If MAILMAN_ADMIN_USER and MAILMAN_ADMIN_EMAIL environment variables are
126 | defined a Django Superuser is created by default. The password for this user
127 | would have to be reset on the first login.
128 | - Fix cron configuration which would run them in wrong order.
129 | - Removed facebook as default social auth provider in the settings.py
130 | - Uwsgi now listens on port 8080 for uwsgi protocol and 8000 for http protocol.
131 | - Threads are enabled by default in the uwsgi configuration now.
132 | - Hyperkitty updated to v1.1.1
133 |
--------------------------------------------------------------------------------
/deploy.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python
2 |
3 | # Author: Abhilash Raj
4 | #
5 | # This is the primary deployment script for the docker-mailman repo. It does
6 | # deployment for stable and rolling releases both. It should be *always* invoked
7 | # and it will make the deployment decision based on the environment variables
8 | # that it sees.
9 | #
10 | # There are two kinds of deploymnets primarily:
11 | # 1. Rolling tags, which are built from each commit. These are typically run
12 | # in CI every day as well. These always update the "rolling" tag in the
13 | # docker registry.
14 | # 2. Stable tags, which are built from git tags with "va.b.c" tags. We don't
15 | # do the tag verification because for now, Circle CI does this for us. We
16 | # will tag and release a stable version whenever the right ENV var is set.
17 | #
18 | # Publishing:
19 | # We are typically publishing all the images to three repositories:
20 | # 1. Docker Hub: This is now rate-limited and might cause issues for people
21 | # pulling too frequently.
22 | # 2. Quay: This is an old registry that we started publishing too, so let's
23 | # continue publishing here too.
24 | # 3. Github Registry: This is the newest one in the mix and supports free
25 | # uploads and downloads (without very strict rate limits like Dockerhub.)
26 |
27 | import os
28 | import subprocess
29 | from packaging import version
30 |
31 | #: Default user, which owns the repositories.
32 | USER = 'maxking'
33 |
34 | TAG_VAR = 'CIRCLE_TAG'
35 | BRANCH_VAR = 'CIRCLE_BRANCH'
36 | PRIMARY_BRANCH = 'main'
37 | ROLLING_VAR = "BUILD_ROLLING"
38 |
39 |
40 | def tag(original, final):
41 | """Tag the source image with final tag."""
42 | try:
43 | print('Tagging {0} to {1}'.format(original, final))
44 | subprocess.run(
45 | ['docker', 'tag', original, final],
46 | check=True,
47 | )
48 | except subprocess.CalledProcessError:
49 | print('Failed to tag {0}'.format(original))
50 |
51 |
52 | def login(url):
53 | """Login to the registry."""
54 | if 'quay' in url.lower():
55 | password = os.environ['QUAY_PASSWORD']
56 | elif 'docker' in url.lower():
57 | password = os.environ['DOCKER_PASSWORD']
58 | elif 'ghcr' in url.lower():
59 | password = os.environ['GITHUB_PASSWORD']
60 | else:
61 | print('Password not found for {0}'.format(url))
62 | return None
63 | print('Logging in to {0}'.format(url))
64 | subprocess.run(
65 | ['docker', 'login', '-u', USER, '-p', password, url],
66 | check=True
67 | )
68 | print('Logged in to {0}'.format(url))
69 |
70 |
71 | def push(image):
72 | """Push all the images."""
73 | print('Pushing {}'.format(image))
74 | subprocess.run(['docker', 'push', image], check=True)
75 |
76 |
77 | def tag_and_push(image_names, url, img_tag):
78 | """Given the URL to repository, tag and push the images."""
79 | # Tag recently built images.
80 | source, final = image_names
81 | tag(source, final)
82 | # Finall, push all the images.
83 | push(final)
84 |
85 |
86 | def get_tag_without_patch(tag):
87 | """Given A.B.C return A.B"""
88 | v = version.parse(tag)
89 | return '{}.{}'.format(v.major, v.minor)
90 |
91 |
92 | def get_urls(url, img_tag):
93 | core = ('maxking/mailman-core:rolling',
94 | '{0}/maxking/mailman-core:{1}'.format(url, img_tag))
95 | web = ('maxking/mailman-web:rolling',
96 | '{0}/maxking/mailman-web:{1}'.format(url, img_tag))
97 | postorius = ('maxking/postorius:rolling',
98 | '{0}/maxking/postorius:{1}'.format(url, img_tag))
99 |
100 | return (core, web, postorius)
101 |
102 |
103 |
104 | def main():
105 | """Primary entrypoint to this script."""
106 | # Boolean signifying if this is a stable release tag or just a branch.
107 | is_release = False
108 |
109 | if os.environ.get(TAG_VAR) not in (None, ''):
110 | img_tag = os.environ.get(TAG_VAR)
111 | # Released versions are tagged vA.B.C, so remove
112 | # v from the tag when creating the release.
113 | if img_tag.startswith('v'):
114 | img_tag = img_tag[1:]
115 | is_release = True
116 | elif os.environ.get(BRANCH_VAR) == PRIMARY_BRANCH and os.environ.get(ROLLING_VAR) == "yes":
117 | img_tag = 'rolling'
118 | else:
119 | print('Not running on {PRIMARY_BRANCH} branch or Git tag so not publishing...'.format(
120 | PRIMARY_BRANCH=PRIMARY_BRANCH))
121 | exit(0)
122 |
123 | # All the registries we are pushing to.
124 | for url in ('quay.io', 'docker.io', 'ghcr.io'):
125 |
126 | try:
127 | login(url)
128 | except subprocess.CalledProcessError:
129 | print('Failed to login to {}'.format(url))
130 | continue
131 |
132 | # Push all the container images.
133 | for each in get_urls(url, img_tag):
134 | tag_and_push(each, url, img_tag)
135 |
136 | # If this is a release tag, tag them also with a.b version.
137 | if is_release:
138 | rel_tag = get_tag_without_patch(img_tag)
139 | for each in get_urls(url, rel_tag):
140 | tag_and_push(each, url, rel_tag)
141 |
142 |
143 |
144 | if __name__ == '__main__':
145 |
146 | main()
147 |
148 |
149 |
--------------------------------------------------------------------------------
/postorius/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | set -e
3 |
4 |
5 | function wait_for_postgres () {
6 | # Check if the postgres database is up and accepting connections before
7 | # moving forward.
8 | # TODO: Use python's psycopg2 module to do this in python instead of
9 | # installing postgres-client in the image.
10 | until psql -P pager=off $DATABASE_URL -c '\l'; do
11 | >&2 echo "Postgres is unavailable - sleeping"
12 | sleep 1
13 | done
14 | >&2 echo "Postgres is up - continuing"
15 | }
16 |
17 | function wait_for_mysql () {
18 | # Check if MySQL is up and accepting connections.
19 | readarray -d' ' -t ENDPOINT <<< $(python3 -c "from urllib.parse import urlparse; o = urlparse('$DATABASE_URL'); print('%s %s' % (o.hostname, o.port if o.port else '3306'));")
20 | until mysqladmin ping --host ${ENDPOINT[0]} --port ${ENDPOINT[1]} --silent; do
21 | >&2 echo "MySQL is unavailable - sleeping"
22 | sleep 1
23 | done
24 | >&2 echo "MySQL is up - continuing"
25 | }
26 |
27 | function check_or_create () {
28 | # Check if the path exists, if not, create the directory.
29 | if [[ ! -e dir ]]; then
30 | echo "$1 does not exist, creating ..."
31 | mkdir "$1"
32 | fi
33 | }
34 |
35 | # function postgres_ready(){
36 | # python << END
37 | # import sys
38 | # import psycopg2
39 | # try:
40 | # conn = psycopg2.connect(dbname="$POSTGRES_DB", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres")
41 | # except psycopg2.OperationalError:
42 | # sys.exit(-1)
43 | # sys.exit(0)
44 | # END
45 | # }
46 |
47 | # SMTP_HOST defaults to the gateway
48 | if [[ ! -v SMTP_HOST ]]; then
49 | export SMTP_HOST=$(/sbin/ip route | awk '/default/ { print $3 }')
50 | fi
51 |
52 | # Check if $SECRET_KEY is defined, if not, bail out.
53 | if [[ ! -v SECRET_KEY ]]; then
54 | echo "SECRET_KEY is not defined. Aborting."
55 | exit 1
56 | fi
57 |
58 | # Check if $DATABASE_URL is defined, if not, use a standard sqlite database.
59 | #
60 | # If the $DATABASE_URL is defined and is postgres, check if it is available
61 | # yet. Do not start the container before the postgresql boots up.
62 | #
63 | # If the $DATABASE_URL is defined and is mysql, check if the database is
64 | # available before the container boots up.
65 | #
66 | # TODO: Check the database type and detect if it is up based on that. For now,
67 | # assume that postgres is being used if DATABASE_URL is defined.
68 |
69 | if [[ ! -v DATABASE_URL ]]; then
70 | echo "DATABASE_URL is not defined. Using sqlite database..."
71 | export DATABASE_URL=sqlite:////opt/mail-web-data/mailmanweb.db
72 | export DATABASE_TYPE='sqlite'
73 | fi
74 |
75 | if [[ "$DATABASE_TYPE" = 'postgres' ]]
76 | then
77 | wait_for_postgres
78 | elif [[ "$DATABASE_TYPE" = 'mysql' ]]
79 | then
80 | wait_for_mysql
81 | fi
82 |
83 | # Check if we are in the correct directory before running commands.
84 | if [[ ! $(pwd) == '/opt/mailman-web' ]]; then
85 | echo "Running in the wrong directory...switching to /opt/mailman-web"
86 | cd /opt/mailman-web
87 | fi
88 |
89 | # Check if the logs directory is setup.
90 | if [[ ! -e /opt/mailman-web-data/logs/mailmanweb.log ]]; then
91 | echo "Creating log file for mailman web"
92 | mkdir -p /opt/mailman-web-data/logs/
93 | touch /opt/mailman-web-data/logs/mailmanweb.log
94 | fi
95 |
96 | if [[ ! -e /opt/mailman-web-data/logs/uwsgi.log ]]; then
97 | echo "Creating log file for uwsgi.."
98 | touch /opt/mailman-web-data/logs/uwsgi.log
99 | fi
100 |
101 | # Check if the settings_local.py file exists, if yes, copy it too.
102 | if [[ -e /opt/mailman-web-data/settings_local.py ]]; then
103 | echo "Copying settings_local.py ..."
104 | cp /opt/mailman-web-data/settings_local.py /opt/mailman-web/settings_local.py
105 | chown mailman:mailman /opt/mailman-web/settings_local.py
106 | else
107 | echo "settings_local.py not found, it is highly recommended that you provide one"
108 | echo "Using default configuration to run."
109 | fi
110 |
111 | # Collect static for the django installation.
112 | python3 manage.py collectstatic --noinput --clear --verbosity 0
113 |
114 | # Compile all the installed po files to mo.
115 | SITE_DIR=$(python3 -c 'import site; print(site.getsitepackages()[0])')
116 | echo "Compiling locale files in $SITE_DIR"
117 | cd $SITE_DIR && python3 /opt/mailman-web/manage.py compilemessages && cd -
118 |
119 | # Migrate all the data to the database if this is a new installation, otherwise
120 | # this command will upgrade the database.
121 | python3 manage.py migrate
122 |
123 | # If MAILMAN_ADMIN_USER and MAILMAN_ADMIN_EMAIL is defined create a new
124 | # superuser for Django. There is no password setup so it can't login yet unless
125 | # the password is reset.
126 | if [[ -v MAILMAN_ADMIN_USER ]] && [[ -v MAILMAN_ADMIN_EMAIL ]];
127 | then
128 | echo "Creating admin user $MAILMAN_ADMIN_USER ..."
129 | python3 manage.py createsuperuser --noinput --username "$MAILMAN_ADMIN_USER"\
130 | --email "$MAILMAN_ADMIN_EMAIL" 2> /dev/null || \
131 | echo "Superuser $MAILMAN_ADMIN_USER already exists"
132 | fi
133 |
134 | # If SERVE_FROM_DOMAIN is defined then rename the default `example.com`
135 | # domain to the defined domain.
136 | if [[ -v SERVE_FROM_DOMAIN ]];
137 | then
138 | echo "Setting $SERVE_FROM_DOMAIN as the default domain ..."
139 | python3 manage.py shell -c \
140 | "from django.contrib.sites.models import Site; Site.objects.filter(domain='example.com').update(domain='$SERVE_FROM_DOMAIN', name='$SERVE_FROM_DOMAIN')"
141 | fi
142 |
143 | # Create a mailman user with the specific UID and GID and do not create home
144 | # directory for it. Also chown the logs directory to write the files.
145 | chown mailman:mailman /opt/mailman-web-data -R
146 |
147 | exec $@
148 |
--------------------------------------------------------------------------------
/web/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | set -e
3 |
4 |
5 | function wait_for_postgres () {
6 | # Check if the postgres database is up and accepting connections before
7 | # moving forward.
8 | # TODO: Use python's psycopg2 module to do this in python instead of
9 | # installing postgres-client in the image.
10 | until psql -P pager=off $DATABASE_URL -c '\l'; do
11 | >&2 echo "Postgres is unavailable - sleeping"
12 | sleep 1
13 | done
14 | >&2 echo "Postgres is up - continuing"
15 | }
16 |
17 | function wait_for_mysql () {
18 | # Check if MySQL is up and accepting connections.
19 | readarray -d' ' -t ENDPOINT <<< $(python3 -c "from urllib.parse import urlparse; o = urlparse('$DATABASE_URL'); print('%s %s' % (o.hostname, o.port if o.port else '3306'));")
20 | until mysqladmin ping --host ${ENDPOINT[0]} --port ${ENDPOINT[1]} --silent; do
21 | >&2 echo "MySQL is unavailable - sleeping"
22 | sleep 1
23 | done
24 | >&2 echo "MySQL is up - continuing"
25 | }
26 |
27 | function check_or_create () {
28 | # Check if the path exists, if not, create the directory.
29 | if [[ ! -e dir ]]; then
30 | echo "$1 does not exist, creating ..."
31 | mkdir "$1"
32 | fi
33 | }
34 |
35 | # function postgres_ready(){
36 | # python << END
37 | # import sys
38 | # import psycopg2
39 | # try:
40 | # conn = psycopg2.connect(dbname="$POSTGRES_DB", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres")
41 | # except psycopg2.OperationalError:
42 | # sys.exit(-1)
43 | # sys.exit(0)
44 | # END
45 | # }
46 |
47 | # SMTP_HOST defaults to the gateway
48 | if [[ ! -v SMTP_HOST ]]; then
49 | export SMTP_HOST=$(/sbin/ip route | awk '/default/ { print $3 }')
50 | fi
51 |
52 | # Check if $SECRET_KEY is defined, if not, bail out.
53 | if [[ ! -v SECRET_KEY ]]; then
54 | echo "SECRET_KEY is not defined. Aborting."
55 | exit 1
56 | fi
57 |
58 | # Check if $DATABASE_URL is defined, if not, use a standard sqlite database.
59 | #
60 | # If the $DATABASE_URL is defined and is postgres, check if it is available
61 | # yet. Do not start the container before the postgresql boots up.
62 | #
63 | # If the $DATABASE_URL is defined and is mysql, check if the database is
64 | # available before the container boots up.
65 | #
66 | # TODO: Check the database type and detect if it is up based on that. For now,
67 | # assume that postgres is being used if DATABASE_URL is defined.
68 |
69 | if [[ ! -v DATABASE_URL ]]; then
70 | echo "DATABASE_URL is not defined. Using sqlite database..."
71 | export DATABASE_URL=sqlite:////opt/mailman-web-data/mailmanweb.db
72 | export DATABASE_TYPE='sqlite'
73 | fi
74 |
75 | if [[ "$DATABASE_TYPE" = 'postgres' ]]
76 | then
77 | wait_for_postgres
78 | elif [[ "$DATABASE_TYPE" = 'mysql' ]]
79 | then
80 | wait_for_mysql
81 | fi
82 |
83 | # Check if we are in the correct directory before running commands.
84 | if [[ ! $(pwd) == '/opt/mailman-web' ]]; then
85 | echo "Running in the wrong directory...switching to /opt/mailman-web"
86 | cd /opt/mailman-web
87 | fi
88 |
89 | # Check if the logs directory is setup.
90 | if [[ ! -e /opt/mailman-web-data/logs/mailmanweb.log ]]; then
91 | echo "Creating log file for mailman web"
92 | mkdir -p /opt/mailman-web-data/logs/
93 | touch /opt/mailman-web-data/logs/mailmanweb.log
94 | fi
95 |
96 | if [[ ! -e /opt/mailman-web-data/logs/uwsgi.log ]]; then
97 | echo "Creating log file for uwsgi.."
98 | touch /opt/mailman-web-data/logs/uwsgi.log
99 | fi
100 |
101 | # Check if the settings_local.py file exists, if yes, copy it too.
102 | if [[ -e /opt/mailman-web-data/settings_local.py ]]; then
103 | echo "Copying settings_local.py ..."
104 | cp /opt/mailman-web-data/settings_local.py /opt/mailman-web/settings_local.py
105 | chown mailman:mailman /opt/mailman-web/settings_local.py
106 | else
107 | echo "settings_local.py not found, it is highly recommended that you provide one"
108 | echo "Using default configuration to run."
109 | fi
110 |
111 | # Collect static for the django installation.
112 | python3 manage.py collectstatic --noinput --clear --verbosity 0
113 |
114 |
115 | # Compile all the installed po files to mo.
116 | SITE_DIR=$(python3 -c 'import site; print(site.getsitepackages()[0])')
117 | echo "Compiling locale files in $SITE_DIR"
118 | cd $SITE_DIR && /opt/mailman-web/manage.py compilemessages && cd -
119 |
120 | # Compress static files.
121 | python3 /opt/mailman-web/manage.py compress --force
122 |
123 |
124 | # Migrate all the data to the database if this is a new installation, otherwise
125 | # this command will upgrade the database.
126 | python3 /opt/mailman-web/manage.py migrate
127 |
128 | # If MAILMAN_ADMIN_USER and MAILMAN_ADMIN_EMAIL is defined create a new
129 | # superuser for Django. There is no password setup so it can't login yet unless
130 | # the password is reset.
131 | if [[ -v MAILMAN_ADMIN_USER ]] && [[ -v MAILMAN_ADMIN_EMAIL ]];
132 | then
133 | echo "Creating admin user $MAILMAN_ADMIN_USER ..."
134 | python3 /opt/mailman-web/manage.py createsuperuser --noinput --username "$MAILMAN_ADMIN_USER"\
135 | --email "$MAILMAN_ADMIN_EMAIL" 2> /dev/null || \
136 | echo "Superuser $MAILMAN_ADMIN_USER already exists"
137 | fi
138 |
139 | # If SERVE_FROM_DOMAIN is defined then rename the default `example.com`
140 | # domain to the defined domain.
141 | if [[ -v SERVE_FROM_DOMAIN ]];
142 | then
143 | echo "Setting $SERVE_FROM_DOMAIN as the default domain ..."
144 | python3 /opt/mailman-web/manage.py shell -c \
145 | "from django.contrib.sites.models import Site; Site.objects.filter(domain='example.com').update(domain='$SERVE_FROM_DOMAIN', name='$SERVE_FROM_DOMAIN')"
146 | fi
147 |
148 | # Create a mailman user with the specific UID and GID and do not create home
149 | # directory for it. Also chown the logs directory to write the files.
150 | chown mailman:mailman /opt/mailman-web-data -R
151 |
152 | [[ -v DISKCACHE_PATH ]] && chown mailman:mailman "${DISKCACHE_PATH}" -R
153 |
154 | exec $@
155 |
--------------------------------------------------------------------------------
/core/README.md:
--------------------------------------------------------------------------------
1 | Mailman3 Core Docker Image
2 | ==========================
3 |
4 | When you spawn off this container, you must mount `/opt/mailman` to the
5 | container. Mailman's `var` directory will also be stored here so that it can
6 | persist across different sessions and containers. Any configuration at
7 | `/opt/mailman/core/mailman-extra.cfg` (on the host) will be added to the mailman's default
8 | generated confifguration (see below).
9 |
10 | It is not advised to run multiple mailman processes on the same host sharing the
11 | same `/opt/mailman` (`/opt/mailman/core` on the host) directory as this will
12 | almost certainly be dangerous.
13 |
14 |
15 | Configuration
16 | =============
17 |
18 | These are the variables that you MUST change before deploying:
19 |
20 | - `HYPERKITTY_API_KEY`: Hyperkitty's API Key, should be set to the same value as
21 | set for the mailman-core.
22 |
23 | - `DATABASE_URL`: URL of the type
24 | `driver://user:password@hostname:port/databasename` for the django to use. If
25 | not set, the default is set to
26 | `sqlite:///opt/mailman-web-data/mailmanweb.db`. The standard
27 | docker-compose.yaml comes with it set to a postgres database. It is not must
28 | to change this if you are happy with PostgreSQL.
29 |
30 | - `DATABASE_TYPE`: Its value can be one of `sqlite`, `postgres` or `mysql` as
31 | these are the only three database types that Mailman 3 supports. Its default
32 | value is set to `sqlite` along with the default database class and default
33 | database url above.
34 |
35 | - `DATABASE_CLASS`: Default value is
36 | `mailman.database.sqlite.SQLiteDatabase`. The values for this can be found in
37 | the mailman's documentation [here][11].
38 |
39 |
40 | These are the variables that you don't need to change if you are using a
41 | standard version of docker-compose.yaml from this repository.
42 |
43 | - `MM_HOSTNAME`: Which hostname or IP should Core bind to for REST API and
44 | LMTP. If not defined output from the `hostname -i` command is used.
45 |
46 | - `MAILMAN_REST_PORT`: Which port should Core use for the REST API. If not defined
47 | the default is `8001`.
48 |
49 | - `MAILMAN_REST_USER`: Which username should Core use for the REST API. If not
50 | defined the default is `restadmin`.
51 |
52 | - `MAILMAN_REST_PASSWORD`: Which password should Core use for the REST API. If
53 | not defined the default is `restpass`.
54 |
55 | - `MTA`: Mail Transfer Agent to use. Either `exim` or `postfix`. Default value is `exim`.
56 |
57 | - `SMTP_HOST`: IP Address/hostname from which you will be sending
58 | emails. Default value is the container's gateway retrieved from:
59 | /sbin/ip route | awk '/default/ { print $3 }'
60 |
61 | - `SMTP_PORT`: Port used for SMTP. Default is `25`.
62 |
63 | - `SMTP_SECURE_MODE`: Security mode (encryption) used for SMTP. Default is `smtp`. Can also be `starttls` or `smtps`.
64 |
65 | - `HYPERKITTY_URL`: Default value is `http://mailman-web:8000/hyperkitty`
66 |
67 | In case of a need for fine tuning of REST API web-server that uses [Gunicorn](https://docs.gunicorn.org/en/stable/settings.html) (e.g. for raising of timeouts) `/opt/mailman/core/gunicorn-extra.cfg` file could be provided holding necessary configuration options.
68 |
69 | Configuration file, [shipped with Mailman Core](https://gitlab.com/mailman/mailman/-/blob/master/src/mailman/config/gunicorn.cfg), is used by default.
70 |
71 | For example, to increase the default 30 sec timeout, which won't work for some API calls to highly populated lists, provide the following `gunicorn-extra.cfg` file:
72 |
73 | ```
74 | [gunicorn]
75 | graceful_timeout = 30
76 | timeout = 300
77 | ```
78 |
79 | Running Mailman-Core
80 | ====================
81 |
82 | It is highly recomended that you run this image along with the
83 | docker-compose.yaml file provided at the [github repo][1] of this
84 | image. However, it is possible to run this image as a standalone container if
85 | you want just a mailman-core.
86 |
87 | ```bash
88 | $ mkdir -p /opt/mailman/core
89 | $ docker run -it -e "HYPERKITTY_API_KEY=changeme" -h mailman-core -v /opt/mailman/core:/opt/mailman mailman-core
90 | ```
91 |
92 | However, if you don't provide the environment `DATABASE_URL`, the database _may_
93 | not be persisted. All the configuration options are explained in more detail.
94 |
95 | If you need mode advanced configuration for mailman, you can create
96 | `/opt/mailman/mailman.cfg` and it be added to the configuration inside the
97 | container. Note that anything inside this configuration will override the
98 | settings provided via the environment variables and their default values.
99 |
100 | By default, the following settings are generated:
101 |
102 | ```
103 | # mailman.cfg
104 | [mta]
105 | incoming: mailman.mta.exim4.LMTP
106 | outgoing: mailman.mta.deliver.deliver
107 | lmtp_host: $MM_HOSTNAME
108 | lmtp_port: 8024
109 | smtp_host: $SMTP_HOST
110 | smtp_port: $SMTP_PORT
111 | smtp_secure_mode: $SMTP_SECURE_MODE
112 | smtp_verify_hostname: $SMTP_VERIFY_HOSTNAME
113 | smtp_verify_cert: $SMTP_VERIFY_CERT
114 | configuration: python:mailman.config.exim4
115 |
116 | [runner.retry]
117 | sleep_time: 10s
118 |
119 | [webservice]
120 | hostname: $MM_HOSTNAME
121 | port: $MAILMAN_REST_PORT
122 | admin_user: $MAILMAN_REST_USER
123 | admin_pass: $MAILMAN_REST_PASSWORD
124 | configuration: /etc/gunicorn.cfg
125 |
126 | [archiver.hyperkitty]
127 | class: mailman_hyperkitty.Archiver
128 | enable: yes
129 | configuration: /etc/mailman-hyperkitty.cfg
130 |
131 | [database]
132 | class: $DATABASE_CLASS
133 | url: $DATABASE_URL
134 | ```
135 |
136 | ```
137 | # mailman-hyperkitty.cfg
138 | [general]
139 | base_url: $HYPERKITTY_URL
140 | api_key: $HYPERKITTY_API_KEY
141 | ```
142 |
143 | MTA
144 | ===
145 |
146 | You can use Postfix or [Exim][2] with this image to send emails. Mailman Core
147 | can interact with any modern MTA which can deliver emails over LMTP. The
148 | documentation for Mailman Core has configuration settigs for using them.
149 |
150 | Only Exim and Postfix have been tested with these images and are supported as of
151 | now. There _might_ be some limitations with using other MTAs in a containerized
152 | environments. Contributions are welcome for anything additional needed to
153 | support other MTAs.
154 |
155 | To setup Exim or Posfix, checkout the [documentation][3].
156 |
157 | [1]: https://github.com/maxking/docker-mailman
158 | [2]: http://www.exim.org
159 | [3]: https://asynchronous.in/docker-mailman#setting-up-your-mta
160 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # Mailman 3 Web UI
2 |
3 | This image consists of Mailman3's Web UI(Postorius) and Archiver
4 | (Hyperkitty). This image is built from latest sources on [gitlab][1]. In future,
5 | latest and stable releases will be seperate. I am looking forward to the release
6 | of Mailman Suite 3.1 before that.
7 |
8 | ## Configuration
9 |
10 |
11 | These are the settings that you MUST change before deploying:
12 |
13 | - `SERVE_FROM_DOMAIN`: The domain name from which Django will be served. To be
14 | added to `ALLOWED_HOSTS` in django settings. Default value is not set. This
15 | also replaces Django's default `example.com` SITE and becomes the default SITE
16 | (with SITE_ID=1).
17 |
18 | - `HYPERKITTY_API_KEY`: Hyperkitty's API Key, should be set to the same value as
19 | set for the mailman-core.
20 |
21 | - `MAILMAN_ADMIN_USER`: The username for the admin user to be created by default.
22 |
23 | - `MAILMAN_ADMIN_EMAIL`: The email for the admin user to be created by default.
24 |
25 | - `SECRET_KEY`: Django's secret key, mainly used for signing cookies and others.
26 |
27 | These are the settings that are set to sane default and you do not need to
28 | change them unless you know what you want.
29 |
30 | - `DATABASE_URL`: URL of the type
31 | `driver://user:password@hostname:port/databasename` for the django to use. If
32 | not set, the default is set to
33 | `sqlite:///opt/mailman-web-data/mailmanweb.db`. The standard
34 | docker-compose.yaml comes with it set to a postgres database. It is not must
35 | to change this if you are happy with PostgreSQL.
36 |
37 | - `MAILMAN_REST_URL`: The URL to the Mailman core's REST API server. Defaut
38 | value is `http://mailman-core:8001`.
39 |
40 | - `MAILMAN_REST_USER`: Mailman's REST API username. Default value is `restadmin`
41 |
42 | - `MAILMAN_REST_PASSWORD`: Mailman's REST API user's password. Default value is
43 | `restpass`
44 |
45 | - `MAILMAN_HOSTNAME`: IP of the Container from which Mailman will send emails to
46 | hyperkitty (django). Set to `mailman-core` by default.
47 |
48 | - `SMTP_HOST`: IP Address/hostname from which you will be sending
49 | emails. Default value is the container's gateway retrieved from:
50 | /sbin/ip route | awk '/default/ { print $3 }'
51 |
52 | - `SMTP_PORT`: Port used for SMTP. Default is `25`.
53 |
54 | - `SMTP_HOST_USER`: Used for SMTP authentication. Default is an empty string.
55 |
56 | - `SMTP_HOST_PASSWORD`: Default is an empty string.
57 |
58 | - `SMTP_USE_TLS`: Specifies wheather the SMTP connection is encrypted
59 | via TLS. Default is `False`. (`EMAIL_USE_TLS`/`EMAIL_USE_SSL` are mutually exclusive, so only set one of those settings.)
60 |
61 | - `SMTP_USE_SSL`: Specifies wheather the SMTP connection is encrypted
62 | via SSL. Default is `False`. (EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set one of those settings.)
63 |
64 | - `DJANGO_LOG_URL`: Path to the django's log file. Defaults to
65 | `/opt/mailman-web-data/logs/mailmanweb.log`.
66 |
67 | - `DJANGO_ALLOWED_HOSTS`: Entry to add to ALLOWED_HOSTS in Django
68 | configuration. Format as comma-separated list (no whitespace). This is a separate configuration from`SERVE_FROM_DOMAIN` as
69 | latter is used for other purposes too.
70 |
71 | - `POSTORIUS_TEMPLATE_BASE_URL`: The base url at which the `mailman-web`
72 | container can be reached from `mailman-core` container. This is set to
73 | `http://mailman-web:8000` by default so that Core can fetch templates from
74 | Web.
75 |
76 | - `DISKCACHE_PATH` and `DISKCACHE_SIZE`: Django Diskcache location path and
77 | size respectively. Defaults are `/opt/mailman-web-data/diskcache` and 1G.
78 |
79 | [1]: https://github.com/maxking/docker-mailman/blob/master/web/mailman-web/settings.py
80 |
81 | ## Social Auth
82 |
83 | In order to separate `INSTALLED_APPS` from the social authentication plugins a new settings `MAILMAN_WEB_SOCIAL_AUTH` is created. This includes all the enabled social auth plugins.
84 |
85 | ### Disable social auth
86 |
87 | In order to disable social auth, you can add the following to your
88 | settings_local.py
89 |
90 | ```python
91 | MAILMAN_WEB_SOCIAL_AUTH = []
92 | ```
93 |
94 | In older versions of continer images (0.3.*), you had to override
95 | `INSTALLED_APPS` in order to disable social auth, but addition of
96 | this new setting will make it easier to disable social auth making
97 | sure that you get any updates to the django apps that are added in
98 | future.
99 |
100 | The default behavior will remain the same as 0.3 release if you
101 | have not overriden `INSTALLED_APPS` though.
102 |
103 | ## Running
104 |
105 | It is highly recommended that you run this using the [docker-compose.yaml][2]
106 | provided in the [github repo][3] of this project. You will need to proxy the
107 | requests the container that you create with this image using an actual web
108 | server like Nginx. The [github repo][3] provides the setup instructions for
109 | Nginx.
110 |
111 | Since the setup has `USE_SSL` set to `True` in django's `settings.py`, you may
112 | also want to get a SSL certificate if you don't already have one. [Lets
113 | Encrypt][4] provides free SSL certiticates for everyone and there are _some_
114 | instructions about that also.
115 |
116 | After the first run, you can create a superuser for django using the following
117 | command:
118 |
119 | ```bash
120 | $ docker exec -it mailman-web python3 manage.py createsuperuser
121 | ```
122 |
123 | ## Django management commands
124 |
125 | In order to run Django management commands in the `mailman-web` container, you
126 | can run following:
127 |
128 | ```bash
129 | $ docker exec -it mailman-web python3 manage.py
130 | ```
131 |
132 | And replace `` with the appropriate management command.
133 |
134 |
135 | ## Importing Archives from Mailman 2
136 |
137 | In order to import archvies from Mailman 2, you need to get the `listname.mbox`
138 | file in a location that is readable inside `mailman-web` container.
139 |
140 | Please place `listname.mbox` file at `/opt/mailman/web` **on the host**. Verify
141 | that the file is present inside the `mailman-web` contianer by running:
142 |
143 | ```bash
144 | $ docker exec -it mailman-web ls /opt/mailman-web-data
145 | ```
146 | And verify that you can see `listname.mbox` in the `ls` output above. After you
147 | have verified that, you can then run the `hyperkitty_import` command to do the
148 | actual import:
149 |
150 | ```bash
151 | $ docker exec -it mailman-web python3 manage.py hyperkitty_import -l listname@domain /opt/mailman-web-data/listname.mbox
152 | ```
153 |
154 | This should take some time to import depending on how many emails are in the
155 | archives.
156 |
157 |
158 | [1]: https://gitlab.com/mailman
159 | [3]: https://github.com/maxking/docker-mailman/
160 | [2]: https://github.com/maxking/docker-mailman/blob/master/docker-compose.yaml
161 | [4]: https://letsencrypt.org
162 |
--------------------------------------------------------------------------------
/core/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | set -e
3 |
4 | function wait_for_postgres () {
5 | # Check if the postgres database is up and accepting connections before
6 | # moving forward.
7 | # TODO: Use python3's psycopg2 module to do this in python3 instead of
8 | # installing postgres-client in the image.
9 | until psql -P pager=off $DATABASE_URL -c '\l'; do
10 | >&2 echo "Postgres is unavailable - sleeping"
11 | sleep 1
12 | done
13 | >&2 echo "Postgres is up - continuing"
14 | }
15 |
16 | function wait_for_mysql () {
17 | # Check if MySQL is up and accepting connections.
18 | readarray -d' ' -t ENDPOINT <<< $(python3 -c "from urllib.parse import urlparse; o = urlparse('$DATABASE_URL'); print('%s %s' % (o.hostname, o.port if o.port else '3306'));")
19 | until mysqladmin ping --host ${ENDPOINT[0]} --port ${ENDPOINT[1]} --silent; do
20 | >&2 echo "MySQL is unavailable - sleeping"
21 | sleep 1
22 | done
23 | >&2 echo "MySQL is up - continuing"
24 | }
25 |
26 | # Empty the config file.
27 | echo "# This file is autogenerated at container startup." > /etc/mailman.cfg
28 |
29 | # Check if $MM_HOSTNAME is set, if not, set it to the value returned by
30 | # `hostname -i` command to set it to whatever IP address is assigned to the
31 | # container.
32 | if [[ ! -v MM_HOSTNAME ]]; then
33 | export MM_HOSTNAME=`hostname -i`
34 | fi
35 |
36 | # SMTP_HOST defaults to the gateway
37 | if [[ ! -v SMTP_HOST ]]; then
38 | export SMTP_HOST=$(/sbin/ip route | awk '/default/ { print $3 }')
39 | echo "SMTP_HOST not specified, using the gateway ($SMTP_HOST) as default"
40 | fi
41 |
42 | if [[ ! -v SMTP_PORT ]]; then
43 | export SMTP_PORT=25
44 | fi
45 |
46 | if [[ ! -v SMTP_SECURE_MODE ]]; then
47 | export SMTP_SECURE_MODE="smtp"
48 | fi
49 |
50 | if [[ ! -v SMTP_VERIFY_HOSTNAME ]]; then
51 | export SMTP_VERIFY_HOSTNAME="true"
52 | fi
53 |
54 | if [[ ! -v SMTP_VERIFY_CERT ]]; then
55 | export SMTP_VERIFY_CERT="true"
56 | fi
57 |
58 | # Check if REST port, username, and password are set, if not, set them
59 | # to default values.
60 | if [[ ! -v MAILMAN_REST_PORT ]]; then
61 | export MAILMAN_REST_PORT='8001'
62 | fi
63 |
64 | if [[ ! -v MAILMAN_REST_USER ]]; then
65 | export MAILMAN_REST_USER='restadmin'
66 | fi
67 |
68 | if [[ ! -v MAILMAN_REST_PASSWORD ]]; then
69 | export MAILMAN_REST_PASSWORD='restpass'
70 | fi
71 |
72 | function setup_database () {
73 | if [[ ! -v DATABASE_URL ]]
74 | then
75 | echo "Environment variable DATABASE_URL should be defined..."
76 | exit 1
77 | fi
78 |
79 | # Translate mysql:// urls to mysql+mysql:// backend:
80 | if [[ "$DATABASE_URL" == mysql://* ]]; then
81 | DATABASE_URL="mysql+pymysql://${DATABASE_URL:8}"
82 | echo "Database URL prefix was automatically rewritten to: mysql+pymysql://"
83 | fi
84 |
85 | # If DATABASE_CLASS is not set, guess it for common databases:
86 | if [ -z "$DATABASE_CLASS" ]; then
87 | if [[ ("$DATABASE_URL" == mysql:*) ||
88 | ("$DATABASE_URL" == mysql+*) ]]; then
89 | DATABASE_CLASS=mailman.database.mysql.MySQLDatabase
90 | fi
91 | if [[ ("$DATABASE_URL" == postgres:*) ||
92 | ("$DATABASE_URL" == postgres+*) ]]; then
93 | DATABASE_CLASS=mailman.database.postgresql.PostgreSQLDatabase
94 | fi
95 | fi
96 |
97 | cat >> /etc/mailman.cfg <> /etc/mailman.cfg << EOF
132 | [runner.retry]
133 | sleep_time: 10s
134 |
135 | [webservice]
136 | hostname: $MM_HOSTNAME
137 | port: $MAILMAN_REST_PORT
138 | admin_user: $MAILMAN_REST_USER
139 | admin_pass: $MAILMAN_REST_PASSWORD
140 | configuration: /etc/gunicorn.cfg
141 |
142 | EOF
143 |
144 | # Generate a basic gunicorn.cfg.
145 | SITE_DIR=$(python3 -c 'import site; print(site.getsitepackages()[0])')
146 | cp "${SITE_DIR}/mailman/config/gunicorn.cfg" /etc/gunicorn.cfg
147 |
148 | # Generate a basic configuration to use exim
149 | cat > /tmp/exim-mailman.cfg < /etc/postfix-mailman.cfg << EOF
167 | [postfix]
168 | transport_file_type: regex
169 | # While in regex mode, postmap_command is never used, a placeholder
170 | # is added here so that it doesn't break anything.
171 | postmap_command: true
172 | EOF
173 |
174 | # Generate a basic configuration to use postfix.
175 | cat > /tmp/postfix-mailman.cfg <> /etc/mailman.cfg
196 | elif [ "$MTA" == "postfix" ]
197 | then
198 | echo "Using Postfix configuration"
199 | cat /tmp/postfix-mailman.cfg >> /etc/mailman.cfg
200 | else
201 | echo "No MTA environment variable found, defaulting to Exim"
202 | cat /tmp/exim-mailman.cfg >> /etc/mailman.cfg
203 | fi
204 |
205 | rm -f /tmp/{postfix,exim}-mailman.cfg
206 |
207 | if [[ -e /opt/mailman/mailman-extra.cfg ]]
208 | then
209 | echo "Found configuration file at /opt/mailman/mailman-extra.cfg"
210 | cat /opt/mailman/mailman-extra.cfg >> /etc/mailman.cfg
211 | fi
212 |
213 | if [[ -e /opt/mailman/gunicorn-extra.cfg ]]
214 | then
215 | echo "Found [webserver] configuration file at /opt/mailman/gunicorn-extra.cfg"
216 | cat /opt/mailman/gunicorn-extra.cfg > /etc/gunicorn.cfg
217 | fi
218 |
219 | if [[ -v HYPERKITTY_API_KEY ]]; then
220 |
221 | echo "HYPERKITTY_API_KEY found, setting up HyperKitty archiver..."
222 |
223 | cat >> /etc/mailman.cfg << EOF
224 | [archiver.hyperkitty]
225 | class: mailman_hyperkitty.Archiver
226 | enable: yes
227 | configuration: /etc/mailman-hyperkitty.cfg
228 |
229 | EOF
230 |
231 | if [[ ! -v HYPERKITTY_URL ]]; then
232 | echo "HYPERKITTY_URL not set, using the default value of http://mailman-web:8000/hyperkitty"
233 | export HYPERKITTY_URL="http://mailman-web:8000/hyperkitty/"
234 | fi
235 |
236 | # Generate a basic mailman-hyperkitty.cfg.
237 | cat > /etc/mailman-hyperkitty.cfg <.
18 | """
19 | Django Settings for Mailman Suite (hyperkitty + postorius)
20 |
21 | For more information on this file, see
22 | https://docs.djangoproject.com/en/1.8/topics/settings/
23 |
24 | For the full list of settings and their values, see
25 | https://docs.djangoproject.com/en/1.8/ref/settings/
26 | """
27 |
28 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
29 | import os
30 | import dj_database_url
31 | import sys
32 | from socket import gethostbyname
33 |
34 | BASE_DIR = os.path.dirname(os.path.abspath(__file__))
35 |
36 | # SECURITY WARNING: keep the secret key used in production secret!
37 | SECRET_KEY = os.environ.get('SECRET_KEY')
38 |
39 | # SECURITY WARNING: don't run with debug turned on in production!
40 | DEBUG = False
41 |
42 | ADMINS = (
43 | ('Mailman Suite Admin', 'root@localhost'),
44 | )
45 |
46 | SITE_ID = 1
47 |
48 | # Hosts/domain names that are valid for this site; required if DEBUG is False
49 | # See https://docs.djangoproject.com/en/3.1/ref/settings/#allowed-hosts
50 | ALLOWED_HOSTS = [
51 | "localhost", # Archiving API from Mailman, keep it.
52 | "mailman-web",
53 | gethostbyname("mailman-web"),
54 | os.environ.get('SERVE_FROM_DOMAIN'),
55 | ]
56 | ALLOWED_HOSTS.extend(os.getenv("DJANGO_ALLOWED_HOSTS", "").split(","))
57 |
58 | # Mailman API credentials
59 | MAILMAN_REST_API_URL = os.environ.get('MAILMAN_REST_URL', 'http://mailman-core:8001')
60 | MAILMAN_REST_API_USER = os.environ.get('MAILMAN_REST_USER', 'restadmin')
61 | MAILMAN_REST_API_PASS = os.environ.get('MAILMAN_REST_PASSWORD', 'restpass')
62 |
63 | # Application definition
64 |
65 | INSTALLED_APPS = []
66 | DEFAULT_APPS = [
67 | 'postorius',
68 | 'django_mailman3',
69 | # Uncomment the next line to enable the admin:
70 | 'django.contrib.admin',
71 | # Uncomment the next line to enable admin documentation:
72 | # 'django.contrib.admindocs',
73 | 'django.contrib.auth',
74 | 'django.contrib.contenttypes',
75 | 'django.contrib.sessions',
76 | 'django.contrib.sites',
77 | 'django.contrib.messages',
78 | 'django.contrib.staticfiles',
79 | 'django.contrib.humanize',
80 | 'django_gravatar',
81 | 'allauth',
82 | 'allauth.account',
83 | 'allauth.socialaccount',
84 | ]
85 | MAILMAN_WEB_SOCIAL_AUTH = [
86 | 'django_mailman3.lib.auth.fedora',
87 | 'allauth.socialaccount.providers.openid',
88 | 'allauth.socialaccount.providers.github',
89 | 'allauth.socialaccount.providers.gitlab',
90 | 'allauth.socialaccount.providers.google',
91 | ]
92 |
93 | MIDDLEWARE = (
94 | 'django.contrib.sessions.middleware.SessionMiddleware',
95 | 'django.middleware.common.CommonMiddleware',
96 | 'django.middleware.csrf.CsrfViewMiddleware',
97 | 'django.middleware.locale.LocaleMiddleware',
98 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
99 | 'django.contrib.messages.middleware.MessageMiddleware',
100 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
101 | 'django.middleware.security.SecurityMiddleware',
102 | 'django_mailman3.middleware.TimezoneMiddleware',
103 | 'allauth.account.middleware.AccountMiddleware',
104 | 'postorius.middleware.PostoriusMiddleware',
105 | )
106 |
107 | ROOT_URLCONF = 'urls'
108 |
109 | TEMPLATES = [
110 | {
111 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
112 | 'DIRS': [],
113 | 'APP_DIRS': True,
114 | 'OPTIONS': {
115 | 'context_processors': [
116 | 'django.template.context_processors.debug',
117 | 'django.template.context_processors.i18n',
118 | 'django.template.context_processors.media',
119 | 'django.template.context_processors.static',
120 | 'django.template.context_processors.tz',
121 | 'django.template.context_processors.csrf',
122 | 'django.template.context_processors.request',
123 | 'django.contrib.auth.context_processors.auth',
124 | 'django.contrib.messages.context_processors.messages',
125 | 'django_mailman3.context_processors.common',
126 | 'postorius.context_processors.postorius',
127 | ],
128 | },
129 | },
130 | ]
131 |
132 | WSGI_APPLICATION = 'wsgi.application'
133 |
134 |
135 | # Database
136 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
137 |
138 |
139 | # This uses $DATABASE_URL from the environment variable to create a
140 | # django-style-config-dict.
141 | # https://github.com/kennethreitz/dj-database-url
142 | DATABASES = {
143 | 'default': dj_database_url.config(conn_max_age=600)
144 | }
145 |
146 | # Avoid Django 3.2+ warning
147 | # https://github.com/maxking/docker-mailman/issues/595
148 | DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
149 |
150 |
151 | # If you're behind a proxy, use the X-Forwarded-Host header
152 | # See https://docs.djangoproject.com/en/1.8/ref/settings/#use-x-forwarded-host
153 | USE_X_FORWARDED_HOST = True
154 |
155 |
156 | # Password validation
157 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
158 | AUTH_PASSWORD_VALIDATORS = [
159 | {
160 | 'NAME':
161 | 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
162 | },
163 | {
164 | 'NAME':
165 | 'django.contrib.auth.password_validation.MinimumLengthValidator',
166 | },
167 | {
168 | 'NAME':
169 | 'django.contrib.auth.password_validation.CommonPasswordValidator',
170 | },
171 | {
172 | 'NAME':
173 | 'django.contrib.auth.password_validation.NumericPasswordValidator',
174 | },
175 | ]
176 |
177 | # Internationalization
178 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
179 |
180 | LANGUAGE_CODE = 'en-us'
181 |
182 | TIME_ZONE = 'UTC'
183 |
184 | USE_I18N = True
185 |
186 | USE_L10N = True
187 |
188 | USE_TZ = True
189 |
190 | STATIC_ROOT = '/opt/mailman-web-data/static'
191 |
192 | STATIC_URL = '/static/'
193 |
194 | # Additional locations of static files
195 |
196 |
197 | # List of finder classes that know how to find static files in
198 | # various locations.
199 | STATICFILES_FINDERS = (
200 | 'django.contrib.staticfiles.finders.FileSystemFinder',
201 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
202 | )
203 |
204 |
205 | SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
206 |
207 | LOGIN_URL = 'account_login'
208 | LOGIN_REDIRECT_URL = 'list_index'
209 | LOGOUT_URL = 'account_logout'
210 |
211 |
212 | # Use SERVE_FROM_DOMAIN as the default domain in the email.
213 | hostname = os.environ.get('SERVE_FROM_DOMAIN', 'localhost.local')
214 | DEFAULT_FROM_EMAIL = 'postorius@{}'.format(hostname)
215 | SERVER_EMAIL = 'root@{}'.format(hostname)
216 |
217 | # Change this when you have a real email backend
218 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
219 | EMAIL_HOST = os.environ.get('SMTP_HOST', '')
220 | EMAIL_PORT = os.environ.get('SMTP_PORT', 25)
221 | EMAIL_HOST_USER = os.environ.get('SMTP_HOST_USER', '')
222 | EMAIL_HOST_PASSWORD = os.environ.get('SMTP_HOST_PASSWORD', '')
223 | EMAIL_USE_TLS = os.environ.get('SMTP_USE_TLS', False)
224 | EMAIL_USE_SSL = os.environ.get('SMTP_USE_SSL', False)
225 |
226 | # Compatibility with Bootstrap 3
227 | from django.contrib.messages import constants as messages # flake8: noqa
228 | MESSAGE_TAGS = {
229 | messages.ERROR: 'danger'
230 | }
231 |
232 |
233 | #
234 | # Social auth
235 | #
236 | AUTHENTICATION_BACKENDS = (
237 | 'django.contrib.auth.backends.ModelBackend',
238 | 'allauth.account.auth_backends.AuthenticationBackend',
239 | )
240 |
241 | # Django Allauth
242 | ACCOUNT_AUTHENTICATION_METHOD = "username_email"
243 | ACCOUNT_EMAIL_REQUIRED = True
244 | ACCOUNT_EMAIL_VERIFICATION = "mandatory"
245 | # You probably want https in production, but this is a dev setup file
246 | ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
247 | ACCOUNT_UNIQUE_EMAIL = True
248 |
249 | SOCIALACCOUNT_PROVIDERS = {
250 | 'openid': {
251 | 'SERVERS': [
252 | dict(id='yahoo',
253 | name='Yahoo',
254 | openid_url='http://me.yahoo.com'),
255 | ],
256 | },
257 | 'google': {
258 | 'SCOPE': ['profile', 'email'],
259 | 'AUTH_PARAMS': {'access_type': 'online'},
260 | },
261 | 'facebook': {
262 | 'METHOD': 'oauth2',
263 | 'SCOPE': ['email'],
264 | 'FIELDS': [
265 | 'email',
266 | 'name',
267 | 'first_name',
268 | 'last_name',
269 | 'locale',
270 | 'timezone',
271 | ],
272 | 'VERSION': 'v2.4',
273 | },
274 | }
275 |
276 |
277 | import sys
278 | # A sample logging configuration. The only tangible logging
279 | # performed by this configuration is to send an email to
280 | # the site admins on every HTTP 500 error when DEBUG=False.
281 | # See http://docs.djangoproject.com/en/dev/topics/logging for
282 | # more details on how to customize your logging configuration.
283 | LOGGING = {
284 | 'version': 1,
285 | 'disable_existing_loggers': False,
286 | 'filters': {
287 | 'require_debug_false': {
288 | '()': 'django.utils.log.RequireDebugFalse'
289 | }
290 | },
291 | 'handlers': {
292 | 'mail_admins': {
293 | 'level': 'ERROR',
294 | 'filters': ['require_debug_false'],
295 | 'class': 'django.utils.log.AdminEmailHandler'
296 | },
297 | 'file':{
298 | 'level': 'INFO',
299 | 'class': 'logging.handlers.RotatingFileHandler',
300 | #'class': 'logging.handlers.WatchedFileHandler',
301 | 'filename': os.environ.get('DJANGO_LOG_URL','/opt/mailman-web-data/logs/mailmanweb.log'),
302 | 'formatter': 'verbose',
303 | },
304 | 'console': {
305 | 'class': 'logging.StreamHandler',
306 | 'formatter': 'simple',
307 | 'level': 'INFO',
308 | 'stream': sys.stdout,
309 | },
310 | # TODO: use an environment variable $DJ_LOG_URL to configure the logging
311 | # using an environment variable.
312 | },
313 | 'loggers': {
314 | 'django.request': {
315 | 'handlers': ['mail_admins', 'file'],
316 | 'level': 'INFO',
317 | 'propagate': True,
318 | },
319 | 'django': {
320 | 'handlers': ['file'],
321 | 'level': 'INFO',
322 | 'propagate': True,
323 | },
324 | 'postorius': {
325 | 'handlers': ['file'],
326 | 'level': 'INFO',
327 | 'propagate': True
328 | },
329 | },
330 | 'formatters': {
331 | 'verbose': {
332 | 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s'
333 | },
334 | 'simple': {
335 | 'format': '%(levelname)s %(message)s'
336 | },
337 | },
338 | #'root': {
339 | # 'handlers': ['file'],
340 | # 'level': 'INFO',
341 | #},
342 | }
343 |
344 |
345 | if os.environ.get('LOG_TO_CONSOLE') == 'yes':
346 | LOGGING['loggers']['django']['handlers'].append('console')
347 | LOGGING['loggers']['django.request']['handlers'].append('console')
348 | POSTORIUS_TEMPLATE_BASE_URL = os.environ.get('POSTORIUS_TEMPLATE_BASE_URL', 'http://mailman-web:8000')
349 |
350 | try:
351 | from settings_local import *
352 | except ImportError:
353 | pass
354 |
355 | # Compatibility for older installs that override INSTALLED_APPS
356 | if not INSTALLED_APPS:
357 | INSTALLED_APPS = DEFAULT_APPS + MAILMAN_WEB_SOCIAL_AUTH
358 |
--------------------------------------------------------------------------------
/web/mailman-web/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Copyright (C) 1998-2016 by the Free Software Foundation, Inc.
3 | #
4 | # This file is part of Mailman Suite.
5 | #
6 | # Mailman Suite is free sofware: you can redistribute it and/or modify it
7 | # under the terms of the GNU General Public License as published by the Free
8 | # Software Foundation, either version 3 of the License, or (at your option)
9 | # any later version.
10 | #
11 | # Mailman Suite is distributed in the hope that it will be useful, but
12 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 | # for more details.
15 |
16 | # You should have received a copy of the GNU General Public License along
17 | # with Mailman Suite. If not, see .
18 | """
19 | Django Settings for Mailman Suite (hyperkitty + postorius)
20 |
21 | For more information on this file, see
22 | https://docs.djangoproject.com/en/1.8/topics/settings/
23 |
24 | For the full list of settings and their values, see
25 | https://docs.djangoproject.com/en/1.8/ref/settings/
26 | """
27 |
28 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
29 | import os
30 | import dj_database_url
31 | import sys
32 | from socket import gethostbyname, gaierror
33 |
34 | BASE_DIR = os.path.dirname(os.path.abspath(__file__))
35 |
36 | # SECURITY WARNING: keep the secret key used in production secret!
37 | SECRET_KEY = os.environ.get('SECRET_KEY')
38 |
39 | # SECURITY WARNING: don't run with debug turned on in production!
40 | DEBUG = False
41 |
42 | ADMINS = (
43 | ('Mailman Suite Admin', 'root@localhost'),
44 | )
45 |
46 | SITE_ID = 1
47 |
48 | # Hosts/domain names that are valid for this site; required if DEBUG is False
49 | # See https://docs.djangoproject.com/en/3.1/ref/settings/#allowed-hosts
50 | ALLOWED_HOSTS = [
51 | "localhost", # Archiving API from Mailman, keep it.
52 | "mailman-web",
53 | os.environ.get('SERVE_FROM_DOMAIN'),
54 | ]
55 |
56 | try:
57 | ALLOWED_HOSTS.append(gethostbyname("mailman-web")) # only add if this resolves
58 | except gaierror:
59 | pass
60 |
61 | ALLOWED_HOSTS.extend(os.getenv("DJANGO_ALLOWED_HOSTS", "").split(","))
62 |
63 | # Mailman API credentials
64 | MAILMAN_REST_API_URL = os.environ.get('MAILMAN_REST_URL', 'http://mailman-core:8001')
65 | MAILMAN_REST_API_USER = os.environ.get('MAILMAN_REST_USER', 'restadmin')
66 | MAILMAN_REST_API_PASS = os.environ.get('MAILMAN_REST_PASSWORD', 'restpass')
67 | MAILMAN_ARCHIVER_KEY = os.environ.get('HYPERKITTY_API_KEY')
68 | MAILMAN_ARCHIVER_FROM = (os.environ.get('MAILMAN_HOST_IP', gethostbyname(os.environ.get('MAILMAN_HOSTNAME', 'mailman-core'))),)
69 |
70 | # Application definition
71 |
72 | INSTALLED_APPS = []
73 | DEFAULT_APPS = [
74 | 'hyperkitty',
75 | 'postorius',
76 | 'django_mailman3',
77 | # Uncomment the next line to enable the admin:
78 | 'django.contrib.admin',
79 | # Uncomment the next line to enable admin documentation:
80 | # 'django.contrib.admindocs',
81 | 'django.contrib.auth',
82 | 'django.contrib.contenttypes',
83 | 'django.contrib.sessions',
84 | 'django.contrib.sites',
85 | 'django.contrib.messages',
86 | 'django.contrib.staticfiles',
87 | 'django.contrib.humanize',
88 | 'rest_framework',
89 | 'django_gravatar',
90 | 'compressor',
91 | 'haystack',
92 | 'django_extensions',
93 | 'django_q',
94 | 'allauth',
95 | 'allauth.account',
96 | 'allauth.socialaccount',
97 | ]
98 |
99 | MAILMAN_WEB_SOCIAL_AUTH = [
100 | 'django_mailman3.lib.auth.fedora',
101 | 'allauth.socialaccount.providers.openid',
102 | 'allauth.socialaccount.providers.github',
103 | 'allauth.socialaccount.providers.gitlab',
104 | 'allauth.socialaccount.providers.google',
105 | ]
106 |
107 | MIDDLEWARE = (
108 | 'django.contrib.sessions.middleware.SessionMiddleware',
109 | 'django.middleware.common.CommonMiddleware',
110 | 'django.middleware.csrf.CsrfViewMiddleware',
111 | 'django.middleware.locale.LocaleMiddleware',
112 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
113 | 'django.contrib.messages.middleware.MessageMiddleware',
114 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
115 | 'django.middleware.security.SecurityMiddleware',
116 | 'allauth.account.middleware.AccountMiddleware',
117 | 'django_mailman3.middleware.TimezoneMiddleware',
118 | 'postorius.middleware.PostoriusMiddleware',
119 | )
120 |
121 | ROOT_URLCONF = 'urls'
122 |
123 | TEMPLATES = [
124 | {
125 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
126 | 'DIRS': [],
127 | 'APP_DIRS': True,
128 | 'OPTIONS': {
129 | 'context_processors': [
130 | 'django.template.context_processors.debug',
131 | 'django.template.context_processors.i18n',
132 | 'django.template.context_processors.media',
133 | 'django.template.context_processors.static',
134 | 'django.template.context_processors.tz',
135 | 'django.template.context_processors.csrf',
136 | 'django.template.context_processors.request',
137 | 'django.contrib.auth.context_processors.auth',
138 | 'django.contrib.messages.context_processors.messages',
139 | 'django_mailman3.context_processors.common',
140 | 'hyperkitty.context_processors.common',
141 | 'postorius.context_processors.postorius',
142 | ],
143 | },
144 | },
145 | ]
146 |
147 | WSGI_APPLICATION = 'wsgi.application'
148 |
149 |
150 | # Database
151 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
152 | # dj_database_url uses $DATABASE_URL environment variable to create a
153 | # django-style-config-dict.
154 | # https://github.com/kennethreitz/dj-database-url
155 | DATABASES = {
156 | 'default': dj_database_url.config(conn_max_age=600)
157 | }
158 |
159 | # Avoid Django 3.2+ warning
160 | # https://github.com/maxking/docker-mailman/issues/595
161 | DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
162 |
163 |
164 | # If you're behind a proxy, use the X-Forwarded-Host header
165 | # See https://docs.djangoproject.com/en/1.8/ref/settings/#use-x-forwarded-host
166 | USE_X_FORWARDED_HOST = True
167 |
168 |
169 | # Password validation
170 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
171 | AUTH_PASSWORD_VALIDATORS = [
172 | {
173 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
174 | },
175 | {
176 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
177 | },
178 | {
179 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
180 | },
181 | {
182 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
183 | },
184 | ]
185 |
186 | # Internationalization
187 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
188 |
189 | LANGUAGE_CODE = 'en-us'
190 |
191 | TIME_ZONE = os.environ.get('TZ', 'UTC')
192 |
193 | USE_I18N = True
194 |
195 | USE_L10N = True
196 |
197 | USE_TZ = True
198 |
199 | STATIC_ROOT = '/opt/mailman-web-data/static'
200 |
201 | STATIC_URL = '/static/'
202 |
203 | # Additional locations of static files
204 |
205 |
206 | # List of finder classes that know how to find static files in
207 | # various locations.
208 | STATICFILES_FINDERS = (
209 | 'django.contrib.staticfiles.finders.FileSystemFinder',
210 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
211 | 'compressor.finders.CompressorFinder',
212 | )
213 |
214 |
215 | SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
216 |
217 | LOGIN_URL = 'account_login'
218 | LOGIN_REDIRECT_URL = 'list_index'
219 | LOGOUT_URL = 'account_logout'
220 |
221 |
222 | # Use SERVE_FROM_DOMAIN as the default domain in the email.
223 | hostname = os.environ.get('SERVE_FROM_DOMAIN', 'localhost.local')
224 | DEFAULT_FROM_EMAIL = 'postorius@{}'.format(hostname)
225 | SERVER_EMAIL = 'root@{}'.format(hostname)
226 |
227 | # Change this when you have a real email backend
228 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
229 | EMAIL_HOST = os.environ.get('SMTP_HOST', '')
230 | EMAIL_PORT = os.environ.get('SMTP_PORT', 25)
231 | EMAIL_HOST_USER = os.environ.get('SMTP_HOST_USER', '')
232 | EMAIL_HOST_PASSWORD = os.environ.get('SMTP_HOST_PASSWORD', '')
233 | EMAIL_USE_TLS = (os.environ.get('SMTP_USE_TLS', 'false').lower() == 'true')
234 | EMAIL_USE_SSL = (os.environ.get('SMTP_USE_SSL', 'false').lower() == 'true')
235 |
236 | # Compatibility with Bootstrap 3
237 | from django.contrib.messages import constants as messages # flake8: noqa
238 | MESSAGE_TAGS = {
239 | messages.ERROR: 'danger'
240 | }
241 |
242 |
243 | #
244 | # Social auth
245 | #
246 | AUTHENTICATION_BACKENDS = (
247 | 'django.contrib.auth.backends.ModelBackend',
248 | 'allauth.account.auth_backends.AuthenticationBackend',
249 | )
250 |
251 | # Django Allauth
252 | ACCOUNT_AUTHENTICATION_METHOD = "username_email"
253 | ACCOUNT_EMAIL_REQUIRED = True
254 | ACCOUNT_EMAIL_VERIFICATION = "mandatory"
255 | # You probably want https in production, but this is a dev setup file
256 | ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
257 | ACCOUNT_UNIQUE_EMAIL = True
258 |
259 | SOCIALACCOUNT_PROVIDERS = {
260 | 'openid': {
261 | 'SERVERS': [
262 | dict(id='yahoo',
263 | name='Yahoo',
264 | openid_url='http://me.yahoo.com'),
265 | ],
266 | },
267 | 'google': {
268 | 'SCOPE': ['profile', 'email'],
269 | 'AUTH_PARAMS': {'access_type': 'online'},
270 | },
271 | 'facebook': {
272 | 'METHOD': 'oauth2',
273 | 'SCOPE': ['email'],
274 | 'FIELDS': [
275 | 'email',
276 | 'name',
277 | 'first_name',
278 | 'last_name',
279 | 'locale',
280 | 'timezone',
281 | ],
282 | 'VERSION': 'v2.4',
283 | },
284 | }
285 |
286 |
287 | # django-compressor
288 | # https://pypi.python.org/pypi/django_compressor
289 | #
290 | COMPRESS_PRECOMPILERS = (
291 | ('text/less', 'lessc {infile} {outfile}'),
292 | ('text/x-scss', 'sassc -t compressed {infile} {outfile}'),
293 | ('text/x-sass', 'sassc -t compressed {infile} {outfile}'),
294 | )
295 |
296 | # On a production setup, setting COMPRESS_OFFLINE to True will bring a
297 | # significant performance improvement, as CSS files will not need to be
298 | # recompiled on each requests. It means running an additional "compress"
299 | # management command after each code upgrade.
300 | # http://django-compressor.readthedocs.io/en/latest/usage/#offline-compression
301 | # COMPRESS_OFFLINE = True
302 |
303 | #
304 | # Full-text search engine
305 | #
306 | HAYSTACK_CONNECTIONS = {
307 | 'default': {
308 | 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
309 | 'PATH': "/opt/mailman-web-data/fulltext_index",
310 | # You can also use the Xapian engine, it's faster and more accurate,
311 | # but requires another library.
312 | # http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian
313 | # Example configuration for Xapian:
314 | #'ENGINE': 'xapian_backend.XapianEngine'
315 | },
316 | }
317 |
318 | import sys
319 | # A sample logging configuration. The only tangible logging
320 | # performed by this configuration is to send an email to
321 | # the site admins on every HTTP 500 error when DEBUG=False.
322 | # See http://docs.djangoproject.com/en/dev/topics/logging for
323 | # more details on how to customize your logging configuration.
324 | LOGGING = {
325 | 'version': 1,
326 | 'disable_existing_loggers': False,
327 | 'filters': {
328 | 'require_debug_false': {
329 | '()': 'django.utils.log.RequireDebugFalse'
330 | }
331 | },
332 | 'handlers': {
333 | 'mail_admins': {
334 | 'level': 'ERROR',
335 | 'filters': ['require_debug_false'],
336 | 'class': 'django.utils.log.AdminEmailHandler'
337 | },
338 | 'file':{
339 | 'level': 'INFO',
340 | 'class': 'logging.handlers.RotatingFileHandler',
341 | #'class': 'logging.handlers.WatchedFileHandler',
342 | 'filename': os.environ.get('DJANGO_LOG_URL','/opt/mailman-web-data/logs/mailmanweb.log'),
343 | 'formatter': 'verbose',
344 | },
345 | 'console': {
346 | 'class': 'logging.StreamHandler',
347 | 'formatter': 'simple',
348 | 'level': 'INFO',
349 | 'stream': sys.stdout,
350 | },
351 | },
352 | 'loggers': {
353 | 'django.request': {
354 | 'handlers': ['mail_admins', 'file'],
355 | 'level': 'INFO',
356 | 'propagate': True,
357 | },
358 | 'django': {
359 | 'handlers': ['file'],
360 | 'level': 'INFO',
361 | 'propagate': True,
362 | },
363 | 'hyperkitty': {
364 | 'handlers': ['file'],
365 | 'level': 'INFO',
366 | 'propagate': True,
367 | },
368 | 'postorius': {
369 | 'handlers': ['file'],
370 | 'level': 'INFO',
371 | 'propagate': True
372 | },
373 | },
374 | 'formatters': {
375 | 'verbose': {
376 | 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s'
377 | },
378 | 'simple': {
379 | 'format': '%(levelname)s %(message)s'
380 | },
381 | },
382 | }
383 |
384 |
385 | if os.environ.get('LOG_TO_CONSOLE') == 'yes':
386 | LOGGING['loggers']['django']['handlers'].append('console')
387 | LOGGING['loggers']['django.request']['handlers'].append('console')
388 |
389 | # HyperKitty-specific
390 | #
391 | # Only display mailing-lists from the same virtual host as the webserver
392 | FILTER_VHOST = False
393 |
394 |
395 | Q_CLUSTER = {
396 | 'timeout': 300,
397 | 'retry': 300,
398 | 'save_limit': 100,
399 | 'orm': 'default',
400 | }
401 |
402 | POSTORIUS_TEMPLATE_BASE_URL = os.environ.get('POSTORIUS_TEMPLATE_BASE_URL', 'http://mailman-web:8000')
403 |
404 | DISKCACHE_PATH = os.environ.get('DISKCACHE_PATH', '/opt/mailman-web-data/diskcache')
405 | DISKCACHE_SIZE = os.environ.get('DISKCACHE_SIZE', 2 ** 30) # 1 gigabyte
406 |
407 | CACHES = {
408 | 'default': {
409 | 'BACKEND': 'diskcache.DjangoCache',
410 | 'LOCATION': DISKCACHE_PATH,
411 | 'OPTIONS': {
412 | 'size_limit': DISKCACHE_SIZE,
413 | },
414 | },
415 | }
416 |
417 | try:
418 | from settings_local import *
419 | except ImportError:
420 | pass
421 |
422 | # Compatibility for older installs that override INSTALLED_APPS
423 | if not INSTALLED_APPS:
424 | INSTALLED_APPS = DEFAULT_APPS + MAILMAN_WEB_SOCIAL_AUTH
425 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /
3 | ---
4 |
5 |
6 | # GNU Mailman 3 Deployment with Docker
7 |
8 |
9 |
10 | [](https://circleci.com/gh/maxking/docker-mailman/tree/main)
11 |
12 | This repository hosts code for two docker images `maxking/mailman-core` and
13 | `maxking/mailman-web` both of which are meant to deploy [GNU Mailman 3][1] in
14 | a production environment.
15 |
16 | [Docker][2] is a container ecosystem which can run containers on several
17 | platforms. It consists of a tool called [docker-compose][3] which can be used to
18 | run multi-container applications. This repository consists of a
19 | [`docker-compose.yaml`][19] file which is a set of
20 | configurations that can be used to deploy the [Mailman 3 Suite][4].
21 |
22 | Please see [release page](https://github.com/maxking/docker-mailman/releases)
23 | for the releases and change log.
24 |
25 | ## Release
26 |
27 |
28 | The tags for the images are assumed to be release versions for images. This is
29 | going to be a somewhat common philosophy of distributing Container images where
30 | the images with same tags are usually updated with the new functionality.
31 |
32 | Releases will follow the following rules:
33 |
34 | * Images tagged like A.B.C will never change. If you want to pin down versions
35 | of Images, use these tags.
36 |
37 | * Images tagged with A.B will correspond to the latest A.B.C version
38 | released. Releases in A.B series are supposed to be backwards compatible,
39 | i.e., any existing installation should not break when upgrading between
40 | subversions of A.B.C. So, if you want the latest updates and want to
41 | frequently update your installation without having to change the version
42 | numbers, you can use this.
43 |
44 | * Any changes in the minor version of Mailman components of the images will
45 | cause a bump in the minor version, e.g., A.(B+1) can have one or more
46 | updated Mailman components from A.B. Also, significant change in functionality,
47 | that might change how Images work or how people interact with the containers
48 | can also cause a bump in the minor version.
49 |
50 | * Major versions will change either when there are backwards incompatible
51 | changes or when the releases reach a certain set milestone or when there are
52 | bugfix releases for the internal components or both.
53 |
54 |
55 | ## Container Registries
56 |
57 | The container images are available from multiple container registries. Do specify an [explicit version tag](https://hub.docker.com/r/maxking/mailman-web/tags?page=1&ordering=last_updated&name=0.) (e.g. `0.4.5` , MAJOR.MINOR like `0.4` also works as floating tag pointing to latest patch version) as tag `latest` is **not** updated anymore.
58 |
59 | ### Mailman Core
60 |
61 | - `ghcr.io/maxking/mailman-core`
62 | - `docker.io/maxking/mailman-core`
63 |
64 | ### Mailman Web
65 |
66 | - `ghcr.io/maxking/mailman-web`
67 | - `docker.io/maxking/mailman-web`
68 |
69 | ### Postorius
70 |
71 | - `ghcr.io/maxking/postorius`
72 | - `docker.io/maxking/postorius`
73 |
74 | ## Rolling Releases
75 |
76 | Rolling releases are made up of Mailman Components installed from [git
77 | source](https://gitlab.com/mailman). **Note that these releases are made up of
78 | un-released software and should be assumed to be beta quality.**
79 |
80 | Every commit is tested with Mailman's CI infrastructure and is included in
81 | rolling releases only if they have passed the complete test suite.
82 |
83 | ```bash
84 | $ docker pull docker.io/maxking/mailman-web:rolling
85 | $ docker pull docker.io/maxking/mailman-core:rolling
86 | ```
87 |
88 | Rolling releases are built with every commit and also re-generated nightly. You
89 | can inspect the images to get which commit it was built using:
90 |
91 | ```bash
92 | $ docker inspect --format '{{json .Config.Labels }}' mailman-core | python -m json.tool
93 | {
94 | "version.git_commit": "45a4d7805b2b3d0e7c51679f59682d64ba02f05f",
95 | }
96 |
97 | $ docker inspect --format '{{json .Config.Labels }}' mailman-web | python -m json.tool
98 | {
99 | "version.git_commit": "45a4d7805b2b3d0e7c51679f59682d64ba02f05f",
100 | }
101 |
102 | ```
103 |
104 | - `version.git_commit` : This is the commit hash of the Dockerfile in the
105 | [Github repo](https://github.com/maxking/docker-mailman)
106 |
107 | ## Dependencies
108 |
109 | - Docker
110 | - Docker-compose-plugin
111 |
112 | To install these on Ubuntu/Debian:
113 |
114 | ```
115 | $ sudo apt install docker.io docker-compose-plugin
116 | ```
117 |
118 | For other systems, you can read the official Docker documentation to install
119 | [Docker from here][5] and [docker compose from here][6].
120 |
121 |
122 | ## Configuration
123 |
124 | Most of the common configuration is handled through environment variables in the
125 | `docker-compose.yaml`. However, there is need for some extra configuration that
126 | interacts directly with the application. There are two configuration files on
127 | the host that interact directly with Mailman's settings. These files exist on
128 | the host running the containers and are imported at runtime in the containers.
129 |
130 | * `/opt/mailman/core/mailman-extra.cfg` : This is the configuration for Mailman
131 | Core and anything that you add here will be added to Core's configuration. You
132 | need to restart your mailman-core container for the changes in this file to
133 | take effect.
134 |
135 | * `/opt/mailman/web/settings_local.py` : This is the Django configuration that
136 | is imported by the [existing configuration][2]
137 | provided by the mailman-web container. **This file is referred to as
138 | `settings.py` in most of the Postorius and Django documentation.** To change
139 | or override any settings in Django/Postorius, you need to create/edit this file.
140 | A useful configuration for troubleshooting is `DEBUG = True`.
141 |
142 | [2]: https://github.com/maxking/docker-mailman/blob/master/web/mailman-web/settings.py
143 |
144 | Also, note that if you need any other files to be accessible from the host to
145 | inside the container, you can place them at certain directories which are
146 | mounted inside the containers.
147 |
148 |
149 | * `/opt/mailman/core` in host maps to `/opt/mailman/` in mailman-core container.
150 | * `/opt/mailman/web` in host maps to `/opt/mailman-web-data` in mailman-web
151 | container.
152 |
153 | ### Mailman-web
154 | These are the settings that you MUST change in your docker-compose.yaml before deploying:
155 |
156 | - `SERVE_FROM_DOMAIN`: The domain name from which Django will be served. To be
157 | added to `ALLOWED_HOSTS` in django settings. Default value is not set. This
158 | also replaces Django's default `example.com` SITE and becomes the default SITE
159 | (with SITE_ID=1).
160 |
161 | - `HYPERKITTY_API_KEY`: Hyperkitty's API Key, should be set to the same value
162 | as set for the mailman-core. (Not needed in case of Postorius-only version.)
163 |
164 | - `MAILMAN_ADMIN_USER`: The username for the admin user to be created by default.
165 |
166 | - `MAILMAN_ADMIN_EMAIL`: The email for the admin user to be created by default.
167 |
168 | - `SECRET_KEY`: Django's secret key, mainly used for signing cookies and others.
169 |
170 | Please note here that if you choose to create the admin user using the
171 | environment variables mentioned above (`MAILMAN_ADMIN_USER` &
172 | `MAILMAN_ADMIN_EMAIL`), no password is set for your admin account. To set a
173 | password, plese follow the "Forgot Password" link on the "Sign In" page.
174 |
175 | Mailman web is already configured to send emails through `$SMTP_HOST` as the
176 | MTA's address. If you want to modify it, you can set the value in under
177 | docker-compose.yaml for mailman-web container. By default, `SMTP_HOST` points
178 | to the gateway of the web container, which is the host itself.
179 |
180 | You can also use the environment variables `SMTP_HOST` (defaults to
181 | the container's gateway), `SMTP_PORT` (defaults to `25`), `SMTP_HOST_USER` (defaults to
182 | an empty string), `SMTP_HOST_PASSWORD` (defaults to an empty string),
183 | `SMTP_USE_TLS` (defaults to `False`) and `SMTP_USE_SSL` (defaults to `False`).
184 |
185 | This is required in addition to the [Setup your MTA](#setting-up-your-mta)
186 | section below, which covers email setup for Mailman Core.
187 |
188 | For more details on how to configure this image, please look at
189 | [Mailman-web's Readme](web/)
190 |
191 | ### Mailman-Core
192 |
193 | These are the variables that you MUST change in your docker-compose.yaml before deploying:
194 |
195 | - `HYPERKITTY_API_KEY`: Hyperkitty's API Key, should be set to the same value as
196 | set for the mailman-web. Skip the variable in case of non-Hyperkitty deployment.
197 |
198 | - `DATABASE_URL`: URL of the type
199 | `driver://user:password@hostname:port/databasename` for the django to use. If
200 | not set, the default is set to
201 | `sqlite:///opt/mailman-web-data/mailmanweb.db`. The standard
202 | docker-compose.yaml comes with it set to a postgres database. There is no need
203 | to change this if you are happy with PostgreSQL.
204 |
205 | - `DATABASE_TYPE`: Its value can be one of `sqlite`, `postgres` or `mysql` as
206 | these are the only three database types that Mailman 3 supports. Its default
207 | value is set to `sqlite` along with the default database class and default
208 | database url above.
209 |
210 | - `DATABASE_CLASS`: Default value is
211 | `mailman.database.sqlite.SQLiteDatabase`. The values for this can be found in
212 | the mailman's documentation [here][11].
213 | - `SMTP_HOST` : outgoing host for SMTP connections
214 | - `SMTP_PORT` : use this port. 25, 587, whatever your host asks for.
215 | - `SMTP_HOST_USER`: authenticate this user
216 | - `SMTP_HOST_PASSWORD`: and use this password
217 | - `SMTP_SECURE_MODE`: security mode for smtp connection - can be `smtp` (no encryption), `smtps` or `starttls`
218 | - `SMTP_VERIFY_HOSTNAME`: defaults to `true` - verify, that certificate hostname is identical to `SMTP_HOST`
219 | - `SMTP_VERIFY_CERT`: defaults to `true` - verify, that certificate is valid
220 |
221 | For more details on how to configure this image, please look [Mailman-core's
222 | Readme](core/)
223 |
224 |
225 | While the above configuration will allow you to run the images and possibly view
226 | the Web Frontend, it won't be functional until it is fully configured to to send
227 | emails.
228 |
229 | To configure the mailman-core container to send emails, see the [Setting your MTA
230 | section below](#setting-up-your-mta).
231 |
232 | ## Running
233 |
234 | To run the containers, simply run:
235 |
236 | ```bash
237 | $ mkdir -p /opt/mailman/core
238 | $ mkdir -p /opt/mailman/web
239 | $ git clone https://github.com/maxking/docker-mailman
240 | $ cd docker-mailman
241 | # Change some configuration variables as mentioned above.
242 | $ docker compose up -d
243 | ```
244 |
245 | Note that the web frontend in the mailman-web container is, by default, only
246 | configured to serve dynamic content. Anything static like stylesheets, etc., is
247 | expected to be served directly by the web server. The static content exists at
248 | `/opt/mailman/web/static` and should be _aliased_ to `/static/` in the web
249 | server configuration.
250 |
251 | See [the nginx configuration][17] as an example.
252 |
253 | This command will do several things, most importantly:
254 |
255 | - Run a wsgi server using [`uwsgi`][7] for the Mailman's Django-based web
256 | frontend listening on port 8000. It will run 2 worker
257 | processes with 4 threads each. You may want to change the setting
258 | `ALLOWED_HOSTS` in the settings before deploying the application in
259 | production.
260 |
261 | - Run a PostgreSQL server with a default database, username, and password as
262 | mentioned in the `docker-compose.yaml`. You will have to change configuration
263 | files too if you change any of these.
264 |
265 | - Run mailman-core listening on port 8001 for REST API and port 8024 (LMTP
266 | server) for messages from your MTA. You will have to configure your MTA to
267 | send messages at this address.
268 |
269 | Some more details about what the above system achieves is mentioned below. If you
270 | are only going to deploy a simple configuration, you don't need to read
271 | this. However, these are very easy to understand if you know how docker works.
272 |
273 | - First create a bridge network called `mailman` in the
274 | `docker-compose.yaml`. It will probably be named something else in your
275 | machine. All the containers
276 | mentioned (mailman-core, mailman-web, database) will join this network and are
277 | assigned static IPs. The host operating system is the default gateway
278 | from within these containers.
279 |
280 | - Spin off a mailman-core container attached to the mailman bridge network created above. It has
281 | GNU Mailman 3 core running inside it. Mailman core's REST API is available at
282 | port 8001 and LMTP server listens at port 8024.
283 |
284 | - Spin off a mailman-web container which has a Django application running with
285 | both Mailman's web frontend Postorius and Mailman's web-based Archiver
286 | running. [Uwsgi][7] server is used to run a web server with the configuration
287 | provided in this repository [here][2]. You may want to
288 | change the setting `ALLOWED_HOSTS` in the settings before deploying the
289 | application in production. You can do that by adding a
290 | `/opt/mailman/web/settings_local.py` which is imported by the Django when
291 | running.
292 |
293 | - Spin off a PostgreSQL database container which is used by both mailman-core
294 | and mailman-web as their primary database.
295 |
296 | - mailman-core mounts `/opt/mailman/core` from host OS at `/opt/mailman` in the
297 | container. Mailman's var directory is stored there so that it is accessible
298 | from the host operating system. Configuration for Mailman core is generated on
299 | every run from the environment variables provided. Extra configuration can
300 | also be provided at `/opt/mailman/core/mailman-extra.cfg` (on host), and will
301 | be added to generated configuration file. Mailman also needs another
302 | configuration file called
303 | [mailman-hyperkitty.cfg][3] and is also
304 | expected to be at `/opt/mailman/core/` on the host OS.
305 |
306 | [3]: https://github.com/maxking/docker-mailman/blob/master/core/assets/mailman-hyperkitty.cfg
307 |
308 | - mailman-web mounts `/opt/mailman/web` from the host OS to
309 | `/opt/mailman-web-data` in the container. It consists of the logs and
310 | settings_local.py file for Django.
311 |
312 | - database mounts `/opt/mailman/database` at `/var/lib/postgresql/data` so that
313 | PostgreSQL can persist its data even if the database containers are
314 | updated/changed/removed.
315 |
316 | ## Setting up your MTA
317 |
318 | The provided docker containers do not have an MTA in-built. You can either run
319 | your own MTA inside a container and have them relay emails to the mailman-core
320 | container or just install an MTA on the host and have them relay emails.
321 |
322 | ### Exim4
323 |
324 | To use [Exim4][8], it should be setup to relay emails from mailman-core and
325 | mailman-web. The mailman specific configuration is provided in the
326 | repository at `core/assets/exim`. There are three files
327 |
328 | - [25_mm3_macros](core/assets/exim/25_mm3_macros) to be placed at
329 | `/etc/exim4/conf.d/main/25_mm3_macros` in a typical Debian install of
330 | exim4. Please change MY_DOMAIN_NAME to the domain name that will be used to
331 | serve mailman. Multi-domain setups will be added later.
332 |
333 | - [455_mm3_router](core/assets/exim/455_mm3_router) to be placed at
334 | `/etc/exim4/conf.d/router/455_mm3_router` in a typical Debian install of exim4.
335 |
336 | - [55_mm3_transport](core/assets/exim/55_mm3_transport) to be placed at
337 | `/etc/exim4/conf.d/transport/55_mm3_transport` in a typical Debian install of exim4.
338 |
339 |
340 | Also, the default configuration inside the mailman-core image has the MTA set
341 | to Exim, but just for reference, it looks like this:
342 |
343 | ```
344 | # mailman.cfg
345 | [mta]
346 | incoming: mailman.mta.exim4.LMTP
347 | outgoing: mailman.mta.deliver.deliver
348 | lmtp_host: $MM_HOSTNAME
349 | lmtp_port: 8024
350 | smtp_host: $SMTP_HOST
351 | smtp_port: $SMTP_PORT
352 | configuration: python:mailman.config.exim4
353 | ```
354 |
355 | ### Postfix
356 |
357 | To use [Postfix][12], edit the `main.cf` configuration file, which is typically
358 | at `/etc/postfix/main.cf` on Debian-based operating systems. Add
359 | mailman-core and mailman-web to `mynetworks` so it will relay emails from
360 | the containers and add the following configuration lines:
361 |
362 | ```
363 | # main.cf
364 |
365 | # Support the default VERP delimiter.
366 | recipient_delimiter = +
367 | unknown_local_recipient_reject_code = 550
368 | owner_request_special = no
369 |
370 | transport_maps =
371 | regexp:/opt/mailman/core/var/data/postfix_lmtp
372 | local_recipient_maps =
373 | regexp:/opt/mailman/core/var/data/postfix_lmtp
374 | relay_domains =
375 | regexp:/opt/mailman/core/var/data/postfix_domains
376 | ```
377 |
378 | To configure Mailman to use Postfix, add `MTA=postfix` under mailman-core's
379 | environment section in the `docker-compose.yaml`:
380 |
381 | ```
382 | mailman-core:
383 |
384 | environment:
385 | - MTA=postfix
386 | ```
387 |
388 | This will auto-generate the configuration to talk to Postfix assuming that
389 | Postfix is available at the gateway address for the container's bridge network
390 | at port 25. The final configuration can be found by executing:
391 |
392 | ```
393 | $ docker exec mailman-core cat /etc/mailman.cfg
394 | ```
395 |
396 | The postfix configuration that is generated looks like this:
397 | ```
398 | [mta]
399 | incoming: mailman.mta.postfix.LMTP
400 | outgoing: mailman.mta.deliver.deliver
401 | lmtp_host: $MM_HOSTNAME
402 | lmtp_port: 8024
403 | smtp_host: $SMTP_HOST
404 | smtp_port: $SMTP_PORT
405 | configuration: /etc/postfix-mailman.cfg
406 | ```
407 |
408 | So, if you need to update the values, you can set `SMTP_HOST`, `SMTP_PORT`,
409 | `MM_HOSTNAME` environment variables in `mailman-core` container.
410 |
411 | Please verify the output for `[mta]` section to ensure that it points to
412 | the right `smtp_host` (address to reach postfix from mailman-core container)
413 | and `lmtp_host` (address to reach mailman-core container from postfix).
414 |
415 | The configuration file `/etc/postfix-mailman.cfg` is also generated automatically
416 | inside the `mailman-core` container and contains the configuration specific
417 | for Postfix.
418 |
419 | ## Site Owner
420 |
421 | Setup site owner address. By default, mailman is setup with the site_owner set to 'changeme@example.com'. This should be pointing to a valid mailbox. Add the following to the '/opt/mailman/core/mailman-extra.cfg'.
422 |
423 | ```
424 | [mailman]
425 | # This address is the "site owner" address. Certain messages which must be
426 | # delivered to a human, but which can't be delivered to a list owner (e.g. a
427 | # bounce from a list owner), will be sent to this address. It should point to
428 | # a human.
429 | site_owner: changeme@example.com
430 | ```
431 |
432 | ## Setting up search indexing
433 |
434 | Hyperkitty in mailman-web image support full-text indexing. The current default
435 | indexing engine is [Whoosh](https://whoosh.readthedocs.io/en/latest/intro.html)
436 | for historical reasons. It is highly recommended that you instead use Xapian for
437 | production use cases. The default will change when the next major version bump
438 | happens.
439 |
440 | To configure your Mailman-web container to use Xapian, add the following to your
441 | `settings_local.py`:
442 |
443 | ```python
444 | HAYSTACK_CONNECTIONS = {
445 | 'default': {
446 | 'ENGINE': 'xapian_backend.XapianEngine',
447 | 'PATH': "/opt/mailman-web-data/fulltext_index",
448 | },
449 | }
450 | ```
451 |
452 | If you have been using the default search indexing engine, you might have to
453 | re-index emails using the following command:
454 |
455 | ```bash
456 | $ docker compose exec mailman-web ./manage.py rebuild_index
457 | ```
458 |
459 | This command can take some time if you a lot of emails, so please be patient!
460 |
461 | ## Setting up your web server
462 |
463 | It is advisable to run your Django (interfaced through WSGI server) through an
464 | _actual_ webserver in production for better performance.
465 |
466 | If you are using v0.1.0, the uwsgi server is configured to listen to requests at
467 | port `8000` using the `HTTP` protocol. Make sure that you preserve the `HOST`
468 | header when you proxy the requests from your Web Server. In Nginx you can do
469 | that by adding the following to your configuration:
470 |
471 | ```
472 | # Nginx configuration.
473 | location /static {
474 | alias /opt/mailman/web/static;
475 | autoindex off;
476 | }
477 |
478 | location / {
479 | proxy_pass http://127.0.0.1:8000;
480 | include uwsgi_params;
481 | uwsgi_read_timeout 300;
482 | proxy_set_header Host $host;
483 | proxy_set_header X-Forwarded-For $remote_addr;
484 | }
485 |
486 | ```
487 |
488 | Make sure you are using `proxy_pass` for the `HTTP` protocol.
489 |
490 | ### uwsgi
491 |
492 |
493 | Starting from v0.1.1, the uwsgi server is configured to listen to requests at
494 | port `8000` with the http protocol and port `8080` for the uwsgi
495 | protocol.
496 |
497 | **Please make sure that you are using port 8080 for uwsgi protocol.**
498 |
499 | It is advised to use the uwsgi protocol as it has better performance. Both
500 | Apache and Nginx have native support for the uwsgi protocol through plugins which
501 | are generally included in the distro packages.
502 |
503 | To move to uwsgi protocol in the above nginx configuration use this
504 |
505 | ```
506 | # Nginx configuration.
507 | location /static {
508 | alias /opt/mailman/web/static;
509 | autoindex off;
510 | }
511 |
512 | location / {
513 | uwsgi_pass localhost:8080;
514 | include uwsgi_params;
515 | uwsgi_read_timeout 300;
516 | }
517 | ```
518 |
519 | Please make sure that you are using v0.1.1 or greater if you use this configuration.
520 |
521 |
522 | ### Serving static files
523 |
524 | UWSGI by default doesn't serve static files so, when running
525 | `mailman-web` using the provided `docker-compose.yaml` file, you won't see any
526 | CSS or JS files being served.
527 |
528 | To enable serving of static files using UWSGI, add the following environment
529 | variable to your `docker-compose.yaml` file under `mailman-web`:
530 |
531 | ```
532 | UWSGI_STATIC_MAP=/static=/opt/mailman-web-data/static
533 | ```
534 |
535 | It is recommended to use web-server to serve static files instead of UWSGI for
536 | better performance. You will have to add an alias rule in your web server to
537 | serve the static files. See [here][18] for instructions on how to configure your
538 | web server. The STATIC_ROOT for you would be `/opt/mailman/web/static`.
539 |
540 | ### SSL certificates
541 |
542 | SSL Certificates from Lets Encrypt need to be renewed every 90 days. You can
543 | setup a cron job to do the job. I have this small shell script (certbot-renew.sh)
544 | that you can put up in `/etc/cron.monthly` to get the job done.
545 |
546 | ```
547 | #! /bin/bash
548 |
549 | cd /opt/letsencrypt/
550 | ./certbot-auto --config /etc/letsencrypt/renewal/MY_DOMAIN_NAME.conf certonly
551 |
552 | if [ $? -ne 0 ]
553 | then
554 | ERRORLOG=`tail /var/log/letsencrypt/letsencrypt.log`
555 | echo -e "The Let's Encrypt cert has not been renewed! \n \n" \
556 | $ERRORLOG
557 | else
558 | nginx -s reload
559 | fi
560 |
561 | exit 0
562 | ```
563 |
564 | **Please do not forget to make the script executable (`chmod +x certbot-renew.sh`).**
565 |
566 | ## LICENSE
567 |
568 | This repository is licensed under the MIT License. Please see the LICENSE file for
569 | more details.
570 |
571 | [1]: http://list.org
572 | [2]: https://www.docker.com/
573 | [3]: https://docs.docker.com/compose/
574 | [4]: http://docs.mailman3.org/en/latest/
575 | [5]: https://docs.docker.com/engine/installation/
576 | [6]: https://docs.docker.com/compose/install/
577 | [7]: https://uwsgi-docs.readthedocs.io/en/latest/
578 | [8]: http://exim.org/
579 | [9]: https://letsencrypt.org/
580 | [10]: https://certbot.eff.org/
581 | [11]: https://mailman.readthedocs.io/en/latest/src/mailman/docs/database.html
582 | [12]: http://www.postfix.org/
583 | [13]: http://semver.org/
584 | [14]: https://docs.docker.com/engine/security/trust/content_trust/
585 | [15]: http://docs.mailman3.org/en/latest/config-web.html#setting-up-email
586 | [17]: https://docs.mailman3.org/en/latest/install/virtualenv.html#nginx-configuration
587 | [18]: http://docs.list.org/en/latest/pre-installation-guide.html#django-static-files
588 | [19]: https://github.com/maxking/docker-mailman/blob/master/docker-compose.yaml
589 |
--------------------------------------------------------------------------------