From c37c8938839a868f9b0f57d3827d3b1581f78dea Mon Sep 17 00:00:00 2001 From: debfranca Date: Tue, 14 Jan 2020 16:47:52 +0100 Subject: [PATCH] Added mailman folder --- library/roles/mailman/defaults/main.yml | 256 +++++++++++++++ .../mailman/files/distribute_maps.ansible.cfg | 8 + .../files/distribute_maps.playbook.yml | 21 ++ library/roles/mailman/handlers/main.yml | 18 ++ library/roles/mailman/meta/main.yml | 28 ++ library/roles/mailman/tasks/config.yml | 194 ++++++++++++ .../roles/mailman/tasks/distribute_maps.yml | 44 +++ .../roles/mailman/tasks/group_discovery.yml | 15 + .../roles/mailman/tasks/install_package.yml | 35 +++ library/roles/mailman/tasks/install_pip.yml | 98 ++++++ library/roles/mailman/tasks/main.yml | 37 +++ library/roles/mailman/tasks/user.yml | 20 ++ .../templates/hosts.distribute_maps.j2 | 12 + .../roles/mailman/templates/mailman.cfg.j2 | 11 + .../templates/mailman3-core.service.j2 | 17 + .../mailman/templates/mailman3-web.service.j2 | 22 ++ library/roles/mailman/templates/manage.py.j2 | 10 + .../roles/mailman/templates/settings.py.j2 | 294 ++++++++++++++++++ .../mailman/templates/settings_local.py.j2 | 206 ++++++++++++ library/roles/mailman/templates/urls.py.j2 | 37 +++ library/roles/mailman/templates/uwsgi.ini.j2 | 78 +++++ library/roles/mailman/templates/wsgi.py.j2 | 40 +++ 22 files changed, 1501 insertions(+) create mode 100644 library/roles/mailman/defaults/main.yml create mode 100644 library/roles/mailman/files/distribute_maps.ansible.cfg create mode 100644 library/roles/mailman/files/distribute_maps.playbook.yml create mode 100644 library/roles/mailman/handlers/main.yml create mode 100644 library/roles/mailman/meta/main.yml create mode 100644 library/roles/mailman/tasks/config.yml create mode 100644 library/roles/mailman/tasks/distribute_maps.yml create mode 100644 library/roles/mailman/tasks/group_discovery.yml create mode 100644 library/roles/mailman/tasks/install_package.yml create mode 100644 library/roles/mailman/tasks/install_pip.yml create mode 100644 library/roles/mailman/tasks/main.yml create mode 100644 library/roles/mailman/tasks/user.yml create mode 100644 library/roles/mailman/templates/hosts.distribute_maps.j2 create mode 100644 library/roles/mailman/templates/mailman.cfg.j2 create mode 100644 library/roles/mailman/templates/mailman3-core.service.j2 create mode 100644 library/roles/mailman/templates/mailman3-web.service.j2 create mode 100644 library/roles/mailman/templates/manage.py.j2 create mode 100644 library/roles/mailman/templates/settings.py.j2 create mode 100644 library/roles/mailman/templates/settings_local.py.j2 create mode 100644 library/roles/mailman/templates/urls.py.j2 create mode 100644 library/roles/mailman/templates/uwsgi.ini.j2 create mode 100644 library/roles/mailman/templates/wsgi.py.j2 diff --git a/library/roles/mailman/defaults/main.yml b/library/roles/mailman/defaults/main.yml new file mode 100644 index 00000000..7c07f44c --- /dev/null +++ b/library/roles/mailman/defaults/main.yml @@ -0,0 +1,256 @@ +--- + +# TODO: make sure you point out to set django secret_key, django admins, django archive key? + +mailman3_install_method: pip +mailman3_language: en +mailman3_language_code: en-us +mailman3_install_system_dependencies: "{{ __mailman3_debian or __mailman3_redhat }}" +mailman3_python_uwsgi_package: pyuwsgi +mailman3_backup_configs: false +# list of dicts with keys `name`, `email`, `pass` +mailman3_django_superusers: [] +# list of hosted domains +#mailman3_domains: [] +# If mailman3_domains is set, mailman3_config.default_from_email is ignored since it's assumed you want per-domain +# addresses. In this case, set the username portion of the email (the domain will be added automatically) +mailman3_default_from_user: postorius + +# Distribute Postfix maps to MXs for use with relay_recipient_maps, so that MXs can reject mail to nonexistent +# addresses. Installs Ansible and a playbook in a virtualenv. User/auth setup is up to you. +# +# list of dicts, required keys: +# host: inventory_hostname of mx (or "all") for vars to apply to all hosts +# mailman3_distribute_maps_dir: remote directory on mx to distribute maps to +# all keys other than "host" are set as either host vars or in [all:vars] if host = "all" +#mailman3_distribute_maps: [] +mailman3_distribute_maps_dir: "{{ mailman3_var_dir }}/distribute_maps" + +# For pip installs, the role creates a venv at this path +mailman3_install_dir: /opt/mailman3 + +# uWSGI/proxy communication socket (value only used for pip installs, Debian uses a hardcoded default) +mailman3_uwsgi_socket: "{{ mailman3_django_var_dir }}/run/uwsgi.sock" + +# Optionally serve directly with uWSGI +#mailman3_http_socket: +mailman3_uwsgi_static: no + +# You should rarely need to set these +#mailman3_virtualenv_python: python3 +#mailman3_virtualenv_command: python3 -m venv # https://github.com/ansible/ansible/issues/52275 +mailman3_virtualenv_command: pyvenv + +__mailman3_debian: "{{ ansible_os_family == 'Debian' }}" +__mailman3_redhat: "{{ ansible_os_family == 'RedHat' }}" +__mailman3_pip: "{{ mailman3_install_method == 'pip' }}" + +# pip needed packages if using the pip install method, system packages if using the package method +__mailman3_pip_packages: + - whoosh + - django>=1.11 + - mailman + - postorius + - hyperkitty + - mailman-hyperkitty + - "{{ mailman3_python_uwsgi_package }}" +__mailman3_debian_packages: + - mailman3-full +__mailman3_redhat_packages: null # currently nonexistent +mailman3_packages: >- + {{ + __mailman3_pip_packages if __mailman3_pip else ( + __mailman3_debian_packages if __mailman3_debian else + __mailman3_redhat_packages) + }} +# for e.g. psycopg2 +mailman3_extra_packages: [] + +# Dependant system packages needed if using the pip install method +__mailman3_debian_system_dependency_packages: + - python3 # requires Ubuntu >= 16.04, Debian >= stretch (for 3.5) + - python3-setuptools # Ansible pip module needs this despite having venv; UPDATE: no it doesn't if it can find python3; UPDATE2: well now it does again, wtf + - python3-venv + - sassc + - uwsgi + - uwsgi-plugin-python3 +__mailman3_redhat_system_dependency_packages: + # all require EPEL + - python36 # requires EL7+ + - sassc + - uwsgi + - uwsgi-plugin-python36 +# TODO: as of `date`, compilers + python headers are required for these packages that don't have published cext wheels: +# - rcssmin +# - rjsmin +# But this role will not install compilers in case wheels become available at a later date +mailman3_system_dependency_packages: >- + {{ + __mailman3_debian_system_dependency_packages if __mailman3_debian else + __mailman3_redhat_system_dependency_packages + }} + +# TODO: supervisor +mailman3_process_manager: >- + {{ + 'systemd' if ansible_virtualization_type != 'docker' else None + }} + +mailman3_core_service_name: >- + {{ + 'mailman3-core' if __mailman3_pip else ( + 'mailman3' if __mailman3_debian else + None) + }} + +mailman3_web_service_name: >- + {{ + 'mailman3-web' if __mailman3_pip else ( + 'mailman3-web' if __mailman3_debian else + None) + }} + +mailman3_etc_dir: >- + {{ + '/etc/opt/mailman3' if __mailman3_pip else ( + '/etc/mailman3' if __mailman3_debian else + None) + }} + +mailman3_var_dir: >- + {{ + '/var/opt/mailman3/core' if __mailman3_pip else ( + '/var/lib/mailman3' if __mailman3_debian else + None) + }} + +mailman3_log_dir: >- + {{ + '/var/opt/mailman3/core/log' if __mailman3_pip else ( + '/var/log/mailman3' if __mailman3_debian else + None) + }} + +mailman3_django_var_dir: >- + {{ + '/var/opt/mailman3/web' if __mailman3_pip else ( + '/var/lib/mailman3' if __mailman3_debian else + None) + }} + +mailman3_django_project_dir: >- + {{ + '/var/opt/mailman3/web/project' if __mailman3_pip else ( + '/usr/share/mailman3-web' if __mailman3_debian else + None) + }} + +mailman3_django_static_dir: >- + {{ + '/var/opt/mailman3/web/static' if __mailman3_pip else ( + '/var/lib/mailman3/web/static' if __mailman3_debian else + None) + }} + +mailman3_django_log_dir: >- + {{ + '/var/opt/mailman3/web/log' if __mailman3_pip else ( + '/var/log/mailman3/web' if __mailman3_debian else + None) + }} + +mailman3_django_settings_file: >- + {{ + '/etc/opt/mailman3/django-settings.py' if __mailman3_pip else ( + '/etc/mailman3/mailman-web.py' if __mailman3_debian else + None) + }} + +mailman3_web_user: >- + {{ + 'www-data' if __mailman3_debian else ( + None) + }} +# TODO: +#'httpd' if __mailman3_redhat and apache +#'nginx' if __mailman3_redhat and nginx + +mailman3_web_group: >- + {{ + 'www-data' if __mailman3_debian else ( + None) + }} + +mailman3_core_api_hostname: localhost +mailman3_core_api_port: 8001 +mailman3_core_api_admin_user: restadmin +mailman3_core_api_admin_pass: restpass +mailman3_archiver_key: SecretArchiverAPIKey + +__mailman3_config_default: + mailman: + layout: custom + paths.custom: + var_dir: "{{ mailman3_var_dir }}" + bin_dir: "$argv" + log_dir: "{{ mailman3_log_dir }}" + lock_dir: "{{ mailman3_var_dir }}/locks" + data_dir: "{{ mailman3_var_dir }}/data" + cache_dir: "{{ mailman3_var_dir }}/cache" + etc_dir: "{{ mailman3_etc_dir }}" + messages_dir: "{{ mailman3_var_dir }}/messages" + archives_dir: "{{ mailman3_var_dir }}/archives" + template_dir: "{{ mailman3_var_dir }}/templates" + pid_file: "{{ mailman3_var_dir }}/master.pid" + lock_file: "{{ mailman3_var_dir }}/master.lck" + webservice: + hostname: "{{ mailman3_core_api_hostname }}" + port: "{{ mailman3_core_api_port }}" + use_https: "no" + admin_user: "{{ mailman3_core_api_admin_user }}" + admin_pass: "{{ mailman3_core_api_admin_pass }}" + api_version: "3.1" + archiver.hyperkitty: + class: mailman_hyperkitty.Archiver + enable: "yes" + configuration: "{{ mailman3_etc_dir }}/hyperkitty.cfg" +__mailman3_config_merged: "{{ __mailman3_config_default | combine(mailman3_config | default({}), recursive=True) }}" + +__mailman3_django_config_default: + admins: "{{ mailman3_django_superusers }}" + allowed_hosts: "{{ mailman3_domains | default([inventory_hostname]) }}" + rest_api_url: "http://{{ mailman3_core_api_hostname }}:{{ mailman3_core_api_port }}" + rest_api_user: "{{ mailman3_core_api_admin_user }}" + rest_api_pass: "{{ mailman3_core_api_admin_pass }}" + archiver_key: "{{ mailman3_archiver_key }}" + databases: + default: + ENGINE: django.db.backends.sqlite3 + NAME: "{{ mailman3_django_var_dir }}/db/mailmansuite.db" + USER: '' + PASSWORD: '' + HOST: '' + PORT: '' + # Disable by default, recommended Django setup for nginx passes Host, not X-Forwarded-Host + #use_x_forwarded_host: true + secure_proxy_ssl_header: HTTP_X_FORWARDED_PROTO + default_http_protocol: https + default_from_email: postorius@{{ inventory_hostname }} + server_email: root@{{ inventory_hostname }} + compress_offline: true + socialaccount_providers: {} +__mailman3_django_config_merged: "{{ __mailman3_django_config_default | combine(mailman3_django_config | default({}), recursive=True) }}" + +mailman3_postorius_root: 'postorius/' +mailman3_hyperkitty_root: 'hyperkitty/' + +#mailman3_user: mailman +mailman3_create_user: "{{ __mailman3_pip and not __mailman3_debian }}" + +__mailman3_debian_user_name: list +__mailman3_user_name: >- + {{ + (mailman3_user | default({})).name | default( + __mailman3_debian_user_name if __mailman3_debian else + 'mailman') + }} diff --git a/library/roles/mailman/files/distribute_maps.ansible.cfg b/library/roles/mailman/files/distribute_maps.ansible.cfg new file mode 100644 index 00000000..68a71145 --- /dev/null +++ b/library/roles/mailman/files/distribute_maps.ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +inventory = hosts +retry_files_enabled = False +transport = ssh + +[ssh_connection] +# Automatically accept host keys +ssh_args = -o StrictHostKeyChecking=no diff --git a/library/roles/mailman/files/distribute_maps.playbook.yml b/library/roles/mailman/files/distribute_maps.playbook.yml new file mode 100644 index 00000000..d70bf5b2 --- /dev/null +++ b/library/roles/mailman/files/distribute_maps.playbook.yml @@ -0,0 +1,21 @@ +--- + +- name: Distribute Mailman transport maps to backup MX servers + hosts: all + tasks: + - name: Check postfix_lmtp + stat: + path: "{{ mailman3_var_dir }}/data/postfix_lmtp" + register: result + delegate_to: localhost + run_once: true + - name: Copy postfix_lmtp + copy: + src: "{{ mailman3_var_dir }}/data/postfix_lmtp" + dest: "{{ mailman3_distribute_maps_dir }}/postfix_lmtp" + when: result.stat.exists + notify: + - postmap + handlers: + - name: postmap + command: "{{ mailman3_postmap_command | default('postmap') }} {{ mailman3_distribute_maps_dir | quote }}/postfix_lmtp" diff --git a/library/roles/mailman/handlers/main.yml b/library/roles/mailman/handlers/main.yml new file mode 100644 index 00000000..ffeca058 --- /dev/null +++ b/library/roles/mailman/handlers/main.yml @@ -0,0 +1,18 @@ +--- + +- name: reload systemd manager configuration + systemd: + daemon_reload: yes + +- name: restart mailman3-core service + service: + name: "{{ mailman3_core_service_name }}" + state: restarted + when: ansible_virtualization_type != "docker" + +- name: restart mailman3-web service + service: + name: "{{ mailman3_web_service_name }}{{ '@' if mailman3_domains is defined else '' }}{{ item }}" + state: restarted + loop: "{{ mailman3_domains | default(['']) }}" + when: ansible_virtualization_type != "docker" diff --git a/library/roles/mailman/meta/main.yml b/library/roles/mailman/meta/main.yml new file mode 100644 index 00000000..a7840e31 --- /dev/null +++ b/library/roles/mailman/meta/main.yml @@ -0,0 +1,28 @@ +galaxy_info: + author: natefoo + description: Mailman 3 installation, configuration, and management for Linux + company: The Galaxy Project + license: license (MIT) + min_ansible_version: 2.7 + platforms: + - name: EL + versions: + - 7 + - name: Debian + versions: + - stretch + - buster + - sid + - name: Ubuntu + versions: + - xenial + - bionic + galaxy_tags: + - mail + - mailing + - list + - lists + - mailman + - mailman3 + +dependencies: [] diff --git a/library/roles/mailman/tasks/config.yml b/library/roles/mailman/tasks/config.yml new file mode 100644 index 00000000..4a22875f --- /dev/null +++ b/library/roles/mailman/tasks/config.yml @@ -0,0 +1,194 @@ +--- + +- name: Create/update Mailman Core configuration + template: + src: mailman.cfg.j2 + dest: "{{ mailman3_etc_dir }}/mailman.cfg" + group: "{{ __mailman3_group_name }}" + mode: "0640" + backup: "{{ mailman3_backup_configs }}" + notify: + - restart mailman3-core service + +- name: Create HyperKitty configuration file + copy: + content: | + [general] + base_url: http://localhost/{{ mailman3_hyperkitty_root }} + api_key: {{ mailman3_archiver_key }} + dest: "{{ mailman3_etc_dir }}/hyperkitty.cfg" + group: "{{ __mailman3_group_name }}" + notify: + - restart mailman3-core service + +- name: Create/update Django project + template: + src: "{{ item.name }}.j2" + mode: "{{ item.mode }}" + dest: "{{ mailman3_django_project_dir }}/{{ item.name }}" + backup: "{{ mailman3_backup_configs }}" + with_items: + - name: manage.py + mode: "0755" + - name: settings.py + mode: "0644" + - name: urls.py + mode: "0644" + - name: wsgi.py + mode: "0644" + when: __mailman3_pip + notify: + - restart mailman3-web service + +- name: Create/update Django local settings + template: + src: "settings_local.py.j2" + dest: "{{ mailman3_django_settings_file }}" + group: "{{ __mailman3_group_name }}" + mode: "0640" + backup: "{{ mailman3_backup_configs }}" + notify: + - restart mailman3-web service + +- name: Create Django local settings symlink + file: + src: "{{ mailman3_django_settings_file }}" + dest: "{{ mailman3_django_project_dir }}/settings_local.py" + state: link + when: __mailman3_pip + notify: + - restart mailman3-web service + +- name: Create Django site configs + copy: + content: | + # import project settings first + from settings import * + # override any configured SITE_ID + SITE_ID = {{ site_id + 1 }} + FILTER_VHOST = True + DEFAULT_FROM_EMAIL = '{{ mailman3_default_from_user }}@{{ domain }}' + dest: "{{ mailman3_django_project_dir }}/settings_{{ domain | replace('.', '_') | replace('-', '_') }}.py" + loop: "{{ mailman3_domains | default([]) }}" + loop_control: + index_var: site_id + loop_var: domain + when: mailman3_domains is defined + notify: + - restart mailman3-web service + +# This runs before collectstatic because it creates the log file, which must be created as the web user +- name: Create/update Django DB schema + django_manage: + command: migrate + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + # FIXME: + become: yes + become_user: "{{ mailman3_web_user }}" + #become_method: su + #become_flags: '-s /bin/sh' + notify: + - restart mailman3-web service + +- name: Collect Django static files + django_manage: + command: collectstatic + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + when: __mailman3_pip + +- name: Check Django superusers + django_manage: + command: >- + shell -c 'import sys; + from django.contrib.auth.models import User; + sys.stdout.write("exists") if User.objects.filter(username="{{ item.name }}").count() > 0 else sys.stdout.write("missing")' + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + with_items: "{{ mailman3_django_superusers }}" + register: __mailman3_checksuperuser_result + changed_when: __mailman3_checksuperuser_result.out == "missing" + loop_control: + label: "{{ item.name }}" + # FIXME: + become: yes + become_user: "{{ mailman3_web_user }}" + +- name: Create Django superusers + django_manage: + command: >- + shell -c 'import sys; + from django.contrib.auth.models import User; + User.objects.create_superuser("{{ item.item.name }}", "{{ item.item.email }}", "{{ item.item.pass }}")' + #command: "createsuperuser --noinput --username={{ item.item.name }} --email={{ item.item.email }}" + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + when: item is changed + with_items: "{{ __mailman3_checksuperuser_result.results }}" + register: __mailman3_createsuperuser_result + changed_when: true + loop_control: + label: "{{ item.item.name }}" + # FIXME: + become: yes + become_user: "{{ mailman3_web_user }}" + +- name: Check Django sites + django_manage: + command: >- + shell -c 'import sys; + from django.contrib.sites.models import Site; + sys.stdout.write("exists") if Site.objects.filter(domain="example.com").count() > 0 else sys.stdout.write("missing")' + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + register: __mailman3_checkexamplesite_result + changed_when: __mailman3_checkexamplesite_result.out == "exists" + # FIXME: + become: yes + become_user: "{{ mailman3_web_user }}" + +- name: Correct default Django site + django_manage: + command: >- + shell -c 'from django.contrib.sites.models import Site; + Site.objects.filter(domain="example.com").update( + domain="{{ (mailman3_domains | default([])).0 | default(inventory_hostname) }}", + name="{{ (mailman3_domains | default([])).0 | default(inventory_hostname) }}" + )' + app_path: "{{ mailman3_django_project_dir }}" + virtualenv: "{{ mailman3_install_dir }}" + when: __mailman3_checkexamplesite_result is changed + changed_when: true + # FIXME: + become: yes + become_user: "{{ mailman3_web_user }}" + +# TODO: create additional domains (for right now the admin can do this in the UI) + +- name: Create/update uWSGI configuration file + template: + src: uwsgi.ini.j2 + dest: "{{ mailman3_etc_dir }}/uwsgi.ini" + when: __mailman3_pip and mailman3_domains is not defined + notify: + - restart mailman3-web service + +- name: Create/update uWSGI domain configuration files + template: + src: uwsgi.ini.j2 + dest: "{{ mailman3_etc_dir }}/uwsgi_{{ domain }}.ini" + loop: "{{ mailman3_domains | default([]) }}" + loop_control: + index_var: site_id + loop_var: domain + when: mailman3_domains is defined + notify: + - restart mailman3-web service + +# This is idempotent so it's safe to do as an always-run task +- name: Compress CSS + django_manage: + app_path: "{{ mailman3_django_project_dir }}" + command: compress + virtualenv: "{{ mailman3_install_dir }}" diff --git a/library/roles/mailman/tasks/distribute_maps.yml b/library/roles/mailman/tasks/distribute_maps.yml new file mode 100644 index 00000000..aab4cb76 --- /dev/null +++ b/library/roles/mailman/tasks/distribute_maps.yml @@ -0,0 +1,44 @@ +--- + +- name: Install Ansible + pip: + name: ansible + virtualenv: "{{ mailman3_distribute_maps_dir }}" + virtualenv_command: "{{ mailman3_virtualenv_command | default(omit) }}" + virtualenv_python: "{{ mailman3_virtualenv_python | default(omit) }}" + +- name: Create playbook directory + file: + path: "{{ mailman3_distribute_maps_dir }}/etc" + state: directory + group: "{{ __mailman3_group_name }}" + mode: "0750" + +- name: Create playbook files + copy: + src: "distribute_maps.{{ item }}" + dest: "{{ mailman3_distribute_maps_dir }}/etc/{{ item }}" + group: "{{ __mailman3_group_name }}" + mode: "0640" + loop: + - playbook.yml + - ansible.cfg + +# TODO: dig lookup plugin based inventory plugin +- name: Create hosts file + template: + src: hosts.distribute_maps.j2 + dest: "{{ mailman3_distribute_maps_dir }}/etc/hosts" + group: "{{ __mailman3_group_name }}" + mode: "0640" + +- name: Create cron job + cron: + name: Distribute Mailman 3 Postfix Maps + cron_file: ansible_mailman3_distribute_maps + user: "{{ __mailman3_user_name }}" + minute: "*/{{ mailman3_distribute_map_frequency | default(5) }}" + job: >- + cd {{ mailman3_distribute_maps_dir | quote }}/etc && + {{ mailman3_distribute_maps_dir | quote }}/bin/ansible-playbook playbook.yml + >>{{ mailman3_log_dir }}/distribute_maps.log 2>&1 diff --git a/library/roles/mailman/tasks/group_discovery.yml b/library/roles/mailman/tasks/group_discovery.yml new file mode 100644 index 00000000..705f25f5 --- /dev/null +++ b/library/roles/mailman/tasks/group_discovery.yml @@ -0,0 +1,15 @@ +--- + +- name: Get Mailman user group ID + getent: + database: passwd + key: "{{ __mailman3_user_name }}" + +- name: Get Mailman user group name + getent: + database: group + key: "{{ getent_passwd[__mailman3_user_name][2] }}" + +- name: Set Mailman user group fact + set_fact: + __mailman3_group_name: "{{ getent_group | first }}" diff --git a/library/roles/mailman/tasks/install_package.yml b/library/roles/mailman/tasks/install_package.yml new file mode 100644 index 00000000..efc33cf6 --- /dev/null +++ b/library/roles/mailman/tasks/install_package.yml @@ -0,0 +1,35 @@ +--- + +- name: Ensure supported OS for package installs + assert: + that: + - "ansible_os_family == 'Debian'" + success_msg: "OS is supported for Mailman 3 installation by package" + fail_msg: "OS is not supported for Mailman 3 installation by package, set `mailman3_install_method` to `pip`" + +# TODO: everything below untested with Mailman 3 +- name: Install debconf packages + apt: + name: + - debconf + - debconf-utils + when: ansible_os_family == "Debian" + +- name: Set client options in debconf + debconf: + name: mailman3 + question: "{{ item.question }}" + value: "{{ item.value }}" + vtype: "{{ item.vtype }}" + when: ansible_os_family == "Debian" + with_items: + - question: "mailman/site_languages" + value: "{{ mailman3_language }}" + type: "multiselect" + - question: "mailman/default_server_language" + value: "{{ mailman3_language }}" + vtype: "multiselect" + +- name: Install Mailman and dependency packages + package: + name: "{{ mailman3_packages }}" diff --git a/library/roles/mailman/tasks/install_pip.yml b/library/roles/mailman/tasks/install_pip.yml new file mode 100644 index 00000000..845747cd --- /dev/null +++ b/library/roles/mailman/tasks/install_pip.yml @@ -0,0 +1,98 @@ +--- + +- name: Install system dependencies + package: + name: "{{ mailman3_system_dependency_packages }}" + when: mailman3_install_system_dependencies + +# This is a separate task from the next one because `python3 -m venv` doesn't install wheel and pip needs it to build +# wheels (not strictly required, but preferred) and won't load it mid-invocation if installed during the next task +- name: Create Mailman venv + pip: + name: wheel + virtualenv: "{{ mailman3_install_dir }}" + virtualenv_python: "{{ mailman3_virtualenv_python | default(omit) }}" + virtualenv_command: "{{ mailman3_virtualenv_command | default(omit) }}" + +- name: Install Mailman and Python dependencies + pip: + name: "{{ mailman3_packages + mailman3_extra_packages }}" + virtualenv: "{{ mailman3_install_dir }}" + +- name: Create configuration, data, and state directories + file: + path: "{{ item.path }}" + owner: "{{ item.owner | default(omit) }}" + group: "{{ item.group | default(omit) }}" + mode: "{{ item.mode | default(omit) }}" + state: directory + with_items: + - path: "{{ mailman3_etc_dir }}" + group: "{{ __mailman3_group_name }}" + mode: "0750" + # Creates the parent /var/opt/mailman3 with default permissions + - path: "{{ mailman3_var_dir | dirname }}" + - path: "{{ mailman3_var_dir }}" + owner: "{{ __mailman3_user_name }}" + group: "{{ __mailman3_group_name }}" + mode: "0750" + - path: "{{ mailman3_log_dir }}" + owner: "{{ __mailman3_user_name }}" + group: "{{ __mailman3_group_name }}" + mode: "0750" + - path: "{{ mailman3_django_var_dir }}" + - path: "{{ mailman3_django_var_dir }}/run" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + - path: "{{ mailman3_django_var_dir }}/db" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + - path: "{{ mailman3_django_var_dir }}/fulltext_index" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + - path: "{{ mailman3_django_var_dir }}/emails" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + - path: "{{ mailman3_django_project_dir }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + - path: "{{ mailman3_django_log_dir }}" + mode: "0750" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + +- name: Create HyperKitty attachment directory + file: + path: "{{ __mailman3_django_config_merged.hyperkitty_attachment_folder }}" + owner: "{{ mailman3_web_user }}" + group: "{{ mailman3_web_group }}" + mode: "0750" + state: directory + when: __mailman3_django_config_merged.hyperkitty_attachment_folder is defined + +# TODO: This is needed to read settings_local.py from /etc/opt/mailman3, but will it be needed for anything else? +# TODO: config option to control this? +- name: Add web user to Mailman user group + user: + name: "{{ mailman3_web_user }}" + groups: "{{ __mailman3_group_name }}" + +- name: Install systemd service unit files + template: + src: "{{ item.src | default(item) }}.j2" + dest: /etc/systemd/system/{{ item.dest | default(item) }} + loop: + - mailman3-core.service + - src: mailman3-web.service + dest: mailman3-web{{ '@' if mailman3_domains is defined else '' }}.service + loop_control: + label: "{{ item.dest | default(item) }}" + when: mailman3_process_manager == 'systemd' + notify: + - reload systemd manager configuration + +# TODO: remove the non-instance service unit file if mailman3_domains is defined, remove the instance service unit file diff --git a/library/roles/mailman/tasks/main.yml b/library/roles/mailman/tasks/main.yml new file mode 100644 index 00000000..93d0a341 --- /dev/null +++ b/library/roles/mailman/tasks/main.yml @@ -0,0 +1,37 @@ +--- + +- name: Include user creation tasks + include_tasks: user.yml + when: mailman3_create_user + +- name: Include group discovery tasks + import_tasks: group_discovery.yml + +- name: Include installation tasks + import_tasks: "install_{{ mailman3_install_method }}.yml" + +- name: Include configuration tasks + import_tasks: config.yml + +- name: Include Postfix map distribution tasks + include_tasks: distribute_maps.yml + when: mailman3_distribute_maps is defined + +# Perform whatever restarts are needed now, prevents double restart on first run +- name: Flush handlers + meta: flush_handlers + +- name: Ensure Mailman Core is enabled and running + service: + name: "{{ mailman3_core_service_name }}" + enabled: yes + state: started + when: mailman3_process_manager != "supervisor" + +- name: Ensure Mailman Web is enabled and running + service: + name: "{{ mailman3_web_service_name }}{{ '@' if mailman3_domains is defined else '' }}{{ item }}" + enabled: yes + state: started + loop: "{{ mailman3_domains | default(['']) }}" + when: mailman3_process_manager != "supervisor" diff --git a/library/roles/mailman/tasks/user.yml b/library/roles/mailman/tasks/user.yml new file mode 100644 index 00000000..1e00b681 --- /dev/null +++ b/library/roles/mailman/tasks/user.yml @@ -0,0 +1,20 @@ +--- + +- name: Create mailman3 group + group: + name: "{{ (mailman3_user | default({})).group }}" + gid: "{{ (mailman3_user | default({})).gid | default(omit) }}" + system: "{{ (mailman3_user | default({})).system | default('yes') }}" + when: (mailman3_user | default({})).group is defined + +- name: Create mailman3 user + user: + name: "{{ __mailman_user_name }}" + comment: "{{ (mailman3_user | default({})).comment | default(omit) }}" + uid: "{{ (mailman3_user | default({})).uid | default(omit) }}" + group: "{{ (mailman3_user | default({})).group | default(omit) }}" + groups: "{{ (mailman3_user | default({})).groups | default(omit) }}" + home: "{{ (mailman3_user | default({})).home | default(mailman3_var_dir) }}" + create_home: "{{ (mailman3_user | default({})).create_home | default('no') }}" + shell: "{{ (mailman3_user | default({})).shell | default(omit) }}" + system: "{{ (mailman3_user | default({})).system | default('yes') }}" diff --git a/library/roles/mailman/templates/hosts.distribute_maps.j2 b/library/roles/mailman/templates/hosts.distribute_maps.j2 new file mode 100644 index 00000000..040e1dd0 --- /dev/null +++ b/library/roles/mailman/templates/hosts.distribute_maps.j2 @@ -0,0 +1,12 @@ +#jinja2: trim_blocks: False +{% for mx in mailman3_distribute_maps %}{% if mx.host != 'all' -%} +{{ mx.host }}{% for opt in mx | sort %}{% if opt != 'host' %} {{ opt }}={{ mx[opt] }}{% endif %}{% endfor %} +{% endif %}{% endfor %} +[all:vars] +mailman3_var_dir = {{ mailman3_var_dir }} +{% set all_vars = mailman3_distribute_maps | selectattr("host", "eq", "all") | first -%} +{% if all_vars is defined -%} +{% for opt in all_vars | sort %}{% if opt != 'host' -%} +{{ opt }} = {{ all_vars[opt] }} +{% endif %}{% endfor -%} +{% endif -%} diff --git a/library/roles/mailman/templates/mailman.cfg.j2 b/library/roles/mailman/templates/mailman.cfg.j2 new file mode 100644 index 00000000..371832be --- /dev/null +++ b/library/roles/mailman/templates/mailman.cfg.j2 @@ -0,0 +1,11 @@ +;; +;; +;; + +{% for section in __mailman3_config_merged | sort %} +[{{ section }}] +{% for key in __mailman3_config_merged[section] | sort %} +{{ key }}: {{ __mailman3_config_merged[section][key] }} +{% endfor %} + +{% endfor %} diff --git a/library/roles/mailman/templates/mailman3-core.service.j2 b/library/roles/mailman/templates/mailman3-core.service.j2 new file mode 100644 index 00000000..4fa62322 --- /dev/null +++ b/library/roles/mailman/templates/mailman3-core.service.j2 @@ -0,0 +1,17 @@ +[Unit] +Description=Mailman 3 Core service +After=network.target +Documentation=https://mailman.readthedocs.io/ +ConditionPathExists={{ mailman3_etc_dir }}/mailman.cfg + +[Service] +ExecStart={{ mailman3_install_dir }}/bin/mailman -C {{ mailman3_etc_dir }}/mailman.cfg start +ExecReload={{ mailman3_install_dir }}/bin/mailman -C {{ mailman3_etc_dir }}/mailman.cfg restart +ExecStop={{ mailman3_install_dir }}/bin/mailman -C {{ mailman3_etc_dir }}/mailman.cfg stop +Type=forking +PIDFile={{ __mailman3_config_merged['paths.' ~ __mailman3_config_merged.mailman.layout].pid_file | default(mailman3_var_dir ~ '/master.pid') }} +SyslogIdentifier=mailman3 +User={{ __mailman3_user_name }} +Group={{ __mailman3_group_name }} + +[Install] diff --git a/library/roles/mailman/templates/mailman3-web.service.j2 b/library/roles/mailman/templates/mailman3-web.service.j2 new file mode 100644 index 00000000..8e4e18ce --- /dev/null +++ b/library/roles/mailman/templates/mailman3-web.service.j2 @@ -0,0 +1,22 @@ +[Unit] +Description=Mailman 3 Django/uWSGI {% if mailman3_domains is defined %}(domain %i) {% endif %}service +After=network.target +Documentation=https://mailman.readthedocs.io/ +ConditionPathExists={{ mailman3_etc_dir }}/uwsgi{% if mailman3_domains is defined %}_%i{% endif %}.ini + +[Service] +ExecStart={{ mailman3_install_dir }}/bin/{{ mailman3_python_uwsgi_package }} --ini {{ mailman3_etc_dir }}/uwsgi{% if mailman3_domains is defined %}_%i{% endif %}.ini +{# https://github.com/unbit/uwsgi/issues/1980 #} +{% if mailman3_python_uwsgi_package == 'pyuwsgi' %} +Environment=DJANGO_SETTINGS_MODULE=settings_%i +{% endif %} +Restart=on-failure +KillSignal=SIGQUIT +Type=notify +StandardError=syslog +NotifyAccess=all +User=root +Group=root + +[Install] +WantedBy=multi-user.target diff --git a/library/roles/mailman/templates/manage.py.j2 b/library/roles/mailman/templates/manage.py.j2 new file mode 100644 index 00000000..97ad7416 --- /dev/null +++ b/library/roles/mailman/templates/manage.py.j2 @@ -0,0 +1,10 @@ +#!{{ mailman3_install_dir }}/bin/python3 +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/library/roles/mailman/templates/settings.py.j2 b/library/roles/mailman/templates/settings.py.j2 new file mode 100644 index 00000000..2a45471e --- /dev/null +++ b/library/roles/mailman/templates/settings.py.j2 @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2016 by the Free Software Foundation, Inc. +# +# This file is part of Mailman Suite. +# +# Mailman Suite is free sofware: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Mailman Suite is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +# You should have received a copy of the GNU General Public License along +# with Mailman Suite. If not, see . +""" +Django Settings for Mailman Suite (hyperkitty + postorius) + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = {{ 'True' if (__mailman3_django_config_merged.debug | default(false) | bool) else 'False' }} + +# Application definition + +MIDDLEWARE = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django_mailman3.middleware.TimezoneMiddleware', + 'postorius.middleware.PostoriusMiddleware', +) + +ROOT_URLCONF = 'urls' + + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.template.context_processors.csrf', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django_mailman3.context_processors.common', + 'hyperkitty.context_processors.common', + 'postorius.context_processors.postorius', + ], + }, + }, +] + +WSGI_APPLICATION = 'wsgi.application' + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': +'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': +'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': +'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': +'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/var/www/example.com/static/" +STATIC_ROOT = '{{ mailman3_django_static_dir }}' + +# URL prefix for static files. +# Example: "http://example.com/static/", "http://static.example.com/" +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + # BASE_DIR + '/static/', +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', + 'compressor.finders.CompressorFinder', +) + +# Django 1.6+ defaults to a JSON serializer, but it won't work with +# django-openid, see +# https://bugs.launchpad.net/django-openid-auth/+bug/1252826 +SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' + + +LOGIN_URL = 'account_login' +LOGIN_REDIRECT_URL = 'list_index' +LOGOUT_URL = 'account_logout' + + +# Compatibility with Bootstrap 3 +from django.contrib.messages import constants as messages # flake8: noqa +MESSAGE_TAGS = { + messages.ERROR: 'danger' +} + + +# +# Social auth +# +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', +) + +# Django Allauth +ACCOUNT_AUTHENTICATION_METHOD = "username_email" +ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_UNIQUE_EMAIL = True + +# +# django-compressor +# https://pypi.python.org/pypi/django_compressor +# +COMPRESS_ENABLED = True +COMPRESS_PRECOMPILERS = ( + ('text/less', 'lessc {infile} {outfile}'), + ('text/x-scss', 'sassc -t compressed {infile} {outfile}'), + ('text/x-sass', 'sassc -t compressed {infile} {outfile}'), +) + +# Needed for debug mode +# INTERNAL_IPS = ('127.0.0.1',) + + +# +# Full-text search engine +# +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': '{{ mailman3_django_haystack_engine | default("haystack.backends.whoosh_backend.WhooshEngine") }}', + 'PATH': os.path.join("{{ mailman3_django_var_dir }}", "fulltext_index"), + # You can also use the Xapian engine, it's faster and more accurate, + # but requires another library. + # http://django-haystack.readthedocs.io/en/v2.4.1/installing_search_engines.html#xapian + # Example configuration for Xapian: + #'ENGINE': 'xapian_backend.XapianEngine' + }, +} + + +# +# Asynchronous tasks +# +Q_CLUSTER = { + 'timeout': 300, + 'save_limit': 100, + 'orm': 'default', +} + + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'file':{ + 'level': 'INFO', + #'class': 'logging.handlers.RotatingFileHandler', + 'class': 'logging.handlers.WatchedFileHandler', + 'filename': os.path.join('{{ mailman3_django_log_dir }}', 'mailmansuite.log'), + 'formatter': 'verbose', + }, + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + }, + }, + 'loggers': { + 'django.request': { + #'handlers': ['mail_admins', 'file'], + 'handlers': ['file'], + 'level': 'ERROR', + 'propagate': True, + }, + 'django': { + 'handlers': ['file'], + 'level': 'ERROR', + 'propagate': True, + }, + 'hyperkitty': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + 'postorius': { + 'handlers': ['console', 'file'], + 'level': 'INFO', + }, + }, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s' + }, + 'simple': { + 'format': '%(levelname)s %(message)s' + }, + }, + #'root': { + # 'handlers': ['file'], + # 'level': 'INFO', + #}, +} + + +# Using the cache infrastructure can significantly improve performance on a +# production setup. This is an example with a local Memcached server. +#CACHES = { +# 'default': { +# 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', +# 'LOCATION': '127.0.0.1:11211', +# } +#} + + +# When DEBUG is True, don't actually send emails to the SMTP server, just store +# them in a directory. This way you won't accidentally spam your mailing-lists +# while you're fiddling with the code. +if DEBUG == True: + EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' + EMAIL_FILE_PATH = os.path.join("{{ mailman3_django_var_dir }}", 'emails') + + +# galaxyproject.mailman3: settings_local.py is required, no exception handling +from settings_local import * diff --git a/library/roles/mailman/templates/settings_local.py.j2 b/library/roles/mailman/templates/settings_local.py.j2 new file mode 100644 index 00000000..4b81f24a --- /dev/null +++ b/library/roles/mailman/templates/settings_local.py.j2 @@ -0,0 +1,206 @@ +# This file is imported by the Mailman Suite. It is used to override +# the default settings from {{ mailman3_django_project_dir }}/settings.py. + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "{{ __mailman3_django_config_merged.secret_key | default('change-this-on-your-production-server') }}" + +ADMINS = ( +{% for admin in __mailman3_django_config_merged.admins %} + ('{{ admin.name }}', '{{ admin.email }}'), +{% endfor %} +) + +# If using multiple domains, this value is overridden in {{ mailman3_django_project_dir }}/settings_DOMAIN.py +SITE_ID = {{ __mailman3_django_config_merged.site_id | default(1) }} + +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/1.8/ref/settings/#allowed-hosts +ALLOWED_HOSTS = [ + "localhost", # Archiving API from Mailman, keep it. + # Add here all production URLs you may have. +{% for host in __mailman3_django_config_merged.allowed_hosts %} + "{{ host }}", +{% endfor %} +] + +# Mailman API credentials +MAILMAN_REST_API_URL = '{{ __mailman3_django_config_merged.rest_api_url | default('http://localhost:8001') }}' +MAILMAN_REST_API_USER = '{{ __mailman3_django_config_merged.rest_api_user | default('restadmin') }}' +MAILMAN_REST_API_PASS = '{{ __mailman3_django_config_merged.rest_api_pass | default('restpass') }}' +MAILMAN_ARCHIVER_KEY = '{{ __mailman3_django_config_merged.archiver_key | default('SecretArchiverAPIKey') }}' +MAILMAN_ARCHIVER_FROM = ( +{% for host in __mailman3_django_config_merged.archiver_from | default(['127.0.0.1', '::1']) %} + '{{ host }}', +{% endfor %} +) + +# Application definition + +INSTALLED_APPS = ( + 'hyperkitty', + 'postorius', + 'django_mailman3', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', + 'django_gravatar', + 'compressor', + 'haystack', + 'django_extensions', + 'django_q', + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + #'django_mailman3.lib.auth.fedora', +{% for provider in __mailman3_django_config_merged.socialaccount_providers | sort %} + 'allauth.socialaccount.providers.{{ provider }}', +{% endfor %} +) + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { +{% for key in __mailman3_django_config_merged.databases | sort %} + '{{ key }}': { +{% for opt in __mailman3_django_config_merged.databases[key] | sort %} + '{{ opt }}': '{{ __mailman3_django_config_merged.databases[key][opt] }}', +{% endfor %} + } +{% endfor %} +} + + +# If you're behind a proxy, use the X-Forwarded-Host header +# See https://docs.djangoproject.com/en/1.8/ref/settings/#use-x-forwarded-host +USE_X_FORWARDED_HOST = {{ 'True' if __mailman3_django_config_merged.use_x_forwarded_host | bool else 'False' }} + +# And if your proxy does your SSL encoding for you, set SECURE_PROXY_SSL_HEADER +# https://docs.djangoproject.com/en/1.8/ref/settings/#secure-proxy-ssl-header +SECURE_PROXY_SSL_HEADER = ('{{ __mailman3_django_config_merged.secure_proxy_ssl_header }}', 'https') + +# Other security settings +# SECURE_SSL_REDIRECT = True +# If you set SECURE_SSL_REDIRECT to True, make sure the SECURE_REDIRECT_EXEMPT +# contains at least this line: +# SECURE_REDIRECT_EXEMPT = [ +# "archives/api/mailman/.*", # Request from Mailman. +# ] +# SESSION_COOKIE_SECURE = True +# SECURE_CONTENT_TYPE_NOSNIFF = True +# SECURE_BROWSER_XSS_FILTER = True +# CSRF_COOKIE_SECURE = True +# CSRF_COOKIE_HTTPONLY = True +# X_FRAME_OPTIONS = 'DENY' + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = '{{ mailman3_language_code }}' + +TIME_ZONE = '{{ __mailman3_django_config_merged.time_zone | default("UTC") }}' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# If you enable internal authentication, this is the address that the emails +# will appear to be coming from. Make sure you set a valid domain name, +# otherwise the emails may get rejected. +# https://docs.djangoproject.com/en/1.8/ref/settings/#default-from-email +DEFAULT_FROM_EMAIL = '{{ __mailman3_django_config_merged.default_from_email }}' + +# If you enable email reporting for error messages, this is where those emails +# will appear to be coming from. Make sure you set a valid domain name, +# otherwise the emails may get rejected. +# https://docs.djangoproject.com/en/1.8/ref/settings/#std:setting-SERVER_EMAIL +SERVER_EMAIL = '{{ __mailman3_django_config_merged.server_email }}' + +# Change this when you have a real email backend +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + + +# +# Social auth +# + +# Django Allauth +# You probably want https in production +ACCOUNT_DEFAULT_HTTP_PROTOCOL = "{{ __mailman3_django_config_merged.default_http_protocol }}" + +SOCIALACCOUNT_PROVIDERS = { +{% for key in __mailman3_django_config_merged.socialaccount_providers | sort %} +{# This just dumps Ansible's Python representation of the value, which isn't ideal, but it probably works #} + '{{ key }}': {{ __mailman3_django_config_merged.socialaccount_providers[key] }} +{% endfor %} +} + + +# +# Gravatar +# https://github.com/twaddington/django-gravatar +# +# Gravatar base url. +# GRAVATAR_URL = 'http://cdn.libravatar.org/' +# Gravatar base secure https url. +# GRAVATAR_SECURE_URL = 'https://seccdn.libravatar.org/' +# Gravatar size in pixels. +# GRAVATAR_DEFAULT_SIZE = '80' +# An image url or one of the following: 'mm', 'identicon', 'monsterid', +# 'wavatar', 'retro'. +# GRAVATAR_DEFAULT_IMAGE = 'mm' +# One of the following: 'g', 'pg', 'r', 'x'. +# GRAVATAR_DEFAULT_RATING = 'g' +# True to use https by default, False for plain http. +# GRAVATAR_DEFAULT_SECURE = True + +# +# django-compressor +# https://pypi.python.org/pypi/django_compressor +# +# On a production setup, setting COMPRESS_OFFLINE to True will bring a +# significant performance improvement, as CSS files will not need to be +# recompiled on each requests. It means running an additional "compress" +# management command after each code upgrade. +# http://django-compressor.readthedocs.io/en/latest/usage/#offline-compression +COMPRESS_OFFLINE = {{ 'True' if __mailman3_django_config_merged.compress_offline | bool else 'False' }} + +# Needed for debug mode +# INTERNAL_IPS = ('127.0.0.1',) + + +# Using the cache infrastructure can significantly improve performance on a +# production setup. This is an example with a local Memcached server. +#CACHES = { +# 'default': { +# 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', +# 'LOCATION': '127.0.0.1:11211', +# } +#} + + +# +# HyperKitty-specific +# + +# Only display mailing-lists from the same virtual host as the webserver +FILTER_VHOST = False + +{% if __mailman3_django_config_merged.hyperkitty_attachment_folder is defined %} +HYPERKITTY_ATTACHMENT_FOLDER = '{{ __mailman3_django_config_merged.hyperkitty_attachment_folder }}' +{% endif %} + +POSTORIUS_TEMPLATE_BASE_URL = 'http://localhost:8000' diff --git a/library/roles/mailman/templates/urls.py.j2 b/library/roles/mailman/templates/urls.py.j2 new file mode 100644 index 00000000..e6f05835 --- /dev/null +++ b/library/roles/mailman/templates/urls.py.j2 @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2016 by the Free Software Foundation, Inc. +# +# This file is part of Postorius. +# +# Postorius is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Postorius is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# Postorius. If not, see . + + +from django.conf.urls import include, url +from django.contrib import admin +from django.urls import reverse_lazy +from django.views.generic import RedirectView + +urlpatterns = [ +{% if mailman3_postorius_root %} + url(r'^$', RedirectView.as_view( + url=reverse_lazy('list_index'), + permanent=True)), +{% endif %} + url(r'^{{ mailman3_postorius_root }}', include('postorius.urls')), + url(r'^{{ mailman3_hyperkitty_root }}', include('hyperkitty.urls')), + url(r'', include('django_mailman3.urls')), + url(r'^accounts/', include('allauth.urls')), + # Django admin + url(r'^admin/', admin.site.urls), +] diff --git a/library/roles/mailman/templates/uwsgi.ini.j2 b/library/roles/mailman/templates/uwsgi.ini.j2 new file mode 100644 index 00000000..a7403c7b --- /dev/null +++ b/library/roles/mailman/templates/uwsgi.ini.j2 @@ -0,0 +1,78 @@ +{% macro getsock(sock) -%} + {% if domain is defined -%} + {% if ':' in sock -%} + {% set host, port = sock.rsplit(':', 1) -%} + {{ [host, (port | int) + site_id - 1] | join(':') }} + {% else -%} + {% set path, ext = sock | splitext -%} + {{ [path, '_' ~ domain, ext] | join('') }} + {% endif -%} + {% else -%} + {{ sock }} + {% endif -%} +{% endmacro -%} + +[uwsgi] +{% if domain is defined %} +# Django settings module to load +env = DJANGO_SETTINGS_MODULE=settings{{ '_' ~ domain | replace('.', '_') | replace('-', '_') }} +{% endif %} + +# Port on which uwsgi will be listening. +uwsgi-socket = {{ getsock(mailman3_uwsgi_socket) }} +{% if mailman3_http_socket is defined %} +http-socket = {{ getsock(mailman3_http_socket) }} +{% endif %} + +# Enable threading for python +enable-threads = true + +# Move to the directory wher the django files are. +chdir = {{ mailman3_django_project_dir }} + +# Use the wsgi file provided with the django project. +wsgi-file = wsgi.py + +# Setup default number of processes and threads per process. +master = true +process = 2 +threads = 2 + +# Drop privielges and don't run as root. +uid = {{ mailman3_web_user }} +gid = {{ mailman3_web_group }} + +virtualenv = {{ mailman3_install_dir }} + +# Setup the django_q related worker processes. +attach-daemon = ./manage.py qcluster + +{% if mailman3_domains is not defined or (site_id | default(-1)) == 0 %} +# Setup hyperkitty's cron jobs. +unique-cron = -1 -1 -1 -1 -1 ./manage.py runjobs minutely +unique-cron = -15 -1 -1 -1 -1 ./manage.py runjobs quarter_hourly +unique-cron = 0 -1 -1 -1 -1 ./manage.py runjobs hourly +unique-cron = 0 0 -1 -1 -1 ./manage.py runjobs daily +unique-cron = 0 0 1 -1 -1 ./manage.py runjobs monthly +unique-cron = 0 0 -1 -1 0 ./manage.py runjobs weekly +unique-cron = 0 0 1 1 -1 ./manage.py runjobs yearly + +{% endif %} +{% if mailman3_uwsgi_static %} +# Directly serve static content. +static-map = /static={{ mailman3_django_static_dir }} + +{% endif %} +# Setup the request log. +req-logger = file:{{ mailman3_django_log_dir }}/uwsgi{{ '_' ~ domain if domain is defined else '' }}.log + +# Log cron seperately. +logger = cron file:{{ mailman3_django_log_dir }}/uwsgi-cron{{ '_' ~ domain if domain is defined else '' }}.log +log-route = cron uwsgi-cron + +# Log qcluster commands seperately. +logger = qcluster file:{{ mailman3_django_log_dir }}/uwsgi-qcluster{{ '_' ~ domain if domain is defined else '' }}.log +log-route = qcluster uwsgi-daemons + +# Last log and it logs the rest of the stuff. +logger = file:{{ mailman3_django_log_dir }}/uwsgi-error{{ '_' ~ domain if domain is defined else '' }}.log diff --git a/library/roles/mailman/templates/wsgi.py.j2 b/library/roles/mailman/templates/wsgi.py.j2 new file mode 100644 index 00000000..5535f996 --- /dev/null +++ b/library/roles/mailman/templates/wsgi.py.j2 @@ -0,0 +1,40 @@ +""" +WSGI config for HyperKitty project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/ docs_version /howto/deployment/wsgi/ +""" + +import os + +# import sys +# import site + +# For some unknown reason, sometimes mod_wsgi fails to set the python paths to +# the virtualenv, with the 'python-path' option. You can do it here too. +# +# # Remember original sys.path. +# prev_sys_path = list(sys.path) +# # Add here, for the settings module +# site.addsitedir(os.path.abspath(os.path.dirname(__file__))) +# # Add the virtualenv +# venv = os.path.join(os.path.abspath(os.path.dirname(__file__)), +# '..', 'lib', 'python2.6', 'site-packages') +# site.addsitedir(venv) +# # Reorder sys.path so new directories at the front. +# new_sys_path = [] +# for item in list(sys.path): +# if item not in prev_sys_path: +# new_sys_path.append(item) +# sys.path.remove(item) +# sys.path[:0] = new_sys_path + +from django.core.wsgi import get_wsgi_application + +m = os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") +# https://github.com/unbit/uwsgi/issues/1980 +os.environ["DJANGO_SETTINGS_MODULE"] = m.replace('-', '_').replace('.', '_') + +application = get_wsgi_application()