├── Dockerfile ├── LICENSE ├── README.md ├── build ├── certbot.sh ├── docker-compose-full-stack.yml ├── docker-compose-stack.yml ├── renewAndSendToProxy.sh ├── renewcron ├── run ├── servicestart └── supervisord.conf /Dockerfile: -------------------------------------------------------------------------------- 1 | #use 18.04 lts 2 | FROM ubuntu:18.04 3 | 4 | #set default env variables 5 | ENV DEBIAN_FRONTEND=noninteractive \ 6 | CERTBOT_EMAIL="" \ 7 | PROXY_ADDRESS="proxy" \ 8 | CERTBOT_CRON_RENEW="('0 3 * * *' '0 15 * * *')" \ 9 | PATH="$PATH:/root" 10 | 11 | # http://stackoverflow.com/questions/33548530/envsubst-command-getting-stuck-in-a-container 12 | RUN apt-get update && \ 13 | apt-get -y install cron supervisor curl certbot && \ 14 | apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 15 | 16 | # Add supervisord.conf 17 | ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf 18 | 19 | # Add certbot and make it executable 20 | ADD certbot.sh /root/certbot.sh 21 | RUN chmod u+x /root/certbot.sh 22 | 23 | ADD renewAndSendToProxy.sh /root/renewAndSendToProxy.sh 24 | RUN chmod u+x /root/renewAndSendToProxy.sh 25 | 26 | RUN ln -sf /proc/1/fd/1 /var/log/dockeroutput.log 27 | 28 | # Add symbolic link in cron.daily directory without ending (important!) 29 | ADD renewcron /etc/cron.d/renewcron 30 | RUN chmod u+x /etc/cron.d/renewcron 31 | 32 | ADD servicestart /root/servicestart 33 | RUN chmod u+x /root/servicestart 34 | 35 | # Run the command on container startup 36 | CMD ["/root/servicestart"] 37 | 38 | EXPOSE 80 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael Hamburger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Docker Flow: Let's Encrypt 2 | [![GitHub release](https://img.shields.io/github/release/hamburml/docker-flow-letsencrypt.svg)]() 3 | [![license](https://img.shields.io/github/license/hamburml/docker-flow-letsencrypt.svg)]() 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/hamburml/docker-flow-letsencrypt.svg)]() 5 | ================== 6 | 7 | * [Introduction](#introduction) 8 | * [How does it work](#how-does-it-work) 9 | * [Usage](#usage) 10 | * [Feedback and Contribution](#feedback) 11 | 12 | ## Introduction 13 | 14 | This project is compatible with Viktor Farcic's [Docker Flow: Proxy](https://github.com/docker-flow/docker-flow-proxy) and [Docker Flow: Swarm Listener](https://github.com/docker-flow/docker-flow-swarm-listener). 15 | It uses certbot-auto to create and renew ssl certificates from Let’s Encrypt for your domains and stores them inside /etc/letsencrypt thus it requires a persistant storage. 16 | You can bind a folder from the host and use a constraint to make sure the companion service always runs on the same host or use storage plugins such as [rexray](https://github.com/codedellemc/rexray) to allow data to follow your container on other nodes. 17 | 18 | The service setups a cron which runs by default two times a day (03:00 and 15:00 UTC) and calls [renewAndSendToProxy](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/renewAndSendToProxy.sh). You can overwrite these cron behavior with the correct environment variables. It runs certbot-auto renew and uploads the certificates to the running [Docker Flow: Proxy](https://github.com/vfarcic/docker-flow-proxy) service. 19 | 20 | You can find this project also on [Docker Hub](https://hub.docker.com/r/hamburml/docker-flow-letsencrypt/). 21 | 22 | ## How does it work 23 | 24 | This docker image uses certbot-auto, curl and cron to create and renew your Let’s Encrypt certificates. 25 | Through environment variables you set the domains certbot-auto should create certificates for, which e-mail is used by Let’s Encrypt when you lose the account and want to get it back, the cronjob starting times and the dns-name of [Docker Flow: Proxy](https://github.com/vfarcic/docker-flow-proxy). 26 | 27 | When the image starts, the [certbot.sh](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/certbot.sh) script runs and creates/renews the certificates and creates /etc/cron.d/renewcron. The script also runs [renewAndSendToProxy.sh](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/renewAndSendToProxy.sh) which combines the cert.pem, chain.pem and privkey.pem to a domainname.combined.pem file and uploads your cert via curl to your proxy. 28 | 29 | [renewAndSendToProxy.sh](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/renewAndSendToProxy.sh) also calls certbot-auto renew because this script is run by default two times a day (03:00 and 15:00 UTC) via /etc/cron.d/renewcron. You can overwrite this behavior by changing CERTBOT_CRON_RENEW environment variable. This script also creates backups of the /etc/letsencrypt folder which are stored in /etc/letsencrypt/backup. Don't worry, the backup folder is excluded from the tar command. 30 | 31 | As you can see the output is piped into /var/log/dockeroutput.log. This file is created in the [Dockerfile](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/Dockerfile) and just redirects directly to the docker logs output. The logs are also colorized so that you are able to find the important information without hesitation. 32 | 33 | If you only want to test this image you should add ```-e CERTBOTMODE="staging"``` when creating the service to use the staging mode of Let’s Encrypt. Remember that the certificate is not trusted so you will get a warning inside your browser. 34 | 35 | Certbot-auto is called with ```--rsa-key-size 4096 --redirect --hsts --staple-ocsp``` for improved security. 36 | 37 | ## Usage 38 | 39 | ### [Build](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/build) 40 | ``` 41 | docker build -t hamburml/docker-flow-letsencrypt . 42 | ``` 43 | 44 | ### [Run](https://github.com/hamburml/docker-flow-letsencrypt/blob/master/run) 45 | 46 | Attention! If you use local storage, create the `/etc/letsencrypt` folder before you start the service. 47 | 48 | ``` 49 | docker service create --name letsencrypt-companion \ 50 | --label com.df.notify=true \ 51 | --label com.df.distribute=true \ 52 | --label com.df.servicePath=/.well-known/acme-challenge \ 53 | --label com.df.port=80 \ 54 | -e DOMAIN_1="('haembi.de' 'www.haembi.de' 'blog.haembi.de')"\ 55 | -e DOMAIN_2="('michael-hamburger.de' 'www.michael-hamburger.de' 'blog.michael-hamburger.de')"\ 56 | -e CERTBOT_EMAIL="your.mail@mail.de" \ 57 | -e PROXY_ADDRESS="proxy" \ 58 | -e CERTBOT_CRON_RENEW="('0 3 * * *' '0 15 * * *')"\ 59 | --network proxy \ 60 | --mount type=bind,source=/etc/letsencrypt,destination=/etc/letsencrypt \ 61 | --constraint 'node.id==' \ 62 | --replicas 1 hamburml/docker-flow-letsencrypt:latest 63 | ``` 64 | 65 | The `aclName` label is optional. However it helps the proxy to order the ACls and make sure that the companion rule is above other service rules since the rules are added sequentially so ACME verification works. 66 | 67 | You may also use a stack file to deploy the letsencrypt companion. The content of the [docker-compose-stack.yml](docker-compose-stack.yml) file is: 68 | 69 | ``` 70 | version: "3" 71 | 72 | services: 73 | 74 | # Let's Encrypt Companion 75 | letsencrypt-companion: 76 | image: hamburml/docker-flow-letsencrypt:latest 77 | networks: 78 | - proxy 79 | environment: 80 | - DOMAIN_1=('haembi.de' 'www.haembi.de' 'blog.haembi.de') 81 | - DOMAIN_2=('michael-hamburger.de' 'www.michael-hamburger.de' 'blog.michael-hamburger.de') 82 | - CERTBOT_EMAIL=your.mail@mail.de 83 | - PROXY_ADDRESS=proxy 84 | - CERTBOT_CRON_RENEW=('0 3 * * *' '0 15 * * *') 85 | volumes: 86 | - /etc/letsencrypt:/etc/letsencrypt 87 | deploy: 88 | labels: 89 | - com.df.servicePath=/.well-known/acme-challenge 90 | - com.df.notify=true 91 | - com.df.distribute=true 92 | - com.df.port=80 93 | placement: 94 | constraints: [node.id == ] 95 | replicas: 1 96 | 97 | networks: 98 | proxy: 99 | external: true 100 | ``` 101 | 102 | As a full stack example with Viktor's proxy and listener, check the [docker-compose-full-stack.yml](docker-compose-full-stack.yml) file. 103 | 104 | You should always start the service on the same docker host. You achieve this by setting to the id of the docker host on which the service should run. The nodeId can be get via ```docker node ls```. 105 | You must not scale the service to two, this wouldn't make any sense! Only one instance of this companion should run. 106 | The certificates are only renewed when they are 60 days old or older. This is standard certbot-auto behavior. But the certificates will be renewed when you add subdomains to the domain-list! If you want to change the number of times certbot-auto renew and the publish script is called you need to change CERTBOT_CRON_RENEW. The syntax is described [here](http://www.adminschoice.com/crontab-quick-reference). 107 | 108 | Important: The first domain must always be the domain without any subdomains. That makes the folder-structure regular. 109 | 110 | We need to obey Let’s Encrypt’s rate limits! https://letsencrypt.org/docs/rate-limits/ 111 | 112 | ### Docker Logs 113 | 114 | You can see the progress of the running service through the logs. 115 | 116 | ``` 117 | root@server # docker logs letsencrypt-companion... -f 118 | 119 | Docker Flow: Let's Encrypt started 120 | We will use your.email@mail.de for certificate registration with certbot. This e-mail is used by Let's Encrypt when you lose the account and want to get it back. 121 | 122 | Use certbot --standalone --non-interactive --expand --keep-until-expiring --agree-tos --standalone-supported-challenges http-01 --rsa-key-size 4096 --redirect --hsts --staple-ocsp -d domain1.de -d www.domain1.de -d subdomain1.domain1.de 123 | ------------------------------------------------------------------------------- 124 | Certificate not yet due for renewal; no action taken. 125 | ------------------------------------------------------------------------------- 126 | (removed some entries...) 127 | Docker Flow: Proxy DNS-Name: proxy 128 | current folder name is: domain1.de 129 | concat certificates for domain1.de 130 | generated domain1.de.combined.pem 131 | transmit domain1.de.combined.pem to proxy 132 | % Total % Received % Xferd Average Speed Time Time Time Current 133 | Dload Upload Total Spent Left Speed 134 | 100 7114 0 0 100 7114 0 108k --:--:-- --:--:-- --:--:-- 108k 135 | HTTP/1.1 100 Continue 136 | 137 | HTTP/1.1 200 OK 138 | Date: Tue, 10 Jan 2017 18:54:43 GMT 139 | Content-Length: 0 140 | Content-Type: text/plain; charset=utf-8 141 | 142 | proxy received domain1.de.combined.pem 143 | (removed some entries...) 144 | Thanks for using Docker Flow: Let's Encrypt and have a nice day! 145 | 146 | Starting supervisord (which starts and monitors cron) 147 | (removed some entries...) 148 | ``` 149 | 150 | When you restart the service and the certificates can't be renewed the logs will show this also. 151 | 152 | ``` 153 | ... 154 | ------------------------------------------------------------------------------- 155 | Certificate not yet due for renewal; no action taken. 156 | ------------------------------------------------------------------------------- 157 | ... 158 | ``` 159 | 160 | ## Feedback 161 | 162 | Thanks for using docker-flow-letsencrypt. If you have problems or some ideas how this can be made better feel free to create a new issue. Thanks to Viktor Farcic for his help and docker flow :) 163 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | docker build --rm -t hamburml/docker-flow-letsencrypt . 2 | docker push hamburml/docker-flow-letsencrypt:latest 3 | -------------------------------------------------------------------------------- /certbot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #colors 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | NC='\033[0m' # No Color 7 | 8 | #maximum number of retries 9 | MAXRETRIES=5 10 | 11 | #timeout 12 | TIMEOUT=5 13 | 14 | printf "${GREEN}Docker Flow: Let's Encrypt started${NC}\n"; 15 | printf "We will use $CERTBOT_EMAIL for certificate registration with certbot. This e-mail is used by Let's Encrypt when you lose the account and want to get it back.\n"; 16 | 17 | #common arguments 18 | args=("--no-self-upgrade" "--no-bootstrap" "--standalone" "--non-interactive" "--expand" "--keep-until-expiring" "--email" "$CERTBOT_EMAIL" "--agree-tos" "--preferred-challenges" "http-01" "--rsa-key-size" "4096" "--redirect" "--hsts" "--staple-ocsp") 19 | 20 | #if we are in a staging enviroment append the staging argument, the staging argument 21 | #was previously set to an empty string if the staging enviroment was not used 22 | #but this confuses cert-auto and should hence not be used 23 | if [ "$CERTBOTMODE" ]; then 24 | printf "${RED}Staging environment of Let's Encrypt is activated! The generated certificates won't be trusted. But you will not reach Let’s Encrypt's rate limits.${NC}\n"; 25 | args+=("--staging"); 26 | fi 27 | 28 | #we need to be careful and don't reach the rate limits of Let's Encrypt https://letsencrypt.org/docs/rate-limits/ 29 | #Let's Encrypt has a certificates per registered domain (20 per week) and a names per certificate (100 subdomains) limit 30 | #so we should create ONE certificiates for a certain domain and add all their subdomains (max 100!) 31 | 32 | for var in $(env | grep -P 'DOMAIN_\d+' | sed -e 's/=.*//'); do 33 | cur_domains=${!var}; 34 | declare -a arr=$cur_domains; 35 | 36 | DOMAINDIRECTORY="/etc/letsencrypt/live/${arr[0]}"; 37 | dom=""; 38 | for i in "${arr[@]}" 39 | do 40 | let validated=tries=0 41 | until [ $tries -ge $MAXRETRIES ] 42 | do 43 | tries=$[$tries+1] 44 | certbot certonly --dry-run "${args[@]}" -d "$i" | grep -q 'The dry run was successful.' 45 | if [ $? -eq 0 ]; then 46 | validated=1 47 | break 48 | else 49 | if [ $tries -eq $MAXRETRIES ]; then 50 | printf "${RED}Unable to verify domain ownership after ${tries} attempts.${NC}\n" 51 | else 52 | printf "${RED}Unable to verify domain ownership, we try again in ${TIMEOUT} seconds.${NC}\n" 53 | sleep $TIMEOUT 54 | fi 55 | fi 56 | 57 | done 58 | echo "Validated is $validated" 59 | if [ $validated -eq 1 ]; then 60 | printf "Domain $i successfully validated\n" 61 | dom="$dom -d $i" 62 | fi 63 | done 64 | 65 | #only if we have successfully validated at least a single domain we have to continue 66 | if [ -n "$dom" ]; then 67 | # check if DOMAINDIRECTORY exists, if it exists use --cert-name to prevent 0001 0002 0003 folders 68 | if [ -d "$DOMAINDIRECTORY" ]; then 69 | printf "\nUse certbot certonly %s --cert-name %s\n" "${args[*]}" "${arr[0]}"; 70 | certbot certonly "${args[@]}" --cert-name "${arr[0]}" $dom 71 | else 72 | printf "\nUse certbot certonly %s\n" "${args[*]}"; 73 | certbot certonly "${args[@]}" $dom 74 | fi 75 | fi 76 | 77 | done 78 | 79 | #prepare renewcron 80 | if [ "$CERTBOTMODE" ]; then 81 | printf "SHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\nPROXY_ADDRESS=$PROXY_ADDRESS\nCERTBOTMODE=$CERTBOTMODE\n" > /etc/cron.d/renewcron 82 | else 83 | printf "SHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\nPROXY_ADDRESS=$PROXY_ADDRESS\n" > /etc/cron.d/renewcron 84 | fi 85 | 86 | 87 | declare -a arr=$CERTBOT_CRON_RENEW; 88 | for i in "${arr[@]}" 89 | do 90 | printf "$i root /root/renewAndSendToProxy.sh > /var/log/dockeroutput.log\n" >> /etc/cron.d/renewcron 91 | done 92 | 93 | printf "\n" >> /etc/cron.d/renewcron 94 | 95 | #run renewAndSendToProxy script which calls certbot renew (yeah, certbot will be run again but this isn't a problem. In fact this script is also run via cron and therefore we must call certbot renew), concatenates the certificates and sends them to the proxy 96 | /root/renewAndSendToProxy.sh 97 | -------------------------------------------------------------------------------- /docker-compose-full-stack.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | # From http://proxy.dockerflow.com/swarm-mode-stack/ 6 | proxy: 7 | image: dockerflow/docker-flow-proxy 8 | ports: 9 | - 80:80 10 | - 443:443 11 | networks: 12 | - proxy 13 | environment: 14 | - LISTENER_ADDRESS=swarm-listener 15 | - MODE=swarm 16 | deploy: 17 | replicas: 2 18 | 19 | swarm-listener: 20 | image: dockerflow/docker-flow-swarm-listener 21 | networks: 22 | - proxy 23 | volumes: 24 | - /var/run/docker.sock:/var/run/docker.sock 25 | environment: 26 | - DF_NOTIFY_CREATE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/reconfigure 27 | - DF_NOTIFY_REMOVE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/remove 28 | deploy: 29 | placement: 30 | constraints: [node.role == manager] 31 | 32 | # Let's Encrypt Companion 33 | letsencrypt-companion: 34 | image: hamburml/docker-flow-letsencrypt:latest 35 | networks: 36 | - proxy 37 | environment: 38 | - DOMAIN_1=('haembi.de' 'www.haembi.de' 'blog.haembi.de') 39 | - DOMAIN_2=('michael-hamburger.de' 'www.michael-hamburger.de' 'blog.michael-hamburger.de') 40 | - CERTBOT_EMAIL=your.mail@mail.de 41 | - PROXY_ADDRESS=proxy 42 | - CERTBOT_CRON_RENEW=('0 3 * * *' '0 15 * * *') 43 | volumes: 44 | - /etc/letsencrypt:/etc/letsencrypt 45 | deploy: 46 | labels: 47 | - com.df.servicePath=/.well-known/acme-challenge 48 | - com.df.notify=true 49 | - com.df.distribute=true 50 | - com.df.port=80 51 | placement: 52 | constraints: [node.id == ] 53 | replicas: 1 54 | 55 | networks: 56 | proxy: 57 | external: true 58 | -------------------------------------------------------------------------------- /docker-compose-stack.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | # Let's Encrypt Companion 6 | letsencrypt-companion: 7 | image: hamburml/docker-flow-letsencrypt:latest 8 | networks: 9 | - proxy 10 | environment: 11 | - DOMAIN_1=('haembi.de' 'www.haembi.de' 'blog.haembi.de') 12 | - DOMAIN_2=('michael-hamburger.de' 'www.michael-hamburger.de' 'blog.michael-hamburger.de') 13 | - CERTBOT_EMAIL=your.mail@mail.de 14 | - PROXY_ADDRESS=proxy 15 | - CERTBOT_CRON_RENEW=('0 3 * * *' '0 15 * * *') 16 | volumes: 17 | - /etc/letsencrypt:/etc/letsencrypt 18 | deploy: 19 | labels: 20 | - com.df.aclName=__acme_letsencrypt_companion # arbitrary aclName to make sure it's on top on HAProxy's list 21 | - com.df.servicePath=/.well-known/acme-challenge 22 | - com.df.notify=true 23 | - com.df.distribute=true 24 | - com.df.port=80 25 | placement: 26 | constraints: [node.id == ] 27 | replicas: 1 28 | 29 | networks: 30 | proxy: 31 | external: true 32 | -------------------------------------------------------------------------------- /renewAndSendToProxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #colors 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | NC='\033[0m' # No Color 7 | 8 | #times we tried curl 9 | TRIES=0 10 | 11 | #maximum number of retries 12 | MAXRETRIES=5 13 | 14 | #timeout 15 | TIMEOUT=5 16 | 17 | printf "${GREEN}Hello! renewAndSendToProxy runs. Today is $(date)${NC}\n" 18 | 19 | # send current certificates to proxy - after that do a certbot renew round (which could take some seconds) and send updated certificates to proxy (faster startup with https when old certificates are still valid) 20 | echo $PROXY_ADDRESS | tr ',' '\n' | while read proxy_addr; do 21 | for d in /etc/letsencrypt/live/*/ ; do 22 | #move to directory 23 | cd $d 24 | 25 | #get directory name (which is the name of the regular domain) 26 | folder=${PWD##*/} 27 | 28 | #concat certificates 29 | printf "old certificates for $folder will be send to proxy\n" 30 | cat cert.pem chain.pem privkey.pem > $folder.combined.pem 31 | 32 | #send to proxy, retry up to 5 times with a timeout of $TIMEOUT seconds 33 | 34 | #reset tries to 0 35 | TRIES=0 36 | exitcode=0 37 | until [ $TRIES -ge $MAXRETRIES ] 38 | do 39 | TRIES=$[$TRIES+1] 40 | curl --silent --show-error -i -XPUT \ 41 | --data-binary @$folder.combined.pem \ 42 | "$proxy_addr:8080/v1/docker-flow-proxy/cert?certName=$folder.combined.pem&distribute=true" > /var/log/dockeroutput.log && break 43 | exitcode=$? 44 | if [ $TRIES -eq $MAXRETRIES ]; then 45 | printf "old certificate: ${RED}transmit failed after ${TRIES} attempts.${NC}\n" 46 | else 47 | printf "old certificate: ${RED}transmit failed, we try again in ${TIMEOUT} seconds.${NC}\n" 48 | sleep $TIMEOUT 49 | fi 50 | done 51 | 52 | if [ $exitcode -eq 0 ]; then 53 | printf "old certificates: proxy received $folder.combined.pem\n" 54 | fi 55 | done 56 | done 57 | 58 | 59 | #full path is needed or it is not started when run as cron 60 | 61 | #--no-bootstrap: prevent certbot from installing OS-level dependencies 62 | #--no-self-upgrade: prevent certbot from upgrading itself to newer released versions 63 | certbot renew --no-bootstrap --no-self-upgrade > /var/log/dockeroutput.log 64 | 65 | echo $PROXY_ADDRESS | tr ',' '\n' | while read proxy_addr; do 66 | printf "Docker Flow: Proxy DNS-Name: ${GREEN}$proxy_addr${NC}\n"; 67 | for d in /etc/letsencrypt/live/*/ ; do 68 | #move to directory 69 | cd $d 70 | 71 | #get directory name (which is the name of the regular domain) 72 | folder=${PWD##*/} 73 | printf "current folder name is: $folder\n" 74 | 75 | #concat certificates 76 | printf "concat certificates for $folder\n" 77 | cat cert.pem chain.pem privkey.pem > $folder.combined.pem 78 | printf "${GREEN}generated $folder.combined.pem${NC}\n" 79 | 80 | #send to proxy, retry up to 5 times with a timeout of $TIMEOUT seconds 81 | printf "${GREEN}transmit $folder.combined.pem to $proxy_addr${NC}\n" 82 | 83 | #reset tries to 0 84 | TRIES=0 85 | 86 | exitcode=0 87 | until [ $TRIES -ge $MAXRETRIES ] 88 | do 89 | TRIES=$[$TRIES+1] 90 | curl --silent --show-error -i -XPUT \ 91 | --data-binary @$folder.combined.pem \ 92 | "$proxy_addr:8080/v1/docker-flow-proxy/cert?certName=$folder.combined.pem&distribute=true" > /var/log/dockeroutput.log && break 93 | exitcode=$? 94 | 95 | if [ $TRIES -eq $MAXRETRIES ]; then 96 | printf "${RED}transmit failed after ${TRIES} attempts.${NC}\n" 97 | else 98 | printf "${RED}transmit failed, we try again in ${TIMEOUT} seconds.${NC}\n" 99 | sleep $TIMEOUT 100 | fi 101 | done 102 | 103 | if [ $exitcode -eq 0 ]; then 104 | printf "proxy received $folder.combined.pem\n" 105 | fi 106 | 107 | done 108 | done 109 | 110 | printf "${RED}/etc/letsencrypt will be backed up as backup-date-time.tar.gz. It's important to know that some files are symbolic links (inside this backup) and they need to be untared correctly.${NC}\n" 111 | cd /etc/letsencrypt 112 | mkdir -p backup 113 | if [ "$CERTBOTMODE" ]; then 114 | tar -cpz --exclude='./backup' -f ./backup/backup-`date +%Y%m%d_%H%M%S_%Z`-$CERTBOTMODE.tar.gz . 115 | else 116 | tar -cpz --exclude='./backup' -f ./backup/backup-`date +%Y%m%d_%H%M%S_%Z`-live.tar.gz . 117 | fi 118 | 119 | 120 | printf "${RED}Backup created, if you like download the /etc/letsencrypt/backup folder and store it on a safe place!${NC}\n\n" 121 | 122 | printf "${GREEN}Thanks for using Docker Flow: Let's Encrypt and have a nice day!${NC}\n\n" 123 | -------------------------------------------------------------------------------- /renewcron: -------------------------------------------------------------------------------- 1 | #placeholder - this file will be overwritten by certbot.sh 2 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | docker service create --name letsencrypt-companion \ 2 | --label com.df.notify=true \ 3 | --label com.df.distribute=true \ 4 | --label com.df.servicePath=/.well-known/acme-challenge \ 5 | --label com.df.port=80 \ 6 | -e DOMAIN_1="('haembi.de' 'www.haembi.de' 'blog.haembi.de')"\ 7 | -e DOMAIN_2="('michael-hamburger.de' 'www.michael-hamburger.de' 'blog.michael-hamburger.de')"\ 8 | -e CERTBOT_EMAIL="michael.hamburger@mail.de" \ 9 | -e PROXY_ADDRESS="proxy" \ 10 | -e CERTBOT_CRON_RENEW="('0 3 * * *' '0 15 * * *')"\ 11 | -e CERTBOTMODE="staging" \ 12 | --network proxy \ 13 | --constraint 'node.id==' \ 14 | --replicas 1 \ 15 | --mount type=bind,source=/etc/letsencrypt,destination=/etc/letsencrypt hamburml/docker-flow-letsencrypt:latest 16 | -------------------------------------------------------------------------------- /servicestart: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $CERTBOT_EMAIL ]; then 4 | printf "CERTBOT_EMAIL is empty!" 5 | exit 1 6 | fi 7 | 8 | printf "Starting Docker Flow: Let's Encrypt\n" 9 | 10 | /root/certbot.sh > /var/log/dockeroutput.log 11 | 12 | printf "\033[0;31mStarting supervisord (which starts and monitors cron) \033[0m\n" 13 | 14 | /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 15 | -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:cron] 5 | command = cron -f -L 15 > /var/log/dockeroutput.log 6 | autostart=true 7 | autorestart=true 8 | --------------------------------------------------------------------------------