/home/zuul/src/opendev.org/opendev/system-config/playbooks/service-lists3.yaml
Execution
Date 08 Apr 2026 21:40:57 +0000
Duration 00:01:49.55
Controller bridge99.opendev.org
User root
Versions
Ansible 2.15.13
ara 1.7.5 / 1.7.5
Python 3.10.12
Summary
1 Hosts
156 Tasks
154 Results
1 Plays
20 Files
0 Records

File: /home/zuul/src/opendev.org/opendev/system-config/playbooks/roles/mailman3/tasks/main.yaml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# The old mailman2 exim config refers to this file. Write it out
# to make basic testing happy, but we may need to clean it up or
# modify it for mailman3.
- name: Write /etc/aliases.domain
  template:
    src: "domain_aliases.j2"
    dest: "/etc/aliases.domain"
    mode: '0444'

- name: Create Mailman Group
  group:
    name: mailman
    gid: 10010
    system: yes

- name: Create Mailman User
  user:
    name: mailman
    uid: 10010
    comment: Mailman User
    shell: /bin/bash
    home: /var/lib/mailman
    group: mailman
    create_home: yes
    system: yes

#### Install Mailman ####

- name: Ensure Mailman core volume directory exists
  file:
    state: directory
    path: "/var/lib/mailman/core"
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 65533
    mode: '0755'

- name: Copy our mailman-extra.cfg for mailman-core
  template:
    src: mailman-extra.cfg.j2
    dest: /var/lib/mailman/core/mailman-extra.cfg
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 65533
    mode: '0644'
  notify: mailman restart containers

- name: Ensure Mailman database volume directory exists
  file:
    state: directory
    path: "/var/lib/mailman/database"
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 999
    group: 999
    mode: '0755'

- name: Ensure Mailman web volume directories exist
  file:
    state: directory
    path: "/var/lib/mailman/{{ item }}"
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 101
    mode: '0755'
  loop:
    - import
    - web
    - web-data
    - web-data/fulltext_index
    - web-data/mm2archives

- name: Copy our overridden settings.py for mailman-web
  copy:
    src: web-settings.py
    dest: /var/lib/mailman/web/settings.py
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 101
    mode: '0644'
  notify: mailman restart containers

- name: Copy our updated uwsgi.ini for mailman-web
  copy:
    src: uwsgi.ini
    dest: /var/lib/mailman/web/uwsgi.ini
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 101
    mode: '0644'
  notify: mailman restart containers

- name: Check for initial setup flag file
  stat:
    path: /var/lib/mailman/bootstrapped
  register: _mailman_bootstrapped

# https://docs.mailman3.org/en/latest/faq.html#the-domain-name-displayed-in-hyperkitty-shows-example-com-or-something-else
- name: Temporarily turn off magic domain guessing
  lineinfile:
    state: present
    backrefs: yes
    path: /var/lib/mailman/web/settings.py
    regexp: '^SITE_ID = 0$'
    line: 'SITE_ID = 1'
  when: not _mailman_bootstrapped.stat.exists

- name: Copy our settings_local.py for mailman-web
  copy:
    src: web-settings_local.py
    dest: /var/lib/mailman/web-data/settings_local.py
    # TODO: undo for https://github.com/maxking/docker-mailman/issues/550
    owner: 100
    group: 101
    mode: '0644'
  notify: mailman restart containers

- name: Copy our max_allowed_packet override config
  copy:
    src: 99-max_allowed_packet.cnf
    dest: /var/lib/mailman/99-max_allowed_packet.cnf
    owner: 999
    group: 999
    mode: '0644'
  notify: mailman restart containers

- name: Create config dir for anubis
  file:
    name: /var/lib/anubis
    state: directory
    mode: 0755
    owner: root
    group: root

- name: Copy anubis policy file to config dir
  copy:
    src: anubis_botPolicy.yaml
    dest: /var/lib/anubis/botPolicy.yaml
    mode: 0644
    owner: root
    group: root

- name: Ensure /etc/mailman-compose directory
  file:
    state: directory
    path: /etc/mailman-compose
    mode: '0755'

- name: Put docker-compose file in place
  template:
    src: docker-compose.yaml.j2
    dest: /etc/mailman-compose/docker-compose.yaml
    mode: '0600'
  notify: mailman restart containers

- name: Run docker-compose pull
  shell:
    cmd: docker-compose pull
    chdir: /etc/mailman-compose/

- name: Run docker-compose up
  shell:
    cmd: docker-compose up -d
    chdir: /etc/mailman-compose/
  register: mailman_dcup

- name: Run docker prune to cleanup unneeded images
  shell:
    cmd: docker image prune -f

- name: Create robots.txt location dir
  file:
    path: /var/www/robots
    state: directory
    owner: root
    group: root
    mode: '0755'

- name: Copy the robots.txt
  copy:
    src: robots.txt
    dest: /var/www/robots/robots.txt
    owner: root
    group: root
    mode: '0644'

- name: Install apache2
  package:
    name:
      - apache2
      - apache2-utils
      - libapache2-mod-security2
    state: present

- name: Add UA filter macro to apache config
  # This is used in the mailman apache vhost.
  include_role:
    name: apache-ua-filter

- name: Apache modules
  apache2_module:
    state: present
    name: "{{ a2_mod }}"
  loop:
    - authz_host
    - proxy
    - proxy_http
    - proxy_uwsgi
    - security2
    - ssl
    - rewrite
  loop_control:
    loop_var: a2_mod
  notify: mailman restart apache2

- name: Make sure packaged default site disabled
  command: a2dissite 000-default.conf
  args:
    removes: /etc/apache2/sites-enabled/000-default.conf

- name: Create mailman vhost config
  template:
    src: mailman.vhost.j2
    dest: "/etc/apache2/sites-enabled/50-{{ mailman_sites.0.listdomain }}.conf"
    owner: root
    group: root
    mode: '0644'
  notify: mailman reload apache2

- name: Enable apache2 server
  service:
    name: "apache2"
    enabled: yes

#### Configure Mailman Services ####

- name: Wait for mm3 REST API to be up and running
  uri:
    url: 'http://localhost:8001/3.1/domains'
    url_username: restadmin
    url_password: "{{ mailman3_rest_password }}"
    force_basic_auth: yes
    method: GET
  register: mm_rest_api_up
  delay: 1
  retries: 300
  until: mm_rest_api_up and mm_rest_api_up.status == 200
  no_log: true

# It has been difficult to nail down a reliable mathod for determining
# when the database is sufficiently populated that we can create the django
# admin user. We apply a number of approaches in response to this. If we
# can identify a single method that is reliable this list can be trimmed.
- name: Wait for DB to be populated
  command: >
    /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T database
    bash -c 'mysql -u mailman -p"$MYSQL_PASSWORD" -D mailmandb -e
    "SHOW TABLES LIKE \"auth_user\";"'
  register: django_db_exists
  delay: 1
  retries: 300
  until: django_db_exists.stdout_lines | length > 1 and django_db_exists.stdout_lines[1] == "auth_user"

- name: Wait for DB to be populated second approach
  command: >
    /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T mailman-core
    sh -c 'alembic -c /usr/lib/python*/site-packages/mailman/config/alembic.cfg current'
  register: alembic_version
  delay: 1
  retries: 300
  until: alembic_version.stdout_lines | length > 0 and "(head)" in alembic_version.stdout_lines[0]

- name: Wait for DB to be populated third approach
  shell: >
    /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T mailman-web
    bash -c 'python3 manage.py showmigrations' | grep -q '^ \[ \] [0-9]\+_.*'
  register: django_db_migrations
  delay: 1
  retries: 300
  failed_when: false
  # When grep stops matching the empty '[ ]' that indicates all migrations
  # are marked with '[X]' and are complete. Grep returns non zero when we
  # reach this point.
  until: django_db_migrations.rc != 0

- name: Check if django admin user exists
  command: >
    /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T database
    bash -c 'mysql -u mailman -p"$MYSQL_PASSWORD" -D mailmandb -e
    "SELECT COUNT(id) FROM auth_user WHERE id = 1 AND is_superuser = 1;"'
  register: django_admin_exists

- name: Create django admin user
  when: django_admin_exists.stdout_lines[1] == "0"
  command: >
    /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T mailman-web
    bash -c "DJANGO_SUPERUSER_PASSWORD={{ mailman3_admin_password }}
    python3 manage.py createsuperuser --no-input
    --username {{ mailman3_admin_user }}
    --email '{{ mailman3_admin_email }}'"
  no_log: true

- name: Complete new server bootstrapping
  when: not _mailman_bootstrapped.stat.exists
  block:
  - name: Run docker-compose down
    shell:
      cmd: docker-compose down
      chdir: /etc/mailman-compose/

  - name: Turn magic domain guessing back on
    lineinfile:
      state: present
      backrefs: yes
      path: /var/lib/mailman/web/settings.py
      regexp: '^SITE_ID = 0$'
      line: 'SITE_ID = 1'

  - name: Create the bootstrapped flag file
    copy:
      content: ""
      dest: /var/lib/mailman/bootstrapped
      force: no
      owner: 100
      group: 101
      mode: '0644'

  - name: Run docker-compose up
    shell:
      cmd: docker-compose up -d
      chdir: /etc/mailman-compose/

  - name: Wait for mm3 REST API to be up and running
    uri:
      url: 'http://localhost:8001/3.1/domains'
      url_username: restadmin
      url_password: "{{ mailman3_rest_password }}"
      force_basic_auth: yes
      method: GET
    register: mm_rest_api_up
    delay: 1
    retries: 300
    until: mm_rest_api_up and mm_rest_api_up.status == 200
    no_log: true

- name: Create lists in mm3
  include_tasks: create_lists.yaml
  loop: "{{ mailman_sites }}"
  loop_control:
    loop_var: mm_site

#### Logrotate for service logs ####

- name: Rotate mailman-core logs
  include_role:
    name: logrotate
  vars:
    logrotate_rotate: 90
    logrotate_file_name: '/var/lib/mailman/core/var/logs/*.log'

- name: Rotate mailman-web logs
  include_role:
    name: logrotate
  vars:
    logrotate_rotate: 90
    logrotate_file_name: '/var/lib/mailman/web-data/logs/*.log'

#### Database Backups ####

- name: Create db backup dest
  file:
    state: directory
    path: /var/backups/mailman-mariadb
    mode: 0700
    owner: root
    group: root

- name: Set up cron job to backup the database
  cron:
    name: mailman-db-backup
    state: present
    user: root
    job: >
      /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T database
      bash -c '/usr/bin/mysqldump --opt --databases mailmandb --single-transaction -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"' |
      gzip -9 > /var/backups/mailman-mariadb/mailman-mariadb.sql.gz
    minute: 14
    hour: 5

- name: Rotate db backups
  include_role:
    name: logrotate
  vars:
    logrotate_file_name: /var/backups/mailman-mariadb/mailman-mariadb.sql.gz
    logrotate_compress: false

- name: Setup db backup streaming job
  block:
    - name: Create backup streaming config dir
      file:
        path: /etc/borg-streams
        state: directory

    - name: Create db streaming file
      copy:
        content: >-
            /usr/local/bin/docker-compose -f /etc/mailman-compose/docker-compose.yaml exec -T database
            bash -c '/usr/bin/mysqldump --skip-extended-insert --databases mailmandb --single-transaction -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"'
        dest: /etc/borg-streams/mysql