- name: fail if acme is not defined fail: msg: acme must be a mapping when: acme is not mapping - name: set acme_cfg set_fact: acme_cfg: "{{ acme_default_config | d({}) | combine(acme_config | d({}), recursive=true) | combine(acme2 | d({}), recursive=true) | combine(acme, recursive=true) }}" - name: determine host architecture include_tasks: tasks/get_host_arch.yml - name: create lego dirs file: path: "{{ item }}" state: directory mode: 0700 loop: - "{{ lego_dir }}" - "{{ lego_cert_dir }}" - name: get and extract latest lego version include_tasks: tasks/get_lastversion.yml vars: package: name: go-acme/lego location: github assets: yes asset_filter: "{{ 'linux_' ~ host_architecture ~ '.tar.gz$' }}" file: "{{ (lego_dir, 'last_version') | path_join }}" extract: "{{ lego_dir }}" - block: - name: remove unnecessary files file: path: "{{ (lego_dir, item) | path_join }}" state: absent loop: - LICENSE - CHANGELOG.md rescue: - meta: noop - name: set job name set_fact: lego_job_name: "{{ acme_cfg.domains[0] ~ '-' ~ acme_cfg.type }}" - name: set initial lego facts set_fact: lego_must_reissue: yes lego_renew_script: "{{ (lego_cert_dir, 'cron-' ~ lego_job_name ~ '.sh') | path_join }}" lego_lastrun_file: "{{ (lego_cert_dir, 'lastrun-' ~ lego_job_name) | path_join }}" - name: create custom renewal script file template: src: renewal.j2 dest: "{{ lego_renew_script }}" force: yes mode: 0500 lstrip_blocks: yes - name: set lego parameters set_fact: lego_params: "{{ [ ([] | zip_longest(acme_cfg.domains | d([]) | select() | map('quote'), fillvalue='--domains ') | map('join') | list), '--server ' ~ ((acme_cfg.endpoint_staging if acme_cfg.staging else acme_cfg.endpoint_prod) | quote), '--accept-tos', '--filename ' ~ (lego_job_name | quote), '--email ' ~ (acme_cfg.email | d(maintainer_email) | quote), '--key-type ' ~ (acme_cfg.type | quote), '--path ' ~ (lego_cert_dir | quote), '--dns acme-dns', '--dns.resolvers ' ~ (acme_cfg.resolver | d('1.1.1.1') | quote), '--dns.disable-cp' ] | flatten(levels=1) | select() | list | join(' ') }}" lego_renewal_params: "{{ [ (('--days ' ~ (acme_cfg.renew_at_days | quote)) if acme_cfg.renew_at_days is defined else ''), ('--reuse-key' if acme_cfg.reuse_key | d(false) == true else ''), ('--no-random-sleep' if acme_cfg.no_random_sleep | d(true) == true else ''), ('--renew-hook ' ~ (lego_renew_script | quote)) ] | flatten(levels=1) | select() | list | join(' ') }}" lego_preferred_chain: "{{ '--preferred-chain ' ~ (acme_cfg.preferred_chain | quote) if acme_cfg.preferred_chain is defined else '' }}" - name: set lego command facts set_fact: lego_full_command: "{{ (lego_dir, 'lego') | path_join }} {{ lego_params }} run {{ lego_preferred_chain }}" lego_renew_command: "{{ (lego_dir, 'lego') | path_join }} {{ lego_params }} renew {{ lego_preferred_chain }} {{ lego_renewal_params }}" - name: check if lastrun file exists stat: path: "{{ lego_lastrun_file }}" get_checksum: no get_mime: no register: result - block: - name: get lastrun file contents slurp: path: "{{ lego_lastrun_file }}" register: file_content no_log: yes - name: determine if cert should be reissued set_fact: lego_must_reissue: "{{ (file_content.content | b64decode) != lego_full_command }}" when: result.stat.exists - block: - name: issue cert with dns mode shell: cmd: "{{ lego_full_command }}" chdir: "{{ lego_dir }}" environment: ACME_DNS_API_BASE: "{{ acme_cfg.server }}" ACME_DNS_STORAGE_PATH: "{{ lego_accounts_file }}" register: result when: lego_must_reissue rescue: - pause: when: interactive | d(true) == true - name: retry issuing cert with dns mode shell: cmd: "{{ lego_full_command }}" chdir: "{{ lego_dir }}" environment: ACME_DNS_API_BASE: "{{ acme_cfg.server }}" ACME_DNS_STORAGE_PATH: "{{ lego_accounts_file }}" register: result - block: - name: save data to lastrun file copy: content: "{{ lego_full_command }}" dest: "{{ lego_lastrun_file }}" remote_src: yes - name: defer service restart debug: msg: deferring service restart changed_when: yes notify: "{{ acme_cfg.notify }}" when: acme_cfg.notify is defined - name: copy certificates to their intended locations copy: src: "{{ (lego_cert_dir, 'certificates', lego_job_name ~ '.' ~ item.src_ext) | path_join }}" dest: "{{ item.dest }}" remote_src: yes mode: 0600 owner: "{{ acme_cfg.owner | d(omit) }}" group: "{{ acme_cfg.group | d(omit) }}" when: item.dest != None loop: - { src_ext: 'crt', dest: "{{ acme_cfg.cert | d(None) }}" } - { src_ext: 'key', dest: "{{ acme_cfg.key | d(None) }}" } notify: "{{ acme_cfg.notify | d(omit) }}" when: lego_must_reissue - name: configure systemd service and timer block: - name: template systemd files template: src: "{{ item.src }}.j2" dest: "{{ ('/etc/systemd/system', item.dst) | path_join }}" force: yes lstrip_blocks: yes loop: - { src: 'lego_systemd', dst: 'lego.service' } - { src: 'lego_timer', dst: 'lego.timer' } notify: reload systemd daemons - name: enable lego timer systemd: name: lego.timer state: started enabled: yes when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' - name: configure crontab entry cron: name: "certificate renewal ({{ lego_job_name ~ ' on ' ~ acme_cfg.server }})" job: "ACME_DNS_API_BASE={{ acme_cfg.server | quote }} ; \ ACME_DNS_STORAGE_PATH={{ lego_accounts_file | quote }} ; \ cd {{ lego_dir | quote }} ; \ {{ lego_renew_command }}" hour: "{{ 4 | random(start=1, seed=(host_name ~ lego_job_name)) }}" minute: "{{ 59 | random(seed=(host_name ~ lego_job_name)) }}" when: ansible_distribution == 'Alpine'