Replace cronjobs with systemd timers

Fixes https://github.com/spantaleev/matrix-docker-ansible-deploy/issues/756

Related to https://github.com/spantaleev/matrix-docker-ansible-deploy/issues/737

I feel like timers are somewhat more complicated and dirty (compared to
cronjobs), but they come with these benefits:

- log output goes to journald
- on newer systemd distros, you can see when the timer fired, when it
will fire, etc.
- we don't need to rely on cron (reducing our dependencies to just
systemd + Docker)

Cronjobs work well, but it's one more dependency that needs to be
installed. We were even asking people to install it manually
(in `docs/prerequisites.md`), which could have gone unnoticed.

Once in a while someone says "my SSL certificates didn't renew"
and it's likely because they forgot to install a cron daemon.

Switching to systemd timers means that installation is simpler
and more unified.
development
Slavi Pantaleev 4 years ago
parent 05ca9357a8
commit e1690722f7

@ -1,3 +1,14 @@
# 2021-01-14
## Moving from cronjobs to systemd timers
We no longer use cronjobs for Let's Encrypt SSL renewal and `matrix-nginx-proxy`/`matrix-coturn` reloading. Instead, we've switched to systemd timers.
The largest benefit of this is that we no longer require you to install a cron daemon, thus simplifying our install procedure.
The playbook will migrate you from cronjobs to systemd timers automatically. This is just a heads up.
# 2021-01-08 # 2021-01-08
## (Breaking Change) New SSL configuration ## (Breaking Change) New SSL configuration

@ -16,8 +16,6 @@ If your distro runs within an [LXC container](https://linuxcontainers.org/), you
- [Python](https://www.python.org/) being installed on the server. Most distributions install Python by default, but some don't (e.g. Ubuntu 18.04) and require manual installation (something like `apt-get install python3`). On some distros, Ansible may incorrectly [detect the Python version](https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html) (2 vs 3) and you may need to explicitly specify the interpreter path in `inventory/hosts` during installation (e.g. `ansible_python_interpreter=/usr/bin/python3`) - [Python](https://www.python.org/) being installed on the server. Most distributions install Python by default, but some don't (e.g. Ubuntu 18.04) and require manual installation (something like `apt-get install python3`). On some distros, Ansible may incorrectly [detect the Python version](https://docs.ansible.com/ansible/latest/reference_appendices/interpreter_discovery.html) (2 vs 3) and you may need to explicitly specify the interpreter path in `inventory/hosts` during installation (e.g. `ansible_python_interpreter=/usr/bin/python3`)
- A `cron`-like tool installed on the server such as `cron` or `anacron` to automatically schedule the Let's Encrypt SSL certificates's renewal. *This can be ignored if you use your own SSL certificates.*
- The [Ansible](http://ansible.com/) program being installed on your own computer. It's used to run this playbook and configures your server for you. Take a look at [our guide about Ansible](ansible.md) for more information, as well as [version requirements](ansible.md#supported-ansible-versions) and alternative ways to run Ansible. - The [Ansible](http://ansible.com/) program being installed on your own computer. It's used to run this playbook and configures your server for you. Take a look at [our guide about Ansible](ansible.md) for more information, as well as [version requirements](ansible.md#supported-ansible-versions) and alternative ways to run Ansible.
- Either the `dig` tool or `python-dns` installed on your own computer. Used later on, by the playbook's [services check](maintenance-checking-services.md) feature. - Either the `dig` tool or `python-dns` installed on your own computer. Used later on, by the playbook's [services check](maintenance-checking-services.md) feature.

@ -23,15 +23,13 @@ If you prefer to uninstall manually, run these commands (most are meant to be ex
- ensure all Matrix services are stopped: `ansible-playbook -i inventory/hosts setup.yml --tags=stop` (if you can't get Ansible working to run this command, you can run `systemctl stop 'matrix*'` manually on the server) - ensure all Matrix services are stopped: `ansible-playbook -i inventory/hosts setup.yml --tags=stop` (if you can't get Ansible working to run this command, you can run `systemctl stop 'matrix*'` manually on the server)
- delete the Matrix-related systemd `.service` files (`rm -f /etc/systemd/system/matrix*.service`) and reload systemd (`systemctl daemon-reload`) - delete the Matrix-related systemd `.service` and `.timer` files (`rm -f /etc/systemd/system/matrix*.{service,timer}`) and reload systemd (`systemctl daemon-reload`)
- delete all Matrix-related cronjobs (`rm -f /etc/cron.d/matrix*`)
- delete some helper scripts (`rm -f /usr/local/bin/matrix*`) - delete some helper scripts (`rm -f /usr/local/bin/matrix*`)
- delete some cached Docker images (`docker system prune -a`) or just delete them all (`docker rmi $(docker images -aq)`) - delete some cached Docker images (`docker system prune -a`) or just delete them all (`docker rmi $(docker images -aq)`)
- delete the Docker network: `docker network rm matrix` (might have been deleted already if you ran the `docker system prune` command) - delete the Docker networks: `docker network rm matrix matrix-coturn` (might have been deleted already if you ran the `docker system prune` command)
- uninstall Docker itself, if necessary - uninstall Docker itself, if necessary

@ -48,7 +48,11 @@ matrix_base_data_path_mode: "750"
matrix_static_files_base_path: "{{ matrix_base_data_path }}/static-files" matrix_static_files_base_path: "{{ matrix_base_data_path }}/static-files"
matrix_systemd_path: "/etc/systemd/system" matrix_systemd_path: "/etc/systemd/system"
# This is now unused. We keep it so that cleanup tasks can use it.
# To be removed in the future.
matrix_cron_path: "/etc/cron.d" matrix_cron_path: "/etc/cron.d"
matrix_local_bin_path: "/usr/local/bin" matrix_local_bin_path: "/usr/local/bin"
matrix_host_command_docker: "/usr/bin/env docker" matrix_host_command_docker: "/usr/bin/env docker"

@ -20,8 +20,6 @@ else
rm -f {{ matrix_systemd_path }}/$s rm -f {{ matrix_systemd_path }}/$s
done done
systemctl daemon-reload systemctl daemon-reload
echo "Remove matrix cronjobs"
find /etc/cron.d/ -name "matrix-*" -delete
echo "Remove matrix scripts" echo "Remove matrix scripts"
find {{ matrix_local_bin_path }}/ -name "matrix-*" -delete find {{ matrix_local_bin_path }}/ -name "matrix-*" -delete
echo "Remove unused Docker images and resources" echo "Remove unused Docker images and resources"

@ -1,6 +1,6 @@
--- ---
- name: Deterimne whether we should make services autostart - name: Determine whether we should make services autostart
set_fact: set_fact:
matrix_services_autostart_enabled_bool: "{{ true if matrix_services_autostart_enabled|default('') == '' else matrix_services_autostart_enabled|bool }}" matrix_services_autostart_enabled_bool: "{{ true if matrix_services_autostart_enabled|default('') == '' else matrix_services_autostart_enabled|bool }}"
@ -46,7 +46,7 @@
Try running `systemctl status {{ item }}` and `journalctl -fu {{ item }}` on the server to investigate. Try running `systemctl status {{ item }}` and `journalctl -fu {{ item }}` on the server to investigate.
with_items: "{{ matrix_systemd_services_list }}" with_items: "{{ matrix_systemd_services_list }}"
when: when:
- "ansible_facts.services[item]|default(none) is none or ansible_facts.services[item].state != 'running'" - "item.endswith('.service') and (ansible_facts.services[item]|default(none) is none or ansible_facts.services[item].state != 'running')"
when: " ansible_distribution != 'Archlinux'" when: " ansible_distribution != 'Archlinux'"
- block: - block:

@ -2,6 +2,10 @@
matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-coturn.service'] }}" matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-coturn.service'] }}"
when: matrix_coturn_enabled|bool when: matrix_coturn_enabled|bool
- set_fact:
matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-coturn-reload.timer'] }}"
when: "matrix_coturn_enabled|bool and matrix_coturn_tls_enabled|bool"
# ansible lower than 2.8, does not support docker_image build parameters # ansible lower than 2.8, does not support docker_image build parameters
# for self buildig it is explicitly needed, so we rather fail here # for self buildig it is explicitly needed, so we rather fail here
- name: Fail if running on Ansible lower than 2.8 and trying self building - name: Fail if running on Ansible lower than 2.8 and trying self building

@ -1,5 +1,11 @@
--- ---
# This is a cleanup/migration task. It can be removed some time in the future.
- name: (Migration) Remove deprecated cronjob
file:
path: "{{ matrix_cron_path }}/matrix-coturn-ssl-reload"
state: absent
- name: Ensure Matrix Coturn path exists - name: Ensure Matrix Coturn path exists
file: file:
path: "{{ item.path }}" path: "{{ item.path }}"
@ -19,6 +25,7 @@
force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_coturn_docker_image_force_pull }}" force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_coturn_docker_image_force_pull }}"
when: "not matrix_coturn_container_image_self_build|bool" when: "not matrix_coturn_container_image_self_build|bool"
- block:
- name: Ensure Coturn repository is present on self-build - name: Ensure Coturn repository is present on self-build
git: git:
repo: "{{ matrix_coturn_container_image_self_build_repo }}" repo: "{{ matrix_coturn_container_image_self_build_repo }}"
@ -26,7 +33,6 @@
version: "{{ matrix_coturn_docker_image.split(':')[1] }}" version: "{{ matrix_coturn_docker_image.split(':')[1] }}"
force: "yes" force: "yes"
register: matrix_coturn_git_pull_results register: matrix_coturn_git_pull_results
when: "matrix_coturn_container_image_self_build|bool"
- name: Ensure Coturn Docker image is built - name: Ensure Coturn Docker image is built
docker_image: docker_image:
@ -52,6 +58,8 @@
src: "{{ role_path }}/templates/turnserver.conf.j2" src: "{{ role_path }}/templates/turnserver.conf.j2"
dest: "{{ matrix_coturn_config_path }}" dest: "{{ matrix_coturn_config_path }}"
mode: 0644 mode: 0644
owner: "{{ matrix_user_username }}"
group: "{{ matrix_user_groupname }}"
- name: Ensure Coturn network is created in Docker - name: Ensure Coturn network is created in Docker
docker_network: docker_network:
@ -63,26 +71,34 @@
src: "{{ role_path }}/templates/systemd/matrix-coturn.service.j2" src: "{{ role_path }}/templates/systemd/matrix-coturn.service.j2"
dest: "{{ matrix_systemd_path }}/matrix-coturn.service" dest: "{{ matrix_systemd_path }}/matrix-coturn.service"
mode: 0644 mode: 0644
register: matrix_coturn_systemd_service_result register: matrix_coturn_systemd_service_change_results
- name: Ensure systemd reloaded after matrix-coturn.service installation
service:
daemon_reload: yes
when: "matrix_coturn_systemd_service_result.changed"
# This may be unnecessary when more long-lived certificates are used. # This may be unnecessary when more long-lived certificates are used.
# We optimize for the common use-case though (short-lived Let's Encrypt certificates). # We optimize for the common use-case though (short-lived Let's Encrypt certificates).
# Reloading doesn't hurt anyway, so there's no need to make this more flexible. # Reloading doesn't hurt anyway, so there's no need to make this more flexible.
- name: Ensure periodic reloading of matrix-coturn is configured for SSL renewal (matrix-coturn-reload) - name: Ensure reloading systemd units installed, if necessary
template: template:
src: "{{ role_path }}/templates/cron.d/matrix-coturn-ssl-reload.j2" src: "{{ role_path }}/templates/systemd/{{ item }}.j2"
dest: /etc/cron.d/matrix-coturn-ssl-reload dest: "{{ matrix_systemd_path }}/{{ item }}"
mode: 0644 mode: 0644
register: "matrix_coturn_systemd_service_change_results"
when: "matrix_coturn_tls_enabled|bool" when: "matrix_coturn_tls_enabled|bool"
with_items:
- matrix-coturn-reload.service
- matrix-coturn-reload.timer
# A similar task exists in `setup_uninstall.yml` # A similar task exists in `setup_uninstall.yml`
- name: Ensure matrix-coturn-ssl-reload cronjob removed - name: Ensure reloading systemd units uninstalled, if unnecessary
file: file:
path: /etc/cron.d/matrix-coturn-ssl-reload path: "{{ item }}"
state: absent state: absent
register: "matrix_coturn_systemd_service_change_results"
when: "not matrix_coturn_tls_enabled|bool" when: "not matrix_coturn_tls_enabled|bool"
with_items:
- matrix-coturn-reload.service
- matrix-coturn-reload.timer
- name: Ensure systemd reloaded if systemd units changed
service:
daemon_reload: yes
when: "matrix_coturn_systemd_service_change_results.changed"

@ -1,11 +1,5 @@
--- ---
# A similar task exists in `setup_install.yml`
- name: Ensure matrix-coturn-ssl-reload cronjob removed
file:
path: /etc/cron.d/matrix-coturn-ssl-reload
state: absent
- name: Check existence of matrix-coturn service - name: Check existence of matrix-coturn service
stat: stat:
path: "{{ matrix_systemd_path }}/matrix-coturn.service" path: "{{ matrix_systemd_path }}/matrix-coturn.service"
@ -17,28 +11,37 @@
name: matrix-coturn name: matrix-coturn
state: stopped state: stopped
daemon_reload: yes daemon_reload: yes
register: stopping_result when: "matrix_coturn_service_stat.stat.exists|bool"
when: "not matrix_coturn_enabled|bool and matrix_coturn_service_stat.stat.exists"
- name: Ensure matrix-coturn.service doesn't exist - name: Ensure matrix-coturn-reload.timer is stopped
service:
name: matrix-coturn
state: stopped
daemon_reload: yes
failed_when: false
when: "matrix_coturn_service_stat.stat.exists|bool"
- name: Ensure systemd units don't exist
file: file:
path: "{{ matrix_systemd_path }}/matrix-coturn.service" path: "{{ matrix_systemd_path }}/{{ item }}"
state: absent state: absent
when: "not matrix_coturn_enabled|bool and matrix_coturn_service_stat.stat.exists" register: matrix_coturn_systemd_unit_uninstallation_result
with_items:
- matrix-coturn.service
- matrix-coturn-reload.service
- matrix-coturn-reload.timer
- name: Ensure systemd reloaded after matrix-coturn.service removal - name: Ensure systemd reloaded after unit removal
service: service:
daemon_reload: yes daemon_reload: yes
when: "not matrix_coturn_enabled|bool and matrix_coturn_service_stat.stat.exists" when: "matrix_coturn_systemd_unit_uninstallation_result.changed|bool"
- name: Ensure Matrix coturn paths don't exist - name: Ensure Matrix coturn paths don't exist
file: file:
path: "{{ matrix_coturn_base_path }}" path: "{{ matrix_coturn_base_path }}"
state: absent state: absent
when: "not matrix_coturn_enabled|bool"
- name: Ensure coturn Docker image doesn't exist - name: Ensure coturn Docker image doesn't exist
docker_image: docker_image:
name: "{{ matrix_coturn_docker_image }}" name: "{{ matrix_coturn_docker_image }}"
state: absent state: absent
when: "not matrix_coturn_enabled|bool"

@ -1 +0,0 @@
20 4 */5 * * root {{ matrix_host_command_systemctl }} reload matrix-coturn.service

@ -0,0 +1,6 @@
[Unit]
Description=Reloads matrix-coturn so that new SSL certificates can kick in
[Service]
Type=oneshot
ExecStart={{ matrix_host_command_systemctl }} reload matrix-coturn.service

@ -0,0 +1,10 @@
[Unit]
Description=Reloads matrix-coturn periodically so that new SSL certificates can kick in
[Timer]
Unit=matrix-coturn-reload.service
OnCalendar=Sunday *-*-* 13:00:00
RandomizedDelaySec=3h
[Install]
WantedBy=timers.target

@ -1,3 +1,8 @@
- set_fact: - set_fact:
matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-nginx-proxy.service'] }}" matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-nginx-proxy.service'] }}"
when: matrix_nginx_proxy_enabled|bool when: matrix_nginx_proxy_enabled|bool
- set_fact:
matrix_systemd_services_list: "{{ matrix_systemd_services_list + [item.name] }}"
when: "item.applicable|bool and item.enableable|bool"
with_items: "{{ matrix_ssl_renewal_systemd_units_list }}"

@ -10,50 +10,53 @@
- "{{ matrix_local_bin_path }}/matrix-ssl-certificates-renew" - "{{ matrix_local_bin_path }}/matrix-ssl-certificates-renew"
- "{{ matrix_cron_path }}/matrix-ssl-certificate-renewal" - "{{ matrix_cron_path }}/matrix-ssl-certificate-renewal"
- "{{ matrix_cron_path }}/matrix-nginx-proxy-periodic-restarter" - "{{ matrix_cron_path }}/matrix-nginx-proxy-periodic-restarter"
- "/etc/cron.d/matrix-ssl-lets-encrypt"
- "{{ matrix_local_bin_path }}/matrix-ssl-lets-encrypt-certificates-renew"
# #
# Tasks related to setting up Let's Encrypt's management of certificates # Tasks related to setting up Let's Encrypt's management of certificates
# #
- block:
- name: Ensure certbot Docker image is pulled - name: Ensure certbot Docker image is pulled
docker_image: docker_image:
name: "{{ matrix_ssl_lets_encrypt_certbot_docker_image }}" name: "{{ matrix_ssl_lets_encrypt_certbot_docker_image }}"
source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}" source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}"
force_source: "{{ matrix_ssl_lets_encrypt_certbot_docker_image_force_pull if ansible_version.major > 2 or ansible_version.minor >= 8 else omit }}" force_source: "{{ matrix_ssl_lets_encrypt_certbot_docker_image_force_pull if ansible_version.major > 2 or ansible_version.minor >= 8 else omit }}"
force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_ssl_lets_encrypt_certbot_docker_image_force_pull }}" force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_ssl_lets_encrypt_certbot_docker_image_force_pull }}"
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Obtain Let's Encrypt certificates - name: Obtain Let's Encrypt certificates
include_tasks: "{{ role_path }}/tasks/ssl/setup_ssl_lets_encrypt_obtain_for_domain.yml" include_tasks: "{{ role_path }}/tasks/ssl/setup_ssl_lets_encrypt_obtain_for_domain.yml"
with_items: "{{ matrix_ssl_domains_to_obtain_certificates_for }}" with_items: "{{ matrix_ssl_domains_to_obtain_certificates_for }}"
loop_control: loop_control:
loop_var: domain_name loop_var: domain_name
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Ensure Let's Encrypt SSL renewal script installed - name: Ensure Let's Encrypt SSL renewal script installed
template: template:
src: "{{ role_path }}/templates/usr-local-bin/matrix-ssl-lets-encrypt-certificates-renew.j2" src: "{{ role_path }}/templates/usr-local-bin/matrix-ssl-lets-encrypt-certificates-renew.j2"
dest: "{{ matrix_local_bin_path }}/matrix-ssl-lets-encrypt-certificates-renew" dest: "{{ matrix_local_bin_path }}/matrix-ssl-lets-encrypt-certificates-renew"
mode: 0750 mode: 0750
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Ensure periodic SSL renewal cronjob configured - name: Ensure SSL renewal systemd units installed
template: template:
src: "{{ role_path }}/templates/cron.d/matrix-ssl-lets-encrypt.j2" src: "{{ role_path }}/templates/systemd/{{ item.name }}.j2"
dest: /etc/cron.d/matrix-ssl-lets-encrypt dest: "{{ matrix_systemd_path }}/{{ item.name }}"
mode: 0644 mode: 0644
when: "item.applicable|bool"
with_items: "{{ matrix_ssl_renewal_systemd_units_list }}"
when: "matrix_ssl_retrieval_method == 'lets-encrypt'" when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
# #
# Tasks related to getting rid of Let's Encrypt's management of certificates # Tasks related to getting rid of Let's Encrypt's management of certificates
# #
- block:
- name: Ensure matrix-ssl-lets-encrypt-renew cronjob removed - name: Ensure matrix-ssl-lets-encrypt-renew cronjob removed
file: file:
path: /etc/cron.d/matrix-ssl-lets-encrypt path: "{{ matrix_systemd_path }}/{{ item.name }}"
state: absent state: absent
when: "matrix_ssl_retrieval_method != 'lets-encrypt'" when: "{{ not item.applicable }}"
with_items: "{{ matrix_ssl_renewal_systemd_units_list }}"
- name: Ensure Let's Encrypt SSL renewal script removed - name: Ensure Let's Encrypt SSL renewal script removed
file: file:

@ -1,5 +0,0 @@
MAILTO="{{ matrix_ssl_lets_encrypt_support_email }}"
15 4 * * * root {{ matrix_local_bin_path }}/matrix-ssl-lets-encrypt-certificates-renew
{% if matrix_nginx_proxy_enabled %}
20 5 * * * root {{ matrix_host_command_systemctl }} reload matrix-nginx-proxy.service
{% endif %}

@ -0,0 +1,6 @@
[Unit]
Description=Renews Let's Encrypt SSL certificates
[Service]
Type=oneshot
ExecStart={{ matrix_local_bin_path }}/matrix-ssl-lets-encrypt-certificates-renew

@ -0,0 +1,10 @@
[Unit]
Description=Renews Let's Encrypt SSL certificates periodically
[Timer]
Unit=matrix-ssl-lets-encrypt-certificates-renew.service
OnCalendar=Sunday *-*-* 05:00:00
RandomizedDelaySec=3h
[Install]
WantedBy=timers.target

@ -0,0 +1,6 @@
[Unit]
Description=Reloads matrix-nginx-proxy so that new SSL certificates can kick in
[Service]
Type=oneshot
ExecStart={{ matrix_host_command_systemctl }} reload matrix-nginx-proxy.service

@ -0,0 +1,10 @@
[Unit]
Description=Reloads matrix-nginx-proxy periodically so that new SSL certificates can kick in
[Timer]
Unit=matrix-ssl-nginx-proxy-reload.service
OnCalendar=Sunday *-*-* 13:00:00
RandomizedDelaySec=3h
[Install]
WantedBy=timers.target

@ -24,7 +24,6 @@ docker run \
{% if matrix_ssl_lets_encrypt_staging %} {% if matrix_ssl_lets_encrypt_staging %}
--staging \ --staging \
{% endif %} {% endif %}
--quiet \
--standalone \ --standalone \
--preferred-challenges http \ --preferred-challenges http \
--agree-tos \ --agree-tos \

@ -2,3 +2,17 @@
# Tells whether this role had executed or not. Toggled to `true` during runtime. # Tells whether this role had executed or not. Toggled to `true` during runtime.
matrix_nginx_proxy_role_executed: false matrix_nginx_proxy_role_executed: false
matrix_ssl_renewal_systemd_units_list:
- name: matrix-ssl-lets-encrypt-certificates-renew.service
applicable: "{{ matrix_ssl_retrieval_method == 'lets-encrypt' }}"
enableable: false
- name: matrix-ssl-lets-encrypt-certificates-renew.timer
applicable: "{{ matrix_ssl_retrieval_method == 'lets-encrypt' }}"
enableable: true
- name: matrix-ssl-nginx-proxy-reload.service
applicable: "{{ matrix_ssl_retrieval_method == 'lets-encrypt' and matrix_nginx_proxy_enabled|bool }}"
enableable: false
- name: matrix-ssl-nginx-proxy-reload.timer
applicable: "{{ matrix_ssl_retrieval_method == 'lets-encrypt' and matrix_nginx_proxy_enabled|bool }}"
enableable: true

Loading…
Cancel
Save