commit cfe7db8716ae95c9526d09b684f8e85b30af71dc Author: sujiba Date: Tue Sep 17 00:45:38 2024 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..528cc86 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# acme.sh + +Thanks to [gmk/ansible-role-acme_sh](https://codeberg.org/gmk/ansible-role-acme_sh) + +Install acme.sh with ansible. + +## Configuration +``` +# netcup settings +netcup_user: ID +netcup_api_key: KEY +netcup_api_password: PASSWORD + +# certificate settings +acmesh_email: hostmaster@example.com +acme_domains: + - domain: example.com + keylength: ec-384 + reloadcmd: "sudo systemctl reload nginx.service" + dnssleep: 900 + - domain: example.org + san: + - www.example.com + - host.example.com + keylength: 3072 + staging: true + key_group: www-data + key_mode: "0640" +``` \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..a0ca2e6 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,26 @@ +--- +# defaults file for acme.sh +# netcup settings +# netcup_user: ID +# netcup_api_key: KEY +# netcup_api_password: PASSWORD + +# certificate settings +# Add wildcards as san +# tls_cert_path: /etc/ssl/acme +# acmesh_email: hostmaster@example.com +# acme_domains: +# - domain: example.com +# san: +# - "*.example.com" +# keylength: ec-384 +# reloadcmd: "sudo systemctl reload nginx.service" +# dnssleep: 900 +# - domain: example.org +# san: +# - www.example.com +# - host.example.com +# keylength: 3072 +# staging: true +# key_group: www-data +# key_mode: "0640" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..8f2fe4a --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,11 @@ +--- +# handlers file for acme.sh +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart acme_sh systemd timer + ansible.builtin.systemd: + name: acme_sh.timer + scope: system + state: restarted diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..c572acc --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,52 @@ +galaxy_info: + author: your name + description: your role description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: license (GPL-2.0-or-later, MIT, etc) + + min_ansible_version: 2.1 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..d59777b --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,213 @@ +--- +# tasks file for acme.sh +- name: Create system acme group + become: true + ansible.builtin.group: + name: "{{ acme_group }}" + state: present + system: true + +- name: Create system acme user + become: true + ansible.builtin.user: + name: "{{ acme_user }}" + group: "{{ acme_group }}" + home: "{{ acme_home }}" + shell: "{{ acme_shell }}" + state: present + system: true + create_home: true + skeleton: false + +- name: Ensure directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + owner: "{{ acme_user }}" + group: "{{ acme_group }}" + loop: + - "{{ acme_home }}" + - "{{ acmesh_config_path }}" + - "{{ acmesh_install_path }}" + - "{{ acmesh_cert_path }}" + +- name: Check acme.sh installation + ansible.builtin.stat: + path: "{{ acmesh_config_path }}/account.conf" + register: stat_account_conf + +- name: Download and install acme.sh + when: not stat_account_conf.stat.exists + become: true + become_method: su + become_user: "{{ acme_user }}" + become_flags: '-s /bin/sh' + become_exe: "sudo su" + block: + - name: Create temporary directory for git repository + ansible.builtin.tempfile: + state: directory + prefix: acmesh_git_ + register: acme_tmp_repo + changed_when: false + + - name: Temporary clone acme.sh git repository + ansible.builtin.git: + dest: "{{ acme_tmp_repo.path }}" + clone: true + depth: 1 + repo: "{{ acmesh_repository }}" + version: "{{ acmesh_version }}" + changed_when: false + + - name: Make install script executable + ansible.builtin.file: + path: "{{ acme_tmp_repo.path }}/acme.sh" + mode: "0744" + owner: acme + group: acme + + - name: Install acme.sh + ansible.builtin.command: + cmd: > + "{{ acme_tmp_repo.path }}/acme.sh" + --install + --home "{{ acmesh_install_path }}" + --config-home "{{ acmesh_config_path }}" + --cert-home "{{ acmesh_cert_path }}" + --accountemail "{{ acmesh_email | mandatory }}" + --nocron + chdir: "{{ acme_tmp_repo.path }}" + creates: "{{ acmesh_config_path }}/account.conf" + + always: + - name: Delete temporary git clone + ansible.builtin.file: + path: "{{ acme_tmp_repo.path }}" + state: absent + changed_when: false + +- name: "Set default CA [letsencrypt]" + ansible.builtin.lineinfile: + path: "{{ acmesh_config_path }}/account.conf" + regexp: '^DEFAULT_ACME_SERVER=' + line: "DEFAULT_ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'" + +- name: Create sudo config file + ansible.builtin.file: + path: "/etc/sudoers.d/60_user_{{ acme_user }}" + state: touch + mode: "0440" + owner: root + group: root + access_time: preserve + modification_time: preserve + +- name: Configure sudo permissions + become: true + ansible.builtin.lineinfile: + path: "/etc/sudoers.d/60_user_{{ acme_user }}" + line: "{{ acme_user }} ALL=(ALL:ALL) NOPASSWD: /usr/bin/{{ item.reloadcmd | regex_replace('^sudo (.*)$', '\\1') }}" + state: present + validate: /usr/sbin/visudo -csf %s + loop: "{{ acme_domains }}" + when: acme_domains is defined + +- name: Ensure directories exist + become: true + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + owner: "{{ acme_user }}" + group: "{{ acme_group }}" + loop: + - "{{ tls_cert_path }}" + +- name: Issue and install certificate + when: acme_domains is defined + become: true + become_method: su + become_user: "{{ acme_user }}" + become_flags: '-s /bin/sh' + become_exe: "sudo su" + block: + - name: Issue certificate with Netcup + ansible.builtin.command: + cmd: > + "{{ acmesh_install_path }}/acme.sh" + --issue + --dns dns_netcup + -d "{{ item.domain }}" + {% if item.san is defined %} + {% for san in item.san %} + -d "{{ san }}" + {% endfor %} + {% endif %} + {% if item.keylength is defined %} + --keylength "{{ item.keylength }}" + {% endif %} + {% if item.staging is defined %} + --staging + {% endif %} + {% if item.dnssleep is defined %} + --dnssleep {{ item.dnssleep }} + {% endif %} + creates: "{{ acmesh_cert_path }}/{{ item.domain }}_ecc/{{ item.domain }}.cer" + environment: + LE_WORKING_DIR: "{{ acmesh_install_path }}" + LE_CONFIG_HOME: "{{ acmesh_config_path }}" + NC_CID: "{{ netcup_user | mandatory }}" + NC_Apikey: "{{ netcup_api_key | mandatory }}" + NC_Apipw: "{{ netcup_api_password | mandatory }}" + loop: "{{ acme_domains }}" + + - name: Install certificates + ansible.builtin.command: + cmd: > + "{{ acmesh_install_path }}/acme.sh" + --installcert + -d "{{ item.domain }}" + {% if 'ec' in item.keylength %} + --ecc + {% endif %} + --cert-file "{{ tls_cert_path }}/{{ item.domain }}.crt" + --key-file "{{ tls_cert_path }}/{{ item.domain }}.key" + --ca-file "{{ tls_cert_path }}/{{ item.domain }}.ca" + --fullchain-file "{{ tls_cert_path }}/{{ item.domain }}.fullchain" + {% if item.reloadcmd is defined %} + --reloadcmd '{{ item.reloadcmd }}' + {% endif %} + creates: "{{ tls_cert_path }}/{{ item.domain }}.crt" + environment: + LE_WORKING_DIR: "{{ acmesh_install_path }}" + LE_CONFIG_HOME: "{{ acmesh_config_path }}" + loop: "{{ acme_domains }}" + +- name: Install systemd service + ansible.builtin.template: + src: acme_sh.service.j2 + dest: /etc/systemd/system/acme_sh.service + owner: root + group: root + mode: "0644" + notify: Reload systemd daemon + +- name: Install systemd timer + ansible.builtin.template: + src: acme_sh.timer.j2 + dest: /etc/systemd/system/acme_sh.timer + owner: root + group: root + mode: "0644" + notify: + - Reload systemd daemon + - Restart acme_sh systemd timer + +- name: Enable systemd timer + ansible.builtin.systemd: + name: acme_sh.timer + enabled: true + scope: system + state: started diff --git a/templates/acme_sh.service.j2 b/templates/acme_sh.service.j2 new file mode 100644 index 0000000..386fdc1 --- /dev/null +++ b/templates/acme_sh.service.j2 @@ -0,0 +1,14 @@ +#jinja2: lstrip_blocks: True +# {{ ansible_managed }} + +[Unit] +Description=Renew certificates using acme.sh +After=network-online.target + +[Service] +Type=oneshot +ExecStart={{ acmesh_install_path }}/acme.sh --cron --home {{ acmesh_install_path }} --config-home {{ acmesh_config_path }} +User={{ acme_user }} +Group={{ acme_group }} + +SuccessExitStatus=0 2 \ No newline at end of file diff --git a/templates/acme_sh.timer.j2 b/templates/acme_sh.timer.j2 new file mode 100644 index 0000000..69fa860 --- /dev/null +++ b/templates/acme_sh.timer.j2 @@ -0,0 +1,13 @@ +#jinja2: lstrip_blocks: True +# {{ ansible_managed }} + +[Unit] +Description=Daily Renew certificates using acme.sh + +[Timer] +OnCalendar=*-*-* 04:00 +RandomizedDelaySec=1h +Persistent=true + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..f27ed0f --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,12 @@ +--- +acme_user: acme +acme_group: acme +acme_home: /opt/acme +acme_shell: /usr/sbin/nologin + +acmesh_install_path: "{{ acme_home }}/acme.sh" +acmesh_config_path: "{{ acme_home }}/config" +acmesh_cert_path: "{{ acme_home }}/certs" + +acmesh_repository: https://github.com/acmesh-official/acme.sh.git +acmesh_version: 3.0.7