├── .gitignore ├── CHANGES.md ├── .travis.yml ├── meta └── main.yml ├── tests └── test.yml ├── LICENSE ├── defaults └── main.yml ├── tasks └── main.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */**.DS_Store 3 | ._* 4 | .*.sw* 5 | *~ 6 | .idea/ 7 | .vscode/ 8 | *.retry 9 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### v1.1.0 4 | 5 | *Released: February 25th 2021* 6 | 7 | - Change `apt` to `package` so the role works with CentOS and potentially other non-Debian distros 8 | - Ensure certificate info task isn't run when Ansible is run in check mode 9 | 10 | ### v1.0.0 11 | 12 | *Released: September 29th 2018* 13 | 14 | - Initial release 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: "docker" 4 | 5 | env: 6 | - distro: "ubuntu1604" 7 | - distro: "ubuntu1804" 8 | - distro: "debian8" 9 | - distro: "debian9" 10 | 11 | script: 12 | # Download test shim. 13 | - wget -O ${PWD}/tests/test.sh https://gist.githubusercontent.com/nickjj/d12353b5b601e33cd62fda111359957a/raw 14 | - chmod +x ${PWD}/tests/test.sh 15 | 16 | # Run tests. 17 | - ${PWD}/tests/test.sh 18 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | galaxy_info: 4 | role_name: "acme_sh" 5 | author: "Nick Janetakis" 6 | description: "Install and auto-renew SSL certificates with Let's Encrypt using acme.sh." 7 | license: "license (MIT)" 8 | min_ansible_version: 2.5 9 | 10 | platforms: 11 | - name: "Ubuntu" 12 | versions: 13 | - "xenial" 14 | - "bionic" 15 | - name: "Debian" 16 | versions: 17 | - "jessie" 18 | - "stretch" 19 | 20 | galaxy_tags: 21 | - "acme" 22 | - "https" 23 | - "letsencrypt" 24 | - "networking" 25 | - "security" 26 | - "ssl" 27 | - "system" 28 | 29 | dependencies: [] 30 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: "all" 4 | become: True 5 | 6 | vars: 7 | acme_sh_become_user: "test" 8 | roles: 9 | - "role_under_test" 10 | 11 | pre_tasks: 12 | - name: Add test user 13 | user: 14 | name: "{{ acme_sh_become_user }}" 15 | shell: "/bin/bash" 16 | 17 | - name: Run the equivalent of "apt-get update" 18 | apt: 19 | update_cache: true 20 | changed_when: false 21 | 22 | post_tasks: 23 | - name: Ensure acme.sh was cloned 24 | command: test -d /usr/local/src/acme.sh 25 | register: result_cloned 26 | changed_when: result_cloned.rc != 0 27 | 28 | - name: Ensure acme.sh was installed 29 | command: ./acme.sh --version 30 | args: 31 | chdir: "~/.acme.sh" 32 | register: result_installed 33 | changed_when: result_installed.rc != 0 34 | become_user: "{{ acme_sh_become_user }}" 35 | 36 | - name: Ensure certificate installation path exists 37 | command: test -d /etc/ssl/ansible 38 | register: result_cert_installed_path 39 | changed_when: result_cert_installed_path.rc != 0 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Nick Janetakis nick.janetakis@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | acme_sh_become_user: "root" 4 | 5 | acme_sh_dependencies: ["cron", "git", "wget"] 6 | 7 | acme_sh_git_url: "https://github.com/acmesh-official/acme.sh" 8 | acme_sh_git_version: "master" 9 | acme_sh_git_update: False 10 | acme_sh_git_clone_dest: "/usr/local/src/acme.sh" 11 | 12 | acme_sh_upgrade: False 13 | acme_sh_uninstall: False 14 | 15 | acme_sh_account_email: "" 16 | 17 | acme_sh_renew_time_in_days: 30 18 | 19 | acme_sh_copy_certs_to_path: "/etc/ssl/ansible" 20 | 21 | acme_sh_list_domains: True 22 | 23 | acme_sh_default_staging: True 24 | 25 | acme_sh_default_force_issue: False 26 | acme_sh_default_force_renew: False 27 | 28 | acme_sh_default_debug: False 29 | 30 | acme_sh_default_dns_provider: "dns_dgon" 31 | acme_sh_default_dns_provider_api_keys: {} 32 | acme_sh_default_dns_sleep: 120 33 | 34 | acme_sh_default_extra_flags_issue: "" 35 | acme_sh_default_extra_flags_renew: "" 36 | acme_sh_default_extra_flags_install_cert: "" 37 | 38 | acme_sh_default_install_cert_reloadcmd: "sudo service nginx reload" 39 | 40 | acme_sh_default_issue_pre_hook: "" 41 | acme_sh_default_issue_post_hook: "" 42 | acme_sh_default_issue_renew_hook: "" 43 | 44 | acme_sh_default_remove: False 45 | 46 | acme_sh_domains: [] 47 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Install dependencies 4 | package: 5 | name: "{{ item }}" 6 | state: "present" 7 | loop: "{{ acme_sh_dependencies }}" 8 | when: not acme_sh_uninstall 9 | 10 | - name: Create git clone path 11 | file: 12 | path: "{{ acme_sh_git_clone_dest | dirname }}" 13 | state: "directory" 14 | owner: "{{ acme_sh_become_user }}" 15 | group: "{{ acme_sh_become_user }}" 16 | mode: "0755" 17 | when: not acme_sh_uninstall 18 | 19 | - name: Git clone https://github.com/acmesh-official/acme.sh 20 | git: 21 | repo: "{{ acme_sh_git_url }}" 22 | version: "{{ acme_sh_git_version }}" 23 | dest: "{{ acme_sh_git_clone_dest }}" 24 | update: "{{ acme_sh_git_update }}" 25 | when: not acme_sh_uninstall 26 | become_user: "{{ acme_sh_become_user }}" 27 | 28 | - name: Install acme.sh 29 | command: >- 30 | ./acme.sh --install --log 31 | --days {{ acme_sh_renew_time_in_days }} 32 | {{ "--accountemail " + acme_sh_account_email if acme_sh_account_email else "" }} 33 | args: 34 | chdir: "{{ acme_sh_git_clone_dest }}" 35 | creates: "~/.acme.sh/acme.sh" 36 | when: not acme_sh_uninstall 37 | become_user: "{{ acme_sh_become_user }}" 38 | 39 | - name: Determine if acme.sh is installed 40 | stat: 41 | path: "~/.acme.sh/acme.sh" 42 | register: is_acme_sh_installed 43 | become_user: "{{ acme_sh_become_user }}" 44 | 45 | - name: Upgrade acme.sh 46 | command: ./acme.sh --upgrade 47 | args: 48 | chdir: "~/.acme.sh" 49 | when: 50 | - acme_sh_upgrade 51 | - is_acme_sh_installed.stat.exists 52 | - not acme_sh_uninstall 53 | register: upgrade_result 54 | changed_when: upgrade_result.rc == 0 and "Upgrade success" in upgrade_result.stdout 55 | become_user: "{{ acme_sh_become_user }}" 56 | 57 | - name: Create certificate path 58 | file: 59 | path: "{{ acme_sh_copy_certs_to_path }}" 60 | state: "directory" 61 | owner: "{{ acme_sh_become_user }}" 62 | group: "{{ acme_sh_become_user }}" 63 | mode: "0755" 64 | when: not acme_sh_uninstall 65 | 66 | - name: Uninstall acme.sh and disable all certificate renewals 67 | command: ./acme.sh --uninstall 68 | args: 69 | chdir: "~/.acme.sh" 70 | when: 71 | - acme_sh_uninstall 72 | - is_acme_sh_installed.stat.exists 73 | become_user: "{{ acme_sh_become_user }}" 74 | 75 | - name: Remove acme.sh certificate(s) renewals from cron job 76 | command: >- 77 | ./acme.sh --remove -d {{ item.domains | first }} 78 | {{ "--debug" if item.debug | default(acme_sh_default_debug) else "" }} 79 | args: 80 | chdir: "~/.acme.sh" 81 | removes: "~/.acme.sh/{{ item.domains | first }}" 82 | loop: "{{ acme_sh_domains }}" 83 | when: 84 | - item.domains is defined and (item.domains is truthy) 85 | - item.remove is defined and item.remove 86 | - not acme_sh_uninstall 87 | become_user: "{{ acme_sh_become_user }}" 88 | register: remove_result 89 | 90 | - name: Remove acme.sh internal certificate files 91 | file: 92 | path: "~/.acme.sh/{{ item.domains | first }}" 93 | state: "absent" 94 | when: 95 | - item.domains is defined and (item.domains is truthy) 96 | - item.remove is defined and item.remove 97 | - not acme_sh_uninstall 98 | loop: "{{ acme_sh_domains }}" 99 | become_user: "{{ acme_sh_become_user }}" 100 | 101 | - name: Remove acme.sh installed certificate files 102 | file: 103 | path: "{{ acme_sh_copy_certs_to_path }}/{{ item.domains | first }}*" 104 | state: "absent" 105 | loop: "{{ acme_sh_domains }}" 106 | when: 107 | - item.domains is defined and (item.domains is truthy) 108 | - item.remove is defined and item.remove 109 | - not acme_sh_uninstall 110 | 111 | - name: Remove acme.sh's cloned source code, installation path and log files 112 | file: 113 | path: "{{ item }}" 114 | state: "absent" 115 | loop: 116 | - "{{ acme_sh_git_clone_dest }}" 117 | - "~/.acme.sh" 118 | when: 119 | - acme_sh_uninstall 120 | become_user: "{{ acme_sh_become_user }}" 121 | 122 | - name: Run custom acme.sh command 123 | command: ./acme.sh {{ item.custom_command }} 124 | args: 125 | chdir: "~/.acme.sh" 126 | environment: "{{ item.dns_provider_api_keys | default(acme_sh_default_dns_provider_api_keys) }}" 127 | loop: "{{ acme_sh_domains }}" 128 | when: 129 | - item.domains is defined and (item.domains is truthy) 130 | - (item.dns_provider | default(acme_sh_default_dns_provider)) is truthy 131 | - (item.dns_provider_api_keys | default(acme_sh_default_dns_provider_api_keys)) is truthy 132 | - item.custom_command is defined and item.custom_command 133 | - item.remove is undefined or not item.remove 134 | - not acme_sh_uninstall 135 | become_user: "{{ acme_sh_become_user }}" 136 | 137 | - name: Issue acme.sh certificate(s) (this will sleep for dns_sleep seconds) 138 | command: >- 139 | ./acme.sh --issue -d {{ item.domains | join(" -d ") }} 140 | --dns {{ item.dns_provider | default(acme_sh_default_dns_provider) }} 141 | --dnssleep {{ item.dns_sleep | default(acme_sh_default_dns_sleep) }} 142 | {{ "--force" if item.force_issue | default(acme_sh_default_force_issue) else "" }} 143 | {{ "--staging" if item.staging | default(acme_sh_default_staging) else "" }} 144 | {{ "--debug" if item.debug | default(acme_sh_default_debug) else "" }} 145 | {{ "--pre-hook " + '"' + item.issue_pre_hook | default(acme_sh_default_issue_pre_hook) + '"' if item.issue_pre_hook | default(acme_sh_default_issue_pre_hook) else "" }} 146 | {{ "--post-hook " + '"' + item.issue_post_hook | default(acme_sh_default_issue_post_hook) + '"' if item.issue_post_hook | default(acme_sh_default_issue_post_hook) else "" }} 147 | {{ "--renew-hook " + '"' + item.issue_renew_hook | default(acme_sh_default_issue_renew_hook) + '"' if item.issue_renew_hook | default(acme_sh_default_issue_renew_hook) else "" }} 148 | {{ item.extra_flags_issue | default(acme_sh_default_extra_flags_issue) }} 149 | args: 150 | chdir: "~/.acme.sh" 151 | environment: "{{ item.dns_provider_api_keys | default(acme_sh_default_dns_provider_api_keys) }}" 152 | loop: "{{ acme_sh_domains }}" 153 | when: 154 | - item.domains is defined and (item.domains is truthy) 155 | - (item.dns_provider | default(acme_sh_default_dns_provider)) is truthy 156 | - (item.dns_provider_api_keys | default(acme_sh_default_dns_provider_api_keys)) is truthy 157 | - item.force_renew is undefined or not item.force_renew 158 | - item.custom_command is undefined or not item.custom_command 159 | - item.remove is undefined or not item.remove 160 | - not acme_sh_uninstall 161 | become_user: "{{ acme_sh_become_user }}" 162 | register: issue_result 163 | changed_when: issue_result.rc == 0 and "Cert success" in issue_result.stdout 164 | failed_when: issue_result.rc != 0 and "Domains not changed" not in issue_result.stdout 165 | 166 | - name: Force renew acme.sh certificate(s) 167 | command: >- 168 | ./acme.sh --renew -d {{ item.domains | first }} --force 169 | {{ "--debug" if item.debug | default(acme_sh_default_debug) else "" }} 170 | {{ item.extra_flags_renew | default(acme_sh_default_extra_flags_renew) }} 171 | args: 172 | chdir: "~/.acme.sh" 173 | loop: "{{ acme_sh_domains }}" 174 | when: 175 | - item.domains is defined and (item.domains is truthy) 176 | - item.force_issue is undefined or not item.force_issue 177 | - item.force_renew is defined and item.force_renew 178 | - item.remove is undefined or not item.remove 179 | - not acme_sh_uninstall 180 | become_user: "{{ acme_sh_become_user }}" 181 | register: renew_result 182 | failed_when: renew_result.rc != 0 and "Reload error for" not in renew_result.stderr 183 | 184 | - name: Ensure installed certificates have correct user / group ownership 185 | file: 186 | path: "{{ acme_sh_copy_certs_to_path }}/{{ item.domains | first }}*" 187 | group: "{{ acme_sh_become_user }}" 188 | owner: "{{ acme_sh_become_user }}" 189 | mode: "0644" 190 | loop: 191 | - "{{ acme_sh_domains }}" 192 | when: 193 | - item.domains is defined and (item.domains is truthy) 194 | - item.custom_command is undefined or not item.custom_command 195 | - item.remove is undefined or not item.remove 196 | - not acme_sh_uninstall 197 | 198 | - name: Install acme.sh certificate(s) 199 | command: >- 200 | ./acme.sh --install-cert -d {{ item.domains | first }} 201 | --key-file {{ acme_sh_copy_certs_to_path }}/{{ item.domains | first }}.key 202 | --fullchain-file {{ acme_sh_copy_certs_to_path }}/{{ item.domains | first }}.pem 203 | --reloadcmd "{{ item.install_cert_reloadcmd | default(acme_sh_default_install_cert_reloadcmd) }}" 204 | {{ "--debug" if item.debug | default(acme_sh_default_debug) else "" }} 205 | {{ item.extra_flags_install_cert | default(acme_sh_default_extra_flags_install_cert) }} 206 | args: 207 | chdir: "~/.acme.sh" 208 | loop: "{{ acme_sh_domains }}" 209 | loop_control: 210 | index_var: domains_index 211 | when: 212 | - item.domains is defined and (item.domains is truthy) 213 | - item.custom_command is undefined or not item.custom_command 214 | - item.remove is undefined or not item.remove 215 | - not acme_sh_uninstall 216 | become_user: "{{ acme_sh_become_user }}" 217 | register: install_cert_result 218 | changed_when: issue_result.results[domains_index].changed or renew_result.results[domains_index].changed 219 | failed_when: install_cert_result.rc != 0 and "Reload error for" not in install_cert_result.stderr 220 | 221 | - name: Register acme.sh certificate information 222 | command: ./acme.sh --list 223 | args: 224 | chdir: "~/.acme.sh" 225 | when: acme_sh_list_domains and not acme_sh_uninstall 226 | changed_when: False 227 | register: list_domains 228 | become_user: "{{ acme_sh_become_user }}" 229 | 230 | - name: List acme.sh certificate information 231 | debug: 232 | msg: "{{ list_domains.stdout_lines }}" 233 | when: not ansible_check_mode and acme_sh_list_domains and not acme_sh_uninstall 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is ansible-acme-sh? [![Build Status](https://secure.travis-ci.org/nickjj/ansible-acme-sh.png)](http://travis-ci.org/nickjj/ansible-acme-sh) 2 | 3 | It is an [Ansible](http://www.ansible.com/home) role to: 4 | 5 | - Install acme.sh to issue, renew or remove Let's Encrypt based SSL certificates 6 | - Issue certificates for single, multiple or wildcard domains 7 | - Configure multiple domains through 1 certificate or separate certificates 8 | - Issue DNS based challenges using acme.sh's automated DNS API feature 9 | - Run custom acme.sh commands if the presets are not enough for you 10 | 11 | ## Why would you want to use this role? 12 | 13 | This role uses [acme.sh](https://github.com/acmesh-official/acme.sh) which is a self 14 | contained Bash script to handle all of the complexities of issuing and 15 | automatically renewing your SSL certificates. 16 | 17 | This role's goals are to be highly configurable but have enough sane defaults 18 | so that you can get going by supplying nothing more than a list of domain names, 19 | setting your DNS provider and supplying your DNS provider's API key. 20 | 21 | It's also idempotent for every task because that's the only way I roll! 22 | 23 | #### Why is DNS based challenges the only supported method? 24 | 25 | Having challenges done through DNS means you can set up your certificates before 26 | your web server or proxy is provisioned. It also means your web server doesn't 27 | need to know anything about how the ACME challenge works. All you have to do is 28 | reference the final certificates this role generates. 29 | 30 | Another perk is if you're running a web server inside of Docker, you might not 31 | have that up and running until after your server has been provisioned by Ansible. 32 | For example, it's common to set up git based deploys to kick off an app deploy. 33 | 34 | Also, it's nice using DNS challenges because DNS challenges are the only way to 35 | issue wildcard certificates using Let's Encrypt. Focusing efforts onto 1 solution 36 | that works with all certificate types seemed like the right move. 37 | 38 | With that said, I probably won't be supporting other modes such as standalone, 39 | webroot, nginx or Apache but nothing is set in stone. 40 | 41 | ## Supported platforms 42 | 43 | - Ubuntu 16.04 LTS (Xenial) 44 | - Ubuntu 18.04 LTS (Bionic) 45 | - Debian 8 (Jessie) 46 | - Debian 9 (Stretch) 47 | 48 | ## Role variables 49 | 50 | ``` 51 | # The user on the system that acme.sh will run as. Keep in mind this user 52 | # needs to already exist, this role will not create it. 53 | acme_sh_become_user: "root" 54 | 55 | # acme.sh package dependencies. The default values are for Debian / Ubuntu. 56 | # For CentOS and Fedora you can replace "cron" with "crond". 57 | acme_sh_dependencies: ["cron", "git", "wget"] 58 | 59 | # The acme.sh repo to clone. 60 | acme_sh_git_url: "https://github.com/acmesh-official/acme.sh" 61 | 62 | # The branch, tag or commit that will be cloned. 63 | acme_sh_git_version: "master" 64 | 65 | # By default if you were to clone this repo now and then 6 months from now you 66 | # clonged it again, it will stick with the old master version from 6 months ago. 67 | # If you want to pull the latest master version on every run, set this to True. 68 | acme_sh_git_update: False 69 | 70 | # Where will this repo get cloned to? 71 | acme_sh_git_clone_dest: "/usr/local/src/acme.sh" 72 | 73 | # When enabled, acme.sh will upgrade itself to the latest version which is 74 | # separate from updating the git repo. That's because acme.sh installs itself 75 | # with an installer after cloning the source code. 76 | # 77 | # Enabling it could be good to get the latest release which may have bug fixes 78 | # but keep in mind if you do this, you may get different results per run. I 79 | # recommend occasionally setting this to True but keeping it disabled usually. 80 | acme_sh_upgrade: False 81 | 82 | # When enabled the cloned source code, installation path, log files and renewal 83 | # cron jobs will be removed. 84 | # 85 | # Installed certificates will not be removed. If you want to remove the installed 86 | # certificates there is another option for that which we'll cover later. 87 | acme_sh_uninstall: False 88 | 89 | # When creating an initial Let's Encrypt account, you can optionally supply an 90 | # email address. By default this isn't set, but feel free to add your email 91 | # address in if you want. If you do set it, you'll get emailed when your 92 | # certificates are within 20 days of expiring. 93 | # 94 | # I highly recommend setting this because if all goes as planned you'll never 95 | # get emailed unless acme.sh malfunctioned and failed to renew a certificate. 96 | acme_sh_account_email: "" 97 | 98 | # Certificates will be renewed through an acme.sh managed cron job. By default 99 | # acme.sh uses 60 days for each renewal attempt, but I've chosen to go with 30 100 | # by default to give 1 extra attempt in case something unexpected happens. 101 | # 102 | # Certificates that don't need to be renewed will be skipped by acme.sh, so 103 | # it's all good. It's also worth mentioning this value cannot be > 60 days which 104 | # is a limit enforced by acme.sh, this role does not double check the value. 105 | acme_sh_renew_time_in_days: 30 106 | 107 | # The base path where certificates will be copied into. If you're familiar with 108 | # acme.sh, this is for the certificates generated with --install-cert. 109 | # 110 | # This is the final destination for your certificates and the user you've chosen 111 | # will need write access to this path. This path will end up being having its 112 | # owner:group set to the acme_sh_become_user's value. 113 | acme_sh_copy_certs_to_path: "/etc/ssl/ansible" 114 | 115 | # At the end of the run, an Ansible debug message will print out a list of 116 | # domains that have valid SSL certificates along with their expiration dates. 117 | # You can disable this by setting it to False. 118 | acme_sh_list_domains: True 119 | 120 | # When set to False, it will use the live Let's Encrypt servers, so please make 121 | # sure everything works with staging True or you may find yourself rate limited. 122 | # 123 | # It is worth mentioning you'll need to force issue a new certificate when 124 | # swiching between staging and live or vice versa. 125 | acme_sh_default_staging: True 126 | 127 | # When set to True, this will regenerate a new certificate even if your list of 128 | # domains didn't change. It's also used to set a new DNS provider and API keys. 129 | # 130 | # Be careful with this because you may get rate limited if on the live server. 131 | # Only consider using this to update your DNS provider. You should set it back 132 | # to False when you're done. 133 | acme_sh_default_force_issue: False 134 | 135 | # When set to True, this will regenerate a new certificate for an existing list 136 | # of certificates. This will not update your DNS provider or API keys. 137 | # 138 | # This could be useful to use if your certificates expired. You should set it 139 | # back to False when you're done. 140 | acme_sh_default_force_renew: False 141 | 142 | # When set to True, this will provide more detailed information to STDOUT. This 143 | # could be useful if you're testing the role in staging mode. 144 | acme_sh_default_debug: False 145 | 146 | # Which DNS provider should you use? 147 | # A list of supported providers can be found at: 148 | # https://github.com/acmesh-official/acme.sh/tree/master/dnsapi 149 | # As for getting the name to use, you can find that at the url above as well. 150 | # 151 | # It defaults to DigitalOcean. Make sure to include the dns_ part of the name, 152 | # but leave off the .sh file extension. 153 | acme_sh_default_dns_provider: "dns_dgon" 154 | 155 | # What are your DNS provider's API key(s)? 156 | # The key names to use can be found at: 157 | # https://github.com/acmesh-official/acme.sh/tree/master/dnsapi 158 | # 159 | # The API key can be created on your DNS provider's website. Some providers 160 | # require 1 key, while others require 2+. Just add them as key / value pairs here 161 | # without the "export ". 162 | # 163 | # For example if you were using DigitalOcean you would enter: 164 | # acme_sh_default_dns_provider_api_keys: 165 | # "DO_API_KEY": "THE_API_SECRET_TOKEN_FROM_THE_DO_DASHBOARD" 166 | acme_sh_default_dns_provider_api_keys: {} 167 | 168 | # How long should acme.sh sleep after attempting to set the TXT record to your 169 | # DNS records? Some DNS providers do not update as fast as others. 170 | # 171 | # 120 is the default value from acme.sh itself but keep in mind if you use 172 | # NameSilo, their DNS updates only propagate once per 15 minutes so you'll need 173 | # to set this value to 900 or you run the risk of getting a DNS NXDOMAIN error. 174 | # 175 | # I recommend keeping it set to 120 or higher if your DNS provider requires it. 176 | # 177 | # Although as an aside, I used 10 when testing this role against DigitalOcean 178 | # and it worked about 30 times in a row. Still, in production I would use 120 179 | # just to be safe because this 2 minute delay will only affect you on the first 180 | # Ansible run. After that it will be updated in the background through a cron job. 181 | acme_sh_default_dns_sleep: 120 182 | 183 | # When issuing new certificates, you can choose to add additional flags that 184 | # are not present here by default. Supply them just as you would on the command 185 | # line, such as "--help". 186 | acme_sh_default_extra_flags_issue: "" 187 | 188 | # When renewing certificates, you can choose to add additional flags that 189 | # are not present here by default. Supply them just as you would on the command 190 | # line, such as "--help". 191 | acme_sh_default_extra_flags_renew: "" 192 | 193 | # When installing certificates, you can choose to add additional flags that 194 | # are not present here by default. Supply them just as you would on the command 195 | # line, such as "--help". 196 | # 197 | # Installing is different than issuing and we'll cover that later. 198 | acme_sh_default_extra_flags_install_cert: "" 199 | 200 | # When a certificate is issued or renewed, acme.sh will attempt to run a command 201 | # of your choosing. This could be to restart or reload your web server or proxy. 202 | # 203 | # Keep in mind the user you set in acme_sh_become_user needs access rights to 204 | # sudo if you use sudo here, or if not, they need access rights to reload your 205 | # web server / proxy. 206 | # 207 | # For a Docker example, check the example section of this README. 208 | acme_sh_default_install_cert_reloadcmd: "sudo systemctl reload nginx" 209 | 210 | # If you need more fine grain control than the reloadcmd you can hook into the 211 | # life cycle of issuing or renewing a certificate. By default the following 3 212 | # options do nothing unless you fill them out. They are not needed for everything 213 | # to function as long as your reloadcmd works. 214 | # 215 | # When a certificate is issued or renewed, acme.sh will attempt to run a command 216 | # before attempting to issue a certificate. This can only be applied while 217 | # issuing a certificate but it will be saved and used for renewing as well. 218 | # 219 | # This will execute even if the certificate wasn't successfully issued / renewed. 220 | acme_sh_default_issue_pre_hook: "" 221 | 222 | # When a certificate is issued or renewed, acme.sh will attempt to run a command 223 | # after attempting to issue a certificate. This can only be applied while 224 | # issuing a certificate but it will be saved and used for renewing as well. 225 | # 226 | # This will execute even if the certificate wasn't successfully issued / renewed. 227 | acme_sh_default_issue_post_hook: "" 228 | 229 | # When a certificate is issued or renewed, acme.sh will attempt to run a command 230 | # after a certificate is successfully renewed. This can only be applied while 231 | # issuing a certificate but it will be saved and used for renewing as well. 232 | # 233 | # This will only execute if the certificate was successfully issued / renewed. 234 | acme_sh_default_issue_renew_hook: "" 235 | 236 | # When set to True, certificates will be removed and unset from being renewed 237 | # instead of being created and set for renewal. This will not uninstall acme.sh. 238 | acme_sh_default_remove: False 239 | 240 | # This list contains a list of domains, along with key / value pairs to 241 | # configure each set of domains individually. 242 | # 243 | # Here's an example with every available option documented, and a couple of real 244 | # examples will also be included in the example section of this README: 245 | acme_sh_domains: 246 | # A list of 1 or more domains, you can use ["example.com", "*.example.com"] or 247 | # ["*.example.com", "example.com"] for setting a wildcard certificate along with 248 | # the root domain certificate in the same file. The first domain in the list 249 | # will end up being used as the base file name for the certificate name. 250 | # 251 | # If you want separate files then create a new "domains:" item in the list. 252 | # - domains: ["example.com", "www.example.com", "admin.example.com"] 253 | # # Optionally override the default staging variable. This overall pattern lets 254 | # # you situationally override the defaults listed above for each domain list. 255 | # staging: False 256 | # # Optionally force issue new certificates. 257 | # force_issue: False 258 | # # Optionally force renew certificates. 259 | # force_renew: False 260 | # # Optionally turn on debug mode. 261 | # debug: True 262 | # # Optionally override the default DNS provider. 263 | # dns_provider: "dns_namesilo" 264 | # # Optionally override the default DNS API keys. 265 | # dns_provider_api_keys: 266 | # "Namesilo_Key": "THE_API_SECRET_TOKEN_FROM_THE_NAMESILO_DASHBOARD" 267 | # # Optionally override the default DNS sleep time. 268 | # dns_sleep: 900 269 | # # Optionally add extra flags to any of these 3 actions: 270 | # extra_flags_issue: "" 271 | # extra_flags_renew: "" 272 | # extra_flags_install_cert: "" 273 | # # Optionally set a different reload command. 274 | # install_cert_reloadcmd: "whoami" 275 | # # Optionally run commands during different points in the cert issue process: 276 | # extra_issue_pre_hook: "" 277 | # extra_issue_post_hook: "" 278 | # extra_issue_renew_hook: "" 279 | # # Optionally remove and disable the certificate. 280 | # remove: True 281 | ``` 282 | 283 | ## Example usage 284 | 285 | For the sake of this example let's assume you have a group called **app** and 286 | you have a typical `site.yml` file. 287 | 288 | To use this role edit your `site.yml` file to look something like this: 289 | 290 | ``` 291 | --- 292 | 293 | - name: Configure app server(s) 294 | hosts: "app" 295 | become: True 296 | 297 | roles: 298 | - { role: "nickjj.acme_sh", tags: ["acme_sh"] } 299 | ``` 300 | 301 | Here's a few examples. You can recreate this example on your end by opening or 302 | creating `group_vars/app.yml` which is located relative to your `inventory` 303 | directory and then making it look like this: 304 | 305 | ``` 306 | --- 307 | 308 | acme_sh_account_email: "you@example.com" 309 | 310 | # An example where a DNS provider has 2 keys for API access: 311 | acme_sh_default_dns_provider: "dns_cf" 312 | acme_sh_default_dns_provider_api_keys: 313 | "CF_Key": "THE_API_SECRET_TOKEN_FROM_THE_CLOUDFLARE_DASHBOARD" 314 | "CF_Email: "you@example.com" 315 | 316 | # Reloading nginx inside of a Docker container that is named "nginx". 317 | # If you are running nginx in a Docker container then you'll also need to volume 318 | # mount in your certificates, but I'm sure you knew that already! 319 | acme_sh_default_install_cert_reloadcmd: "docker exec nginx nginx -s reload" 320 | 321 | # --- Here's a few different acme_sh_domains examples -------------------------- 322 | # You would only need to supply one of these based on what you wanted to do! 323 | # ------------------------------------------------------------------------------ 324 | 325 | # 1 certificate file for all of the domains. 326 | acme_sh_domains: 327 | - domains: ["example.com", "www.example.com", "admin.example.com"] 328 | 329 | # Produces this on your server: 330 | # /etc/ssl/ansible/example.com.key (the private key) 331 | # /etc/ssl/ansible/example.com.pem (the full chain certificate) 332 | 333 | # ------------------------------------------------------------------------------ 334 | 335 | # 2 certificate files using the same domains as above. 336 | acme_sh_domains: 337 | - domains: ["example.com", "www.example.com"] 338 | - domains: ["admin.example.com"] 339 | 340 | # Produces this on your server: 341 | # /etc/ssl/ansible/example.com.key (the private key) 342 | # /etc/ssl/ansible/example.com.pem (the full chain certificate) 343 | # /etc/ssl/ansible/admin.example.com.key (the private key) 344 | # /etc/ssl/ansible/admin.example.com.pem (the full chain certificate) 345 | 346 | # ------------------------------------------------------------------------------ 347 | 348 | # 2 certificate files using the same example but the admin certificate will get 349 | # removed and disabled. 350 | acme_sh_domains: 351 | - domains: ["example.com", "www.example.com"] 352 | - domains: ["admin.example.com"] 353 | remove: True 354 | 355 | # Produces this on your server: 356 | # /etc/ssl/ansible/example.com.key (the private key) 357 | # /etc/ssl/ansible/example.com.pem (the full chain certificate) 358 | 359 | # ------------------------------------------------------------------------------ 360 | 361 | # 2 certificate files using the same example but switching from staging to live 362 | # on admin.example.com (but remember to remove force_issue after it runs once). 363 | acme_sh_domains: 364 | - domains: ["example.com", "www.example.com"] 365 | - domains: ["admin.example.com"] 366 | staging: False 367 | force_issue: True 368 | 369 | # ------------------------------------------------------------------------------ 370 | 371 | # 2 certificate files using the same example but forcing a renew on 372 | # admin.example.com (let's say a catastrophic error happened and the cert expired). 373 | acme_sh_domains: 374 | - domains: ["example.com", "www.example.com"] 375 | - domains: ["admin.example.com"] 376 | force_renew: True 377 | ``` 378 | 379 | *If you're looking for an Ansible role to create users, then check out my 380 | [user role](https://github.com/nickjj/ansible-user)*. 381 | 382 | Now you would run `ansible-playbook -i inventory/hosts site.yml -t acme_sh`. 383 | 384 | ## Installation 385 | 386 | `$ ansible-galaxy install nickjj.acme_sh` 387 | 388 | ## Ansible Galaxy 389 | 390 | You can find it on the official 391 | [Ansible Galaxy](https://galaxy.ansible.com/nickjj/acme_sh/) if you want to 392 | rate it. 393 | 394 | ## License 395 | 396 | MIT 397 | --------------------------------------------------------------------------------