From fd1e882eed4a80941ca301b4e8492ed4ec2f0727 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 22:54:38 +0800 Subject: [PATCH 001/160] less debug info --- le.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/le.sh b/le.sh index d523b60f..56f4842d 100755 --- a/le.sh +++ b/le.sh @@ -26,7 +26,7 @@ API=$DEFAULT_CA DEBUG= _debug() { - if ! [ "DEBUG" ] ; then + if ! [ "$DEBUG" ] ; then return fi @@ -144,8 +144,8 @@ _send_signed_request() { needbas64="$3" - _info url $url - _info payload "$payload" + _debug url $url + _debug payload "$payload" CURL_HEADER="$WORKING_DIR/curl.header" dp="$WORKING_DIR/curl.dump" @@ -190,7 +190,7 @@ _send_signed_request() { _get() { url="$1" - _info url $url + _debug url $url response=$(curl --silent $url) ret=$? _debug response "$response" @@ -212,13 +212,13 @@ _setopt() { fi if grep -H -n "^$__opt$__sep" $__conf ; then - echo OK + _debug OK sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" $__conf else - echo APP + _debug APP echo "$__opt$__sep$__val$__end" >> $__conf fi - grep -H -n "^$__opt$__sep" $__conf + _debug "$(grep -H -n "^$__opt$__sep" $__conf)" } _initpath() { @@ -342,10 +342,10 @@ issue() { _debug http01 "$http01" token=$(echo "$http01" | sed 's/,/\n'/g| grep '"token":'| cut -d : -f 2|sed 's/"//g') - _info token $token + _debug token $token uri=$(echo "$http01" | sed 's/,/\n'/g| grep '"uri":'| cut -d : -f 2,3|sed 's/"//g') - _info uri $uri + _debug uri $uri keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" From 72775c5ad955f16628114e2dc5c9b6b5f70b18a2 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:12:17 +0800 Subject: [PATCH 002/160] fix issue --- le.sh | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/le.sh b/le.sh index 56f4842d..2e005440 100755 --- a/le.sh +++ b/le.sh @@ -233,18 +233,13 @@ _initpath() { mkdir -p $WORKING_DIR/$domain - if [ -z "$CSR_PATH" ] ; then - CSR_PATH=$WORKING_DIR/$domain/$domain.csr - fi - - if [ -z "$CERT_KEY_PATH" ] ; then - CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key - fi - - if [ -z "$CERT_PATH" ] ; then - CERT_PATH=$WORKING_DIR/$domain/$domain.cer - fi - + + CSR_PATH=$WORKING_DIR/$domain/$domain.csr + + CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key + + CERT_PATH=$WORKING_DIR/$domain/$domain.cer + } #issue webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd] @@ -277,7 +272,7 @@ issue() { echo Usage: $0 webroot a.com [b.com,c.com] [key-length] return 1 fi - + createAccountKey $Le_Domain $Le_Keylength createDomainKey $Le_Domain $Le_Keylength From 5a29f018f282e849ee9b1de785eedb200fdec9fd Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:20:24 +0800 Subject: [PATCH 003/160] setopt if the conf doesn't exist. --- le.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 2e005440..c47cfb72 100755 --- a/le.sh +++ b/le.sh @@ -210,7 +210,9 @@ _setopt() { echo usage: $0 '"file" "opt" "=" "value" [";"]' return fi - + if [ -f $__conf ] ; + touch $__conf + fi if grep -H -n "^$__opt$__sep" $__conf ; then _debug OK sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" $__conf From 25355ba8931262a1fcdb2978e6ad168281b713ba Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:22:08 +0800 Subject: [PATCH 004/160] opps --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index c47cfb72..087d289b 100755 --- a/le.sh +++ b/le.sh @@ -210,7 +210,7 @@ _setopt() { echo usage: $0 '"file" "opt" "=" "value" [";"]' return fi - if [ -f $__conf ] ; + if [ -f $__conf ] ; then touch $__conf fi if grep -H -n "^$__opt$__sep" $__conf ; then From a799217ef2ee114e96d090660c7273536e8e5114 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:25:31 +0800 Subject: [PATCH 005/160] opps --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 087d289b..1a1e6261 100755 --- a/le.sh +++ b/le.sh @@ -210,7 +210,7 @@ _setopt() { echo usage: $0 '"file" "opt" "=" "value" [";"]' return fi - if [ -f $__conf ] ; then + if [ ! -f $__conf ] ; then touch $__conf fi if grep -H -n "^$__opt$__sep" $__conf ; then From 00e163506e18af3f20037f455b9a469858be3bed Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:36:12 +0800 Subject: [PATCH 006/160] minor --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 1a1e6261..b29b0d0c 100755 --- a/le.sh +++ b/le.sh @@ -458,7 +458,7 @@ issue() { fi _setopt $DOMAIN_CONF "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" - if [ "Le_ReloadCmd" ] ; then + if [ "$Le_ReloadCmd" ] ; then _info "Run Le_ReloadCmd: $Le_ReloadCmd" $Le_ReloadCmd fi From b5626079be136eed5c677e2fa655626f74e1d7dc Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 26 Dec 2015 23:41:30 +0800 Subject: [PATCH 007/160] typo --- le.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/le.sh b/le.sh index b29b0d0c..97cb55ec 100755 --- a/le.sh +++ b/le.sh @@ -321,7 +321,7 @@ issue() { fi # verify each domain - _info "verify each domain" + _info "Verify each domain" alldomains=$(echo "$Le_Domain,$Le_Alt" | sed "s/,/ /g") for d in $alldomains @@ -371,34 +371,34 @@ issue() { _debug "checking" if ! _get $uri ; then - _info "verify error:$d" + _info "Verify error:$d" return 1 fi status=$(echo $response | egrep -o '"status":"[^"]+"' | cut -d : -f 2 | sed 's/"//g') if [ "$status" == "valid" ] ; then - _info "verify success:$d" + _info "Verify success:$d" break; fi if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) - _info "verify error:$d" + _info "Verify error:$d" _debug $error return 1; fi if [ "$status" == "pending" ] ; then - _info "verify pending:$d" + _info "Verify pending:$d" else - _info "verify error:$d" + _info "Verify error:$d" return 1 fi done done - _info "verify finished, start to sign." + _info "Verify finished, start to sign." der=$(openssl req -in $CSR_PATH -outform DER | base64 | _b64) _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbas64" @@ -408,6 +408,8 @@ issue() { _info "Cert success." cat $CERT_PATH + _info "Your cert is in $CERT_PATH" + _setopt $DOMAIN_CONF "Le_Domain" "=" "$Le_Domain" _setopt $DOMAIN_CONF "Le_Alt" "=" "$Le_Alt" _setopt $DOMAIN_CONF "Le_Webroot" "=" "$Le_Webroot" From 1162f82e894d656b5e7163ee9581db85bcced1fa Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 00:33:53 +0800 Subject: [PATCH 008/160] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 071680a7..27848abb 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Once the cert is renewed, the apache/nginx will be automatically reloaded by th #Under the Hood -Use bash to say ACME language directly to Let's encrypt. +Speak ACME language with bash directly to Let's encrypt. TODO: From 560e5a1780b1abc5c45f957f8ff000e289c785b7 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 00:35:59 +0800 Subject: [PATCH 009/160] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27848abb..15b142ae 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ The cert will be placed in `~/.le/aa.com/` The issued cert will be renewed every 50 days automatically. -# Issue a cert, and install to apache +# Issue a cert, and install to apache/nginx ``` -le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "service apache2 reload" +le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "service apache2/nginx reload" ``` This can link the issued cert to the production apache or nginx path. Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` From 23c46ba1833816fa516470066628ddaa51e27494 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 00:39:08 +0800 Subject: [PATCH 010/160] Remove DEBUG --- le.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/le.sh b/le.sh index 97cb55ec..ffac4214 100755 --- a/le.sh +++ b/le.sh @@ -23,7 +23,6 @@ DEFAULT_CA="https://acme-v01.api.letsencrypt.org" API=$DEFAULT_CA -DEBUG= _debug() { if ! [ "$DEBUG" ] ; then From 8c841b97c8b3738a38ea28a97e821d58f683c53e Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 11:14:23 +0800 Subject: [PATCH 011/160] debug info and check error --- le.sh | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/le.sh b/le.sh index ffac4214..3da983b0 100755 --- a/le.sh +++ b/le.sh @@ -25,7 +25,8 @@ API=$DEFAULT_CA _debug() { - if ! [ "$DEBUG" ] ; then + + if [ -z "$DEBUG" ] ; then return fi @@ -149,7 +150,7 @@ _send_signed_request() { CURL_HEADER="$WORKING_DIR/curl.header" dp="$WORKING_DIR/curl.dump" CURL="curl --silent --dump-header $CURL_HEADER " - if [ "DEBUG" ] ; then + if [ "$DEBUG" ] ; then CURL="$CURL --trace-ascii $dp " fi payload64=$(echo -n $payload | base64 | _b64) @@ -212,7 +213,7 @@ _setopt() { if [ ! -f $__conf ] ; then touch $__conf fi - if grep -H -n "^$__opt$__sep" $__conf ; then + if grep -H -n "^$__opt$__sep" $__conf > /dev/null ; then _debug OK sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" $__conf else @@ -401,27 +402,34 @@ issue() { der=$(openssl req -in $CSR_PATH -outform DER | base64 | _b64) _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbas64" - echo -----BEGIN CERTIFICATE----- > $CERT_PATH - echo $response | sed "s/ /\n/g" >> $CERT_PATH - echo -----END CERTIFICATE----- >> $CERT_PATH - _info "Cert success." - cat $CERT_PATH - _info "Your cert is in $CERT_PATH" + Le_LinkCert=$(grep -i '^Location' $CURL_HEADER | cut -d " " -f 2) + _setopt $DOMAIN_CONF "Le_LinkCert" "=" "$Le_LinkCert" + + if [ "$Le_LinkCert" ] ; then + echo -----BEGIN CERTIFICATE----- > $CERT_PATH + echo $response | sed "s/ /\n/g" >> $CERT_PATH + echo -----END CERTIFICATE----- >> $CERT_PATH + _info "Cert success." + cat $CERT_PATH + + _info "Your cert is in $CERT_PATH" + fi _setopt $DOMAIN_CONF "Le_Domain" "=" "$Le_Domain" _setopt $DOMAIN_CONF "Le_Alt" "=" "$Le_Alt" _setopt $DOMAIN_CONF "Le_Webroot" "=" "$Le_Webroot" _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" - + if [ -z "$Le_LinkCert" ] ; then + _info "Sign failed, ToDO" + return 1 + fi Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') _setopt $DOMAIN_CONF "Le_LinkIssuer" "=" "$Le_LinkIssuer" - Le_LinkCert=$(grep -i '^Location' $CURL_HEADER | cut -d " " -f 2) - _setopt $DOMAIN_CONF "Le_LinkCert" "=" "$Le_LinkCert" - + Le_CertCreateTime=$(date -u "+%s") _setopt $DOMAIN_CONF "Le_CertCreateTime" "=" "$Le_CertCreateTime" From bf586981dbbc72bed8114dadd1aa05438421a3cc Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 11:38:07 +0800 Subject: [PATCH 012/160] error info --- le.sh | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/le.sh b/le.sh index 3da983b0..5ebe867e 100755 --- a/le.sh +++ b/le.sh @@ -141,9 +141,7 @@ _b64() { _send_signed_request() { url=$1 payload=$2 - - needbas64="$3" - + _debug url $url _debug payload "$payload" @@ -173,17 +171,13 @@ _send_signed_request() { body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug body "$body" - - if [ "$needbas64" ] ; then - response=$($CURL -X POST --data "$body" $url | base64) - else - response=$($CURL -X POST --data "$body" $url) - fi + response="$($CURL -X POST --data "$body" $url)" + responseHeaders="$(cat $CURL_HEADER)" _debug responseHeaders "$responseHeaders" _debug response "$response" - code=$(grep ^HTTP $CURL_HEADER | tail -1 | cut -d " " -f 2) + code="$(grep ^HTTP $CURL_HEADER | tail -1 | cut -d " " -f 2)" _debug code $code } @@ -400,7 +394,7 @@ issue() { _info "Verify finished, start to sign." der=$(openssl req -in $CSR_PATH -outform DER | base64 | _b64) - _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbas64" + _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" Le_LinkCert=$(grep -i '^Location' $CURL_HEADER | cut -d " " -f 2) @@ -408,7 +402,7 @@ issue() { if [ "$Le_LinkCert" ] ; then echo -----BEGIN CERTIFICATE----- > $CERT_PATH - echo $response | sed "s/ /\n/g" >> $CERT_PATH + echo $response | base64 | sed "s/ /\n/g" >> $CERT_PATH echo -----END CERTIFICATE----- >> $CERT_PATH _info "Cert success." cat $CERT_PATH @@ -422,7 +416,7 @@ issue() { _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" if [ -z "$Le_LinkCert" ] ; then - _info "Sign failed, ToDO" + _info "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi From cd3cdb5453052a389544db64f50fd5757002b3da Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 13:06:10 +0800 Subject: [PATCH 013/160] fix centos and openssl --- le.sh | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 5ebe867e..cbb287e5 100755 --- a/le.sh +++ b/le.sh @@ -122,7 +122,7 @@ createCSR() { alt=DNS:$(echo $domainlist | sed "s/,/,DNS:/g") #multi echo multi domain $alt - openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=$alt")) -out $CSR_PATH + openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" -reqexts SAN -config <(printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt") -out $CSR_PATH fi } @@ -509,9 +509,31 @@ install() { _initpath if ! command -v "curl" ; then _info "Please install curl first." - _info "sudo apt-get install curl" + _info "Ubuntu: sudo apt-get install curl" + _info "CentOS: yum install curl" return 1 fi + + if ! command -v "crontab" ; then + _info "Please install crontab first." + _info "CentOs: yum -y install crontabs" + return 1 + fi + + if ! command -v "openssl" ; then + _info "Please install openssl first." + _info "CentOs: yum -y install openssl" + return 1 + fi + + if ! command -v "xxd" ; then + _info "Please install xxd first." + _info "CentOs: yum install vim-common" + return 1 + fi + + + _info "Installing to $WORKING_DIR" mkdir -p $WORKING_DIR/ @@ -526,7 +548,11 @@ install() { _info "Installing cron job" if ! crontab -l | grep 'le.sh renewAll' ; then crontab -l | { cat; echo "0 0 * * * le.sh renewAll"; } | crontab - - service cron restart + if command -v crond ; then + service cron reload + else + service cron restart + fi fi From 3bfb563ba4d1b0f3365d751d2509445481b5e442 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 13:23:25 +0800 Subject: [PATCH 014/160] support CentOS --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15b142ae..0d5de0f7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Probably it's the smallest&easiest&smartest shell script to automatically issue #Supported OS -1. Tested on Ubuntu/Debian. -2. CentOS is Not tested yet, It should work. +1. Ubuntu/Debian. +2. CentOS #How to use From db25a3ea610bf4e7b46b4b9ed0e9fa5d52c61348 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 13:50:40 +0800 Subject: [PATCH 015/160] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0d5de0f7..e78e7d1f 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,13 @@ Probably it's the smallest&easiest&smartest shell script to automatically issue ``` ./le.sh install ``` -Which does 2 things: -* create and copy le.sh to your home dir: `~/.le` +Which does 3 jobs: +* create and copy `le.sh` to your home dir: `~/.le` All the certs will be placed in this folder. * create symbol link: `/bin/le -> ~/.le/le.sh` - -3. Ok, you are ready to issue cert now. +* create everyday cron job to check and renew the cert if needed. +* +Ok, you are ready to issue cert now. Show help message: ``` root@xvm:~# le From acc1e53aa0b4ff6679d15812a24ed3d8e81cc3e9 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 27 Dec 2015 13:52:46 +0800 Subject: [PATCH 016/160] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e78e7d1f..c6f5957e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ Which does 3 jobs: All the certs will be placed in this folder. * create symbol link: `/bin/le -> ~/.le/le.sh` * create everyday cron job to check and renew the cert if needed. -* + + Ok, you are ready to issue cert now. Show help message: ``` From d4df6ad17892e17ab35d91a981a40a8b1069b255 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 14:36:09 +0800 Subject: [PATCH 017/160] add ca cert --- le.sh | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/le.sh b/le.sh index cbb287e5..51bab2da 100755 --- a/le.sh +++ b/le.sh @@ -3,22 +3,11 @@ WORKING_DIR=~/.le -ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc - -CERT_KEY_PATH=$WORKING_DIR/domain.key - -CSR_PATH=$WORKING_DIR/domain.csr - -CERT_PATH=$WORKING_DIR/domain.cer - -DOMAIN_CONF=$WORKING_DIR/domain.conf - CURL_HEADER="" - HEADER="" HEADERPLACE="" - ACCOUNT_EMAIL="" + DEFAULT_CA="https://acme-v01.api.letsencrypt.org" API=$DEFAULT_CA @@ -58,7 +47,7 @@ createAccountKey() { echo Use default length 2048 length=2048 fi - + _initpath mkdir -p $WORKING_DIR ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc @@ -85,7 +74,7 @@ createDomainKey() { echo Use default length 2048 length=2048 fi - + _initpath $domain mkdir -p $WORKING_DIR/$domain CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key @@ -185,10 +174,10 @@ _send_signed_request() { _get() { url="$1" _debug url $url - response=$(curl --silent $url) + response="$(curl --silent $url)" ret=$? _debug response "$response" - code=$(echo $response | grep -o '"status":[0-9]\+' | cut -d : -f 2) + code="$(echo $response | grep -o '"status":[0-9]\+' | cut -d : -f 2)" _debug code $code return $ret } @@ -229,13 +218,13 @@ _initpath() { mkdir -p $WORKING_DIR/$domain - CSR_PATH=$WORKING_DIR/$domain/$domain.csr CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key CERT_PATH=$WORKING_DIR/$domain/$domain.cer - + + CA_CERT_PATH=$WORKING_DIR/$domain/ca.cer } #issue webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd] @@ -423,7 +412,14 @@ issue() { Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') _setopt $DOMAIN_CONF "Le_LinkIssuer" "=" "$Le_LinkIssuer" - + if [ "$Le_LinkIssuer" ] ; then + _get "$Le_LinkIssuer" + echo -----BEGIN CERTIFICATE----- > $CA_CERT_PATH + echo $response | base64 | sed "s/ /\n/g" >> $CA_CERT_PATH + echo -----END CERTIFICATE----- >> $CA_CERT_PATH + _info "The intermediate CA cert is in $CA_CERT_PATH" + fi + Le_CertCreateTime=$(date -u "+%s") _setopt $DOMAIN_CONF "Le_CertCreateTime" "=" "$Le_CertCreateTime" @@ -468,8 +464,6 @@ issue() { } - - renew() { Le_Domain="$1" if [ -z "$Le_Domain" ] ; then From a889d6898d22f0801d17bd4243ce6ce467c0ca0a Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 16:18:14 +0800 Subject: [PATCH 018/160] Opps, fix cert format --- le.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/le.sh b/le.sh index 51bab2da..85801445 100755 --- a/le.sh +++ b/le.sh @@ -382,16 +382,16 @@ issue() { done _info "Verify finished, start to sign." - der=$(openssl req -in $CSR_PATH -outform DER | base64 | _b64) + der="$(openssl req -in $CSR_PATH -outform DER | base64 | _b64)" _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" - Le_LinkCert=$(grep -i '^Location' $CURL_HEADER | cut -d " " -f 2) + Le_LinkCert="$(grep -i -o '^Location.*' $CURL_HEADER |sed 's/\r//g'| cut -d " " -f 2)" _setopt $DOMAIN_CONF "Le_LinkCert" "=" "$Le_LinkCert" - if [ "$Le_LinkCert" ] ; then + if [ "$Le_LinkCert" ] ; then echo -----BEGIN CERTIFICATE----- > $CERT_PATH - echo $response | base64 | sed "s/ /\n/g" >> $CERT_PATH + curl --silent $Le_LinkCert | base64 >> $CERT_PATH echo -----END CERTIFICATE----- >> $CERT_PATH _info "Cert success." cat $CERT_PATH @@ -415,7 +415,7 @@ issue() { if [ "$Le_LinkIssuer" ] ; then _get "$Le_LinkIssuer" echo -----BEGIN CERTIFICATE----- > $CA_CERT_PATH - echo $response | base64 | sed "s/ /\n/g" >> $CA_CERT_PATH + curl --silent $Le_LinkIssuer | base64 >> $CA_CERT_PATH echo -----END CERTIFICATE----- >> $CA_CERT_PATH _info "The intermediate CA cert is in $CA_CERT_PATH" fi From 4a743f81a830a04aa9f6452ddbf95ecf869b0e27 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 17:57:41 +0800 Subject: [PATCH 019/160] base64 the binary response --- le.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 85801445..417da11e 100755 --- a/le.sh +++ b/le.sh @@ -130,7 +130,8 @@ _b64() { _send_signed_request() { url=$1 payload=$2 - + needbase64=$3 + _debug url $url _debug payload "$payload" @@ -160,7 +161,11 @@ _send_signed_request() { body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug body "$body" - response="$($CURL -X POST --data "$body" $url)" + if [ "$needbase64" ] ; then + response="$($CURL -X POST --data "$body" $url | base64)" + else + response="$($CURL -X POST --data "$body" $url)" + fi responseHeaders="$(cat $CURL_HEADER)" @@ -383,7 +388,7 @@ issue() { _info "Verify finished, start to sign." der="$(openssl req -in $CSR_PATH -outform DER | base64 | _b64)" - _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" + _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" Le_LinkCert="$(grep -i -o '^Location.*' $CURL_HEADER |sed 's/\r//g'| cut -d " " -f 2)" @@ -405,6 +410,7 @@ issue() { _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" if [ -z "$Le_LinkCert" ] ; then + response="$(echo $response | base64 -d)" _info "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi From a2c5949dfdccc7344610cb05289973c635afe026 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 18:12:34 +0800 Subject: [PATCH 020/160] add "FORCE" macro to force renew cert --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 417da11e..03c62a9c 100755 --- a/le.sh +++ b/le.sh @@ -252,7 +252,7 @@ issue() { DOMAIN_CONF=$WORKING_DIR/$Le_Domain/$Le_Domain.conf if [ -f "$DOMAIN_CONF" ] ; then source "$DOMAIN_CONF" - if [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then + if [ -z "$FORCE" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then _info "Skip, Next renwal time is: $Le_NextRenewTimeStr" return 2 fi From c767f3d9a46bedbfd8caa40c37cc682072347b9b Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 18:27:55 +0800 Subject: [PATCH 021/160] error debug info --- le.sh | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/le.sh b/le.sh index 03c62a9c..5fb88ff4 100755 --- a/le.sh +++ b/le.sh @@ -34,6 +34,14 @@ _info() { fi } +_err() { + if [ -z "$2" ] ; then + echo "$1" >&2 + else + echo "$1:$2" >&2 + fi +} + #domain [2048] createAccountKey() { if [ -z "$1" ] ; then @@ -304,7 +312,7 @@ issue() { elif [ "$code" == '409' ] ; then _info "Already registered" else - _info "Register account Error." + _err "Register account Error." return 1 fi @@ -319,7 +327,7 @@ issue() { _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}" if [ ! -z "$code" ] && [ ! "$code" == '201' ] ; then - _info "new-authz error: $d" + _err "new-authz error: $response" return 1 fi @@ -349,7 +357,7 @@ issue() { _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}" if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then - _info "challenge error: $d" + _err "challenge error: $d" return 1 fi @@ -359,7 +367,7 @@ issue() { _debug "checking" if ! _get $uri ; then - _info "Verify error:$d" + _err "Verify error:$resource" return 1 fi @@ -371,15 +379,14 @@ issue() { if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) - _info "Verify error:$d" - _debug $error + _err "Verify error:$error" return 1; fi if [ "$status" == "pending" ] ; then _info "Verify pending:$d" else - _info "Verify error:$d" + _err "Verify error:$response" return 1 fi From e4a552da2504fa2700cd10fd0bd14ab2860ce694 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 19:27:43 +0800 Subject: [PATCH 022/160] fix the error message --- le.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 5fb88ff4..9d5234f0 100755 --- a/le.sh +++ b/le.sh @@ -175,7 +175,7 @@ _send_signed_request() { response="$($CURL -X POST --data "$body" $url)" fi - responseHeaders="$(cat $CURL_HEADER)" + responseHeaders="$(cat $CURL_HEADER | sed 's/\r//g')" _debug responseHeaders "$responseHeaders" _debug response "$response" @@ -417,7 +417,7 @@ issue() { _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" if [ -z "$Le_LinkCert" ] ; then - response="$(echo $response | base64 -d)" + response="$(echo $response | sed 's/ //g'| base64 -d)" _info "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi @@ -426,7 +426,6 @@ issue() { _setopt $DOMAIN_CONF "Le_LinkIssuer" "=" "$Le_LinkIssuer" if [ "$Le_LinkIssuer" ] ; then - _get "$Le_LinkIssuer" echo -----BEGIN CERTIFICATE----- > $CA_CERT_PATH curl --silent $Le_LinkIssuer | base64 >> $CA_CERT_PATH echo -----END CERTIFICATE----- >> $CA_CERT_PATH From 10af90d67be0ecf12e5d3f05866044bc6252935b Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 27 Dec 2015 19:34:51 +0800 Subject: [PATCH 023/160] minor --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 9d5234f0..177cf59d 100755 --- a/le.sh +++ b/le.sh @@ -175,7 +175,7 @@ _send_signed_request() { response="$($CURL -X POST --data "$body" $url)" fi - responseHeaders="$(cat $CURL_HEADER | sed 's/\r//g')" + responseHeaders="$(sed 's/\r//g' $CURL_HEADER)" _debug responseHeaders "$responseHeaders" _debug response "$response" From 948e8750adbaa31cf80959176419a9eb43e7cb03 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 28 Dec 2015 09:33:27 +0800 Subject: [PATCH 024/160] minor, change base64 encoding --- le.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/le.sh b/le.sh index 177cf59d..89cdc70b 100755 --- a/le.sh +++ b/le.sh @@ -129,7 +129,6 @@ _b64() { __n=$__n$__line done; __n=$(echo $__n | sed "s|/|_|g") - __n=$(echo $__n | sed "s| ||g") __n=$(echo $__n | sed "s|+|-|g") __n=$(echo $__n | sed "s|=||g") echo $__n @@ -149,7 +148,7 @@ _send_signed_request() { if [ "$DEBUG" ] ; then CURL="$CURL --trace-ascii $dp " fi - payload64=$(echo -n $payload | base64 | _b64) + payload64=$(echo -n $payload | base64 -w 0 | _b64) _debug payload64 $payload64 nonceurl="$API/directory" @@ -160,17 +159,17 @@ _send_signed_request() { protected=$(echo -n "$HEADERPLACE" | sed "s/NONCE/$nonce/" ) _debug protected "$protected" - protected64=$( echo -n $protected | base64 | _b64) + protected64=$( echo -n $protected | base64 -w 0 | _b64) _debug protected64 "$protected64" - sig=$(echo -n "$protected64.$payload64" | openssl dgst -sha256 -sign $ACCOUNT_KEY_PATH | base64| _b64) + sig=$(echo -n "$protected64.$payload64" | openssl dgst -sha256 -sign $ACCOUNT_KEY_PATH | base64 -w 0 | _b64) _debug sig "$sig" body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug body "$body" if [ "$needbase64" ] ; then - response="$($CURL -X POST --data "$body" $url | base64)" + response="$($CURL -X POST --data "$body" $url | base64 -w 0)" else response="$($CURL -X POST --data "$body" $url)" fi @@ -287,7 +286,7 @@ issue() { _debug e "$e" modulus=$(openssl rsa -in $ACCOUNT_KEY_PATH -modulus -noout | cut -d '=' -f 2 ) - n=$(echo $modulus| xxd -r -p | base64 | _b64 ) + n=$(echo $modulus| xxd -r -p | base64 -w 0 | _b64 ) jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}' @@ -296,7 +295,7 @@ issue() { _debug HEADER "$HEADER" accountkey_json=$(echo -n "$jwk" | sed "s/ //g") - thumbprint=$(echo -n "$accountkey_json" | sha256sum | xxd -r -p | base64 | _b64) + thumbprint=$(echo -n "$accountkey_json" | sha256sum | xxd -r -p | base64 -w 0 | _b64) _info "Registering account" @@ -394,7 +393,7 @@ issue() { done _info "Verify finished, start to sign." - der="$(openssl req -in $CSR_PATH -outform DER | base64 | _b64)" + der="$(openssl req -in $CSR_PATH -outform DER | base64 -w 0 | _b64)" _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" @@ -417,7 +416,7 @@ issue() { _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" if [ -z "$Le_LinkCert" ] ; then - response="$(echo $response | sed 's/ //g'| base64 -d)" + response="$(echo $response | base64 -d)" _info "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi From 5419c23f62f784804de1376369a4bc6392f1534e Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 28 Dec 2015 11:49:22 +0800 Subject: [PATCH 025/160] minor, fix install messages --- le.sh | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/le.sh b/le.sh index 89cdc70b..66912fcc 100755 --- a/le.sh +++ b/le.sh @@ -512,28 +512,28 @@ renewAll() { install() { _initpath - if ! command -v "curl" ; then - _info "Please install curl first." - _info "Ubuntu: sudo apt-get install curl" - _info "CentOS: yum install curl" + if ! command -v "curl" > /dev/null ; then + _err "Please install curl first." + _err "Ubuntu: sudo apt-get install curl" + _err "CentOS: yum install curl" return 1 fi - if ! command -v "crontab" ; then - _info "Please install crontab first." - _info "CentOs: yum -y install crontabs" + if ! command -v "crontab" > /dev/null ; then + _err "Please install crontab first." + _err "CentOs: yum -y install crontabs" return 1 fi - if ! command -v "openssl" ; then - _info "Please install openssl first." - _info "CentOs: yum -y install openssl" + if ! command -v "openssl" > /dev/null ; then + _err "Please install openssl first." + _err "CentOs: yum -y install openssl" return 1 fi - if ! command -v "xxd" ; then - _info "Please install xxd first." - _info "CentOs: yum install vim-common" + if ! command -v "xxd" > /dev/null ; then + _err "Please install xxd first." + _err "CentOs: yum install vim-common" return 1 fi @@ -553,10 +553,10 @@ install() { _info "Installing cron job" if ! crontab -l | grep 'le.sh renewAll' ; then crontab -l | { cat; echo "0 0 * * * le.sh renewAll"; } | crontab - - if command -v crond ; then - service cron reload + if command -v crond > /dev/null ; then + service crond reload 2>/dev/null else - service cron restart + service cron reload 2>/dev/null fi fi From ae5f79d80a7af44edaca298128f013616320abf8 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 28 Dec 2015 11:52:36 +0800 Subject: [PATCH 026/160] minor, fix uninstall messages. --- le.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index 66912fcc..23afae3d 100755 --- a/le.sh +++ b/le.sh @@ -567,8 +567,16 @@ install() { uninstall() { _initpath _info "Removing cron job" - crontab -l | sed "/le.sh renewAll/d" | crontab - - + + if ! crontab -l | grep 'le.sh renewAll' ; then + crontab -l | sed "/le.sh renewAll/d" | crontab - + if command -v crond > /dev/null ; then + service crond reload 2>/dev/null + else + service cron reload 2>/dev/null + fi + fi + _info "Removing /bin/le.sh" rm -f /bin/le rm -f /bin/le.sh From d337abcab10a1a544071d40fdb6ba42fee3f53ed Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 28 Dec 2015 15:57:22 +0800 Subject: [PATCH 027/160] typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c6f5957e..e7b702fd 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,11 @@ le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com ``` First argument " /home/wwwroot/aa.com" is the web root folder -Second argument "aa.com" is the domain you want to issue cert for. +Second argument "aa.com" is the main domain you want to issue cert for. -Third argument is the additional domain list you want to use. Comma sperated list, Optional. +Third argument is the additional domain list you want to use. Comma separated list, It's Optional. -'You must point and bind all the domains to the same webroot dir:/home/wwwroot/aa.com' +You must point and bind all the domains to the same webroot dir:`/home/wwwroot/aa.com` The cert will be placed in `~/.le/aa.com/` @@ -64,7 +64,7 @@ The issued cert will be renewed every 50 days automatically. le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "service apache2/nginx reload" ``` This can link the issued cert to the production apache or nginx path. -Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` +Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` From 617ec4e3f73ac30b40f60d62240c63fd320e5fde Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 28 Dec 2015 16:00:28 +0800 Subject: [PATCH 028/160] typos --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e7b702fd..e087fbc0 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,11 @@ Usage: /bin/le webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-pa ``` le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com ``` -First argument " /home/wwwroot/aa.com" is the web root folder +First argument `/home/wwwroot/aa.com` is the web root folder, You must have `write` access to this folder. Second argument "aa.com" is the main domain you want to issue cert for. -Third argument is the additional domain list you want to use. Comma separated list, It's Optional. +Third argument is the additional domain list you want to use. Comma separated list, which is Optional. You must point and bind all the domains to the same webroot dir:`/home/wwwroot/aa.com` @@ -63,8 +63,8 @@ The issued cert will be renewed every 50 days automatically. ``` le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "service apache2/nginx reload" ``` -This can link the issued cert to the production apache or nginx path. -Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` +Which issues the cert and then links it to the production apache or nginx path. +The cert will be renewed every 50 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` From 5a177aad36b89e4d712cfb6f77d2ee24b04c4e09 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 29 Dec 2015 11:29:46 +0800 Subject: [PATCH 029/160] opps --- le.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/le.sh b/le.sh index 23afae3d..d6155e8a 100755 --- a/le.sh +++ b/le.sh @@ -125,13 +125,8 @@ createCSR() { } _b64() { - while read __line; do - __n=$__n$__line - done; - __n=$(echo $__n | sed "s|/|_|g") - __n=$(echo $__n | sed "s|+|-|g") - __n=$(echo $__n | sed "s|=||g") - echo $__n + __n=$(cat) + echo $__n | tr '/+' '_-' | tr -d '= ' } _send_signed_request() { From 4ca9bc799df11b247cbc48c1c0d964e0a688b462 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 29 Dec 2015 20:01:39 +0800 Subject: [PATCH 030/160] clear code --- le.sh | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/le.sh b/le.sh index d6155e8a..7bfbd369 100755 --- a/le.sh +++ b/le.sh @@ -478,14 +478,7 @@ renew() { fi DOMAIN_CONF=$WORKING_DIR/$Le_Domain/$Le_Domain.conf - if [ -f "$DOMAIN_CONF" ] ; then - source "$DOMAIN_CONF" - if [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then - _info "Skip, Next renwal time is: $Le_NextRenewTimeStr" - return 2 - fi - fi - + if [ -z "$Le_Webroot" ] ; then echo Le_Webroot can not found, please remove the conf file and issue a new cert return 1 @@ -500,6 +493,25 @@ renewAll() { for d in $(ls -F $WORKING_DIR | grep '/$') ; do d=$(echo $d | cut -d '/' -f 1) _info "renew $d" + + Le_LinkCert="" + Le_Domain="" + Le_Alt="" + Le_Webroot="" + Le_Keylength="" + Le_LinkIssuer="" + + Le_CertCreateTime="" + Le_CertCreateTimeStr="" + Le_RenewalDays="" + Le_NextRenewTime="" + Le_NextRenewTimeStr="" + + Le_RealCertPath="" + Le_RealKeyPath="" + + Le_ReloadCmd="" + renew "$d" done From e2e275f24e333a18e27596fb1d5d7f7e46dfc7f5 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 29 Dec 2015 21:32:45 +0800 Subject: [PATCH 031/160] minor --- le.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/le.sh b/le.sh index 7bfbd369..2bfafe64 100755 --- a/le.sh +++ b/le.sh @@ -476,14 +476,7 @@ renew() { echo Usage: $0 domain.com return 1 fi - - DOMAIN_CONF=$WORKING_DIR/$Le_Domain/$Le_Domain.conf - if [ -z "$Le_Webroot" ] ; then - echo Le_Webroot can not found, please remove the conf file and issue a new cert - return 1 - fi - issue $Le_Domain } From ebb4363394d2bbd2ab4edb098f95f706ca41280c Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 29 Dec 2015 21:37:16 +0800 Subject: [PATCH 032/160] minor --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 2bfafe64..d113ee93 100755 --- a/le.sh +++ b/le.sh @@ -254,7 +254,7 @@ issue() { DOMAIN_CONF=$WORKING_DIR/$Le_Domain/$Le_Domain.conf if [ -f "$DOMAIN_CONF" ] ; then source "$DOMAIN_CONF" - if [ -z "$FORCE" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then + if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then _info "Skip, Next renwal time is: $Le_NextRenewTimeStr" return 2 fi From d8069cd49a47e25e95802501dca5457d1b6116a8 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 30 Dec 2015 22:15:33 +0800 Subject: [PATCH 033/160] typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e087fbc0..82dad2cd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is a shell version from https://github.com/diafygi/acme-tiny Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client (https://github.com/letsencrypt/letsencrypt) -Just one script, to issue, renew your certiricates automatically. +Just one script, to issue, renew your certificates automatically. Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt. From 7d076cfcea158a2fa1d30b27233b61ad963fdb42 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 5 Jan 2016 09:41:48 +0800 Subject: [PATCH 034/160] fix uninstall crontab --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index d113ee93..081f73ea 100755 --- a/le.sh +++ b/le.sh @@ -568,7 +568,7 @@ uninstall() { _initpath _info "Removing cron job" - if ! crontab -l | grep 'le.sh renewAll' ; then + if crontab -l | grep 'le.sh renewAll' ; then crontab -l | sed "/le.sh renewAll/d" | crontab - if command -v crond > /dev/null ; then service crond reload 2>/dev/null From 67afa94047df30fc55354970a586309d905f3973 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 5 Jan 2016 21:54:38 +0800 Subject: [PATCH 035/160] support Standalone server --- le.sh | 92 +++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/le.sh b/le.sh index 081f73ea..cd53c3e0 100755 --- a/le.sh +++ b/le.sh @@ -1,17 +1,12 @@ #!/bin/bash -WORKING_DIR=~/.le - -CURL_HEADER="" -HEADER="" -HEADERPLACE="" -ACCOUNT_EMAIL="" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" +DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" -API=$DEFAULT_CA - +API="$DEFAULT_CA" +AGREEMENT="$DEFAULT_AGREEMENT" _debug() { @@ -213,8 +208,35 @@ _setopt() { _debug "$(grep -H -n "^$__opt$__sep" $__conf)" } +_startserver() { + content="$1" + while true ; do + if [ -z "$DEBUG" ] ; then + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 > /dev/null + else + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 + fi + done +} + +_stopserver() { + pid="$1" + if [ "$pid" ] ; then + if [ -z "$DEBUG" ] ; then + kill -s 9 $pid 2>&1 + killall -s 9 nc 2>&1 + else + kill -s 9 $pid 2>&1 > /dev/null + killall -s 9 nc 2>&1 > /dev/null + fi + fi +} + _initpath() { - WORKING_DIR=~/.le + if [ -z "$WORKING_DIR" ]; then + WORKING_DIR=~/.le + fi + domain=$1 mkdir -p $WORKING_DIR ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc @@ -260,9 +282,23 @@ issue() { fi fi - if [ -z "$Le_Webroot" ] ; then - echo Usage: $0 webroot a.com [b.com,c.com] [key-length] - return 1 + if [ "$Le_Webroot" == "no" ] ; then + _info "Standalone mode." + if ! command -v "nc" > /dev/null ; then + _err "Please install netcat(nc) tools first." + return 1 + fi + if ! command -v "netstat" > /dev/null ; then + _err "Please install netstat first." + return 1 + fi + netprc="$(netstat -antpl | grep ':80 ')" + if [ "$netprc" ] ; then + _err "$netprc" + _err "tcp port 80 is already used by $(echo "$netprc" | cut -d '/' -f 2)" + _err "Please stop it first" + return 1 + fi fi createAccountKey $Le_Domain $Le_Keylength @@ -294,9 +330,9 @@ issue() { _info "Registering account" - regjson='{"resource": "new-reg", "agreement": "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"}' + regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}' if [ "$ACCOUNT_EMAIL" ] ; then - regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"}' + regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}' fi _send_signed_request "$API/acme/new-reg" "$regjson" @@ -337,13 +373,20 @@ issue() { keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" - wellknown_path="$Le_Webroot/.well-known/acme-challenge" - _debug wellknown_path "$wellknown_path" - - mkdir -p "$wellknown_path" - wellknown_path="$wellknown_path/$token" - echo -n "$keyauthorization" > $wellknown_path - + if [ "$Le_Webroot" == "no" ] ; then + _info "Standalone mode server" + _startserver "$keyauthorization" & 2>&1 >/dev/null + serverproc="$!" + sleep 2 + _debug serverproc $serverproc + else + wellknown_path="$Le_Webroot/.well-known/acme-challenge" + _debug wellknown_path "$wellknown_path" + + mkdir -p "$wellknown_path" + wellknown_path="$wellknown_path/$token" + echo -n "$keyauthorization" > $wellknown_path + fi wellknown_url="http://$d/.well-known/acme-challenge/$token" _debug wellknown_url "$wellknown_url" @@ -352,6 +395,7 @@ issue() { if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then _err "challenge error: $d" + _stopserver $serverproc return 1 fi @@ -362,6 +406,7 @@ issue() { if ! _get $uri ; then _err "Verify error:$resource" + _stopserver $serverproc return 1 fi @@ -374,6 +419,7 @@ issue() { if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) _err "Verify error:$error" + _stopserver $serverproc return 1; fi @@ -381,10 +427,12 @@ issue() { _info "Verify pending:$d" else _err "Verify error:$response" + _stopserver $serverproc return 1 fi - done + done + _stopserver $serverproc done _info "Verify finished, start to sign." From 072290f2d39501f8d897a6c699bad373871448c1 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 5 Jan 2016 21:59:27 +0800 Subject: [PATCH 036/160] support standalone server --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 82dad2cd..f7a704f7 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,14 @@ Which issues the cert and then links it to the production apache or nginx path. The cert will be renewed every 50 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` +# Use Standalone server: +Same usage as above, just give `no` as the webroot. +The tcp `80` port must be free to listen, otherwise you will be prompted to free the `80` port and try again. + +``` +le issue no aa.com www.aa.com,cp.aa.com +``` + #Under the Hood From 89a41e09a259e5ef24f3e057028f3a03a3aa3b14 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 21:24:39 +0800 Subject: [PATCH 037/160] give "no" to alternate DNS name list to support single domain cert --- le.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/le.sh b/le.sh index cd53c3e0..392a3645 100755 --- a/le.sh +++ b/le.sh @@ -282,6 +282,10 @@ issue() { fi fi + if [ "$Le_Alt" == "no" ] ; then + Le_Alt="" + fi + if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode." if ! command -v "nc" > /dev/null ; then From 97e44b974fdcdfc633718aaedf43c6f6cad28f17 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 21:41:06 +0800 Subject: [PATCH 038/160] add ca cert --- le.sh | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 392a3645..585e6bba 100755 --- a/le.sh +++ b/le.sh @@ -259,13 +259,17 @@ _initpath() { #issue webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd] issue() { if [ -z "$1" ] ; then - echo "Usage: $0 webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd]" + echo "Usage: $0 webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [ca-cert-file-path] [reloadCmd]" return 1 fi Le_Webroot=$1 Le_Domain=$2 Le_Alt=$3 Le_Keylength=$4 + Le_RealCertPath=$5 + Le_RealKeyPath=$6 + Le_RealCACertPath=$7 + Le_ReloadCmd=$8 if [ -z "$Le_Domain" ] ; then Le_Domain="$1" @@ -285,6 +289,21 @@ issue() { if [ "$Le_Alt" == "no" ] ; then Le_Alt="" fi + if [ "$Le_Keylength" == "no" ] ; then + Le_Keylength="" + fi + if [ "$Le_RealCertPath" == "no" ] ; then + Le_RealCertPath="" + fi + if [ "$Le_RealKeyPath" == "no" ] ; then + Le_RealKeyPath="" + fi + if [ "$Le_RealCACertPath" == "no" ] ; then + Le_RealCACertPath="" + fi + if [ "$Le_ReloadCmd" == "no" ] ; then + Le_ReloadCmd="" + fi if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode." @@ -505,6 +524,14 @@ issue() { fi + _setopt $DOMAIN_CONF "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + if [ "$Le_RealCACertPath" ] ; then + if [ -f "$Le_RealCACertPath" ] ; then + rm -f $Le_RealCACertPath + fi + ln -s $CA_CERT_PATH $Le_RealCACertPath + fi + _setopt $DOMAIN_CONF "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" if [ "$Le_RealKeyPath" ] ; then if [ -f "$Le_RealKeyPath" ] ; then @@ -554,6 +581,8 @@ renewAll() { Le_RealCertPath="" Le_RealKeyPath="" + + Le_RealCACertPath="" Le_ReloadCmd="" From bf108bb7f11debca893b025ebc08c48d61caeb5d Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 21:56:51 +0800 Subject: [PATCH 039/160] save config still if sign failed --- le.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/le.sh b/le.sh index 585e6bba..9aeb694f 100755 --- a/le.sh +++ b/le.sh @@ -480,6 +480,10 @@ issue() { _setopt $DOMAIN_CONF "Le_Alt" "=" "$Le_Alt" _setopt $DOMAIN_CONF "Le_Webroot" "=" "$Le_Webroot" _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" + _setopt $DOMAIN_CONF "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" + _setopt $DOMAIN_CONF "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + _setopt $DOMAIN_CONF "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" + _setopt $DOMAIN_CONF "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" @@ -515,16 +519,15 @@ issue() { Le_NextRenewTimeStr=$(date -u -d "+$Le_RenewalDays day" "+%Y-%m-%d %H:%M:%S UTC") _setopt $DOMAIN_CONF "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\"" - _setopt $DOMAIN_CONF "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" + if [ "$Le_RealCertPath" ] ; then if [ -f "$Le_RealCertPath" ] ; then rm -f $Le_RealCertPath fi ln -s $CERT_PATH $Le_RealCertPath - fi - _setopt $DOMAIN_CONF "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + if [ "$Le_RealCACertPath" ] ; then if [ -f "$Le_RealCACertPath" ] ; then rm -f $Le_RealCACertPath @@ -532,15 +535,13 @@ issue() { ln -s $CA_CERT_PATH $Le_RealCACertPath fi - _setopt $DOMAIN_CONF "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" - if [ "$Le_RealKeyPath" ] ; then + + if [ "$Le_RealKeyPath" ] ; then if [ -f "$Le_RealKeyPath" ] ; then rm -f $Le_RealKeyPath fi ln -s $CERT_KEY_PATH $Le_RealKeyPath - fi - _setopt $DOMAIN_CONF "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" if [ "$Le_ReloadCmd" ] ; then _info "Run Le_ReloadCmd: $Le_ReloadCmd" From 960ab88c54aad4db1acd0383e128a8a1ffb07a7c Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 22:14:45 +0800 Subject: [PATCH 040/160] minor, fix error messages --- le.sh | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/le.sh b/le.sh index 9aeb694f..3f03b6e1 100755 --- a/le.sh +++ b/le.sh @@ -47,15 +47,13 @@ createAccountKey() { account=$1 length=$2 if [ -z "$2" ] ; then - echo Use default length 2048 + _info "Use default length 2048" length=2048 fi _initpath - mkdir -p $WORKING_DIR - ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc if [ -f "$ACCOUNT_KEY_PATH" ] ; then - echo account key exists, skip + _info "Account key exists, skip" return else #generate account key @@ -74,7 +72,7 @@ createDomainKey() { domain=$1 length=$2 if [ -z "$2" ] ; then - echo Use default length 2048 + _info "Use default length 2048" length=2048 fi _initpath $domain @@ -82,7 +80,7 @@ createDomainKey() { CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key if [ -f "$CERT_KEY_PATH" ] ; then - echo domain key exists, skip + _info "Domain key exists, skip" else #generate account key openssl genrsa $length > $CERT_KEY_PATH @@ -102,18 +100,18 @@ createCSR() { domainlist=$2 if [ -f $CSR_PATH ] ; then - echo CSR exists, skip + _info "CSR exists, skip" return fi if [ -z "$domainlist" ] ; then #single domain - echo single domain + _info "Single domain" $domain openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" > $CSR_PATH else alt=DNS:$(echo $domainlist | sed "s/,/,DNS:/g") #multi - echo multi domain $alt + _info "Multi domain" $alt openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" -reqexts SAN -config <(printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt") -out $CSR_PATH fi @@ -222,7 +220,7 @@ _startserver() { _stopserver() { pid="$1" if [ "$pid" ] ; then - if [ -z "$DEBUG" ] ; then + if [ "$DEBUG" ] ; then kill -s 9 $pid 2>&1 killall -s 9 nc 2>&1 else @@ -375,7 +373,7 @@ issue() { alldomains=$(echo "$Le_Domain,$Le_Alt" | sed "s/,/ /g") for d in $alldomains do - _info "Verifing domain $d" + _info "Verifing domain" $d _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}" @@ -398,7 +396,7 @@ issue() { if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode server" - _startserver "$keyauthorization" & 2>&1 >/dev/null + _startserver "$keyauthorization" 2>&1 >/dev/null & serverproc="$!" sleep 2 _debug serverproc $serverproc @@ -435,7 +433,7 @@ issue() { status=$(echo $response | egrep -o '"status":"[^"]+"' | cut -d : -f 2 | sed 's/"//g') if [ "$status" == "valid" ] ; then - _info "Verify success:$d" + _info "Success" break; fi @@ -447,7 +445,7 @@ issue() { fi if [ "$status" == "pending" ] ; then - _info "Verify pending:$d" + _info "Pending" else _err "Verify error:$response" _stopserver $serverproc From 29a02475df8d1136428002134fc8f916544b6df1 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 22:20:36 +0800 Subject: [PATCH 041/160] init path for renewAll --- le.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/le.sh b/le.sh index 3f03b6e1..f4df3766 100755 --- a/le.sh +++ b/le.sh @@ -560,7 +560,9 @@ renew() { } renewAll() { + _initpath _info "renewAll" + for d in $(ls -F $WORKING_DIR | grep '/$') ; do d=$(echo $d | cut -d '/' -f 1) _info "renew $d" From b78a2c81d0a0322c02f8fe1cb29885c23a9bec59 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 22:30:18 +0800 Subject: [PATCH 042/160] minor, check 80 port more gracefully --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index f4df3766..d9dacbd4 100755 --- a/le.sh +++ b/le.sh @@ -313,7 +313,7 @@ issue() { _err "Please install netstat first." return 1 fi - netprc="$(netstat -antpl | grep ':80 ')" + netprc="$(netstat -ntpl | grep ':80 ')" if [ "$netprc" ] ; then _err "$netprc" _err "tcp port 80 is already used by $(echo "$netprc" | cut -d '/' -f 2)" From 8abd3e90aacbf148057e634d6edeef4dcd13330b Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 6 Jan 2016 22:51:16 +0800 Subject: [PATCH 043/160] polish Usage message --- le.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index d9dacbd4..5f7cb731 100755 --- a/le.sh +++ b/le.sh @@ -254,10 +254,10 @@ _initpath() { CA_CERT_PATH=$WORKING_DIR/$domain/ca.cer } -#issue webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd] + issue() { if [ -z "$1" ] ; then - echo "Usage: $0 webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [ca-cert-file-path] [reloadCmd]" + echo "Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" return 1 fi Le_Webroot=$1 From 01ba821632f65446b54407ce3b3b60adfa603354 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 13:05:16 +0800 Subject: [PATCH 044/160] fix error message for cron job --- le.sh | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/le.sh b/le.sh index 5f7cb731..7ec0c972 100755 --- a/le.sh +++ b/le.sh @@ -209,10 +209,10 @@ _setopt() { _startserver() { content="$1" while true ; do - if [ -z "$DEBUG" ] ; then - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 > /dev/null - else + if [ "$DEBUG" ] ; then echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 + else + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 2>&1 > /dev/null fi done } @@ -225,6 +225,7 @@ _stopserver() { killall -s 9 nc 2>&1 else kill -s 9 $pid 2>&1 > /dev/null + wait $pid 2>/dev/null killall -s 9 nc 2>&1 > /dev/null fi fi @@ -415,7 +416,7 @@ issue() { _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}" if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then - _err "challenge error: $d" + _err "$d:Challenge error: $resource" _stopserver $serverproc return 1 fi @@ -426,7 +427,7 @@ issue() { _debug "checking" if ! _get $uri ; then - _err "Verify error:$resource" + _err "$d:Verify error:$resource" _stopserver $serverproc return 1 fi @@ -439,7 +440,7 @@ issue() { if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) - _err "Verify error:$error" + _err "$d:Verify error:$error" _stopserver $serverproc return 1; fi @@ -447,7 +448,7 @@ issue() { if [ "$status" == "pending" ] ; then _info "Pending" else - _err "Verify error:$response" + _err "$d:Verify error:$response" _stopserver $serverproc return 1 fi @@ -485,7 +486,7 @@ issue() { if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" - _info "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" + _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi @@ -633,12 +634,17 @@ install() { fi _info "Installing cron job" + if command -v sudo > /dev/null ; then + if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then + SUDO=sudo + fi + fi if ! crontab -l | grep 'le.sh renewAll' ; then - crontab -l | { cat; echo "0 0 * * * le.sh renewAll"; } | crontab - + crontab -l | { cat; echo "0 0 * * * $SUDO le renewAll > /dev/null"; } | crontab - if command -v crond > /dev/null ; then - service crond reload 2>/dev/null + service crond reload >/dev/null else - service cron reload 2>/dev/null + service cron reload >/dev/null fi fi From 6d0a26f4f2055cae82cf21d1133bcb6adec3110a Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 13:08:24 +0800 Subject: [PATCH 045/160] fix uninstall --- le.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/le.sh b/le.sh index 7ec0c972..a3787d78 100755 --- a/le.sh +++ b/le.sh @@ -639,7 +639,7 @@ install() { SUDO=sudo fi fi - if ! crontab -l | grep 'le.sh renewAll' ; then + if ! crontab -l | grep 'le renewAll' ; then crontab -l | { cat; echo "0 0 * * * $SUDO le renewAll > /dev/null"; } | crontab - if command -v crond > /dev/null ; then service crond reload >/dev/null @@ -656,12 +656,12 @@ uninstall() { _initpath _info "Removing cron job" - if crontab -l | grep 'le.sh renewAll' ; then - crontab -l | sed "/le.sh renewAll/d" | crontab - + if crontab -l | grep 'le renewAll' ; then + crontab -l | sed "/le renewAll/d" | crontab - if command -v crond > /dev/null ; then - service crond reload 2>/dev/null + service crond reload >/dev/null else - service cron reload 2>/dev/null + service cron reload >/dev/null fi fi From 85fb3776eb614e3171864689bfedb1a4d2c59008 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 13:15:22 +0800 Subject: [PATCH 046/160] fix uninstall, compatible with previous versions --- le.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index a3787d78..4cd263c0 100755 --- a/le.sh +++ b/le.sh @@ -656,8 +656,8 @@ uninstall() { _initpath _info "Removing cron job" - if crontab -l | grep 'le renewAll' ; then - crontab -l | sed "/le renewAll/d" | crontab - + if crontab -l | grep 'le.*renewAll' ; then + crontab -l | sed "/le.*renewAll/d" | crontab - if command -v crond > /dev/null ; then service crond reload >/dev/null else From 0334473a5824985d9de735add3995cfbae16f759 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 18:06:44 +0800 Subject: [PATCH 047/160] fix param issues --- le.sh | 154 +++++++++++++++++++++++++++------------------------------- 1 file changed, 72 insertions(+), 82 deletions(-) diff --git a/le.sh b/le.sh index 4cd263c0..bedd7f32 100755 --- a/le.sh +++ b/le.sh @@ -57,7 +57,7 @@ createAccountKey() { return else #generate account key - openssl genrsa $length > $ACCOUNT_KEY_PATH + openssl genrsa $length > "$ACCOUNT_KEY_PATH" fi } @@ -76,14 +76,12 @@ createDomainKey() { length=2048 fi _initpath $domain - mkdir -p $WORKING_DIR/$domain - CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key if [ -f "$CERT_KEY_PATH" ] ; then _info "Domain key exists, skip" else #generate account key - openssl genrsa $length > $CERT_KEY_PATH + openssl genrsa $length > "$CERT_KEY_PATH" fi } @@ -99,7 +97,7 @@ createCSR() { domainlist=$2 - if [ -f $CSR_PATH ] ; then + if [ -f "$CSR_PATH" ] ; then _info "CSR exists, skip" return fi @@ -107,12 +105,12 @@ createCSR() { if [ -z "$domainlist" ] ; then #single domain _info "Single domain" $domain - openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" > $CSR_PATH + openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" > "$CSR_PATH" else - alt=DNS:$(echo $domainlist | sed "s/,/,DNS:/g") + alt="DNS:$(echo $domainlist | sed "s/,/,DNS:/g")" #multi - _info "Multi domain" $alt - openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" -reqexts SAN -config <(printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt") -out $CSR_PATH + _info "Multi domain" "$alt" + openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -reqexts SAN -config <(printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt") -out "$CSR_PATH" fi } @@ -193,15 +191,15 @@ _setopt() { echo usage: $0 '"file" "opt" "=" "value" [";"]' return fi - if [ ! -f $__conf ] ; then - touch $__conf + if [ ! -f "$__conf" ] ; then + touch "$__conf" fi - if grep -H -n "^$__opt$__sep" $__conf > /dev/null ; then + if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then _debug OK - sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" $__conf + sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" "$__conf" else _debug APP - echo "$__opt$__sep$__val$__end" >> $__conf + echo "$__opt$__sep$__val$__end" >> "$__conf" fi _debug "$(grep -H -n "^$__opt$__sep" $__conf)" } @@ -212,7 +210,7 @@ _startserver() { if [ "$DEBUG" ] ; then echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 else - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 2>&1 > /dev/null + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 > /dev/null fi done } @@ -221,12 +219,12 @@ _stopserver() { pid="$1" if [ "$pid" ] ; then if [ "$DEBUG" ] ; then - kill -s 9 $pid 2>&1 - killall -s 9 nc 2>&1 + kill -s 9 $pid + killall -s 9 nc else - kill -s 9 $pid 2>&1 > /dev/null + kill -s 9 $pid > /dev/null wait $pid 2>/dev/null - killall -s 9 nc 2>&1 > /dev/null + killall -s 9 nc > /dev/null fi fi } @@ -236,23 +234,25 @@ _initpath() { WORKING_DIR=~/.le fi - domain=$1 - mkdir -p $WORKING_DIR - ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc + domain="$1" + mkdir -p "$WORKING_DIR" + ACCOUNT_KEY_PATH="$WORKING_DIR/account.acc" if [ -z "$domain" ] ; then return 0 fi - mkdir -p $WORKING_DIR/$domain + mkdir -p "$WORKING_DIR/$domain" + + DOMAIN_CONF="$WORKING_DIR/$domain/$Le_Domain.conf" - CSR_PATH=$WORKING_DIR/$domain/$domain.csr + CSR_PATH="$WORKING_DIR/$domain/$domain.csr" - CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key + CERT_KEY_PATH="$WORKING_DIR/$domain/$domain.key" - CERT_PATH=$WORKING_DIR/$domain/$domain.cer + CERT_PATH="$WORKING_DIR/$domain/$domain.cer" - CA_CERT_PATH=$WORKING_DIR/$domain/ca.cer + CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" } @@ -261,22 +261,21 @@ issue() { echo "Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" return 1 fi - Le_Webroot=$1 - Le_Domain=$2 - Le_Alt=$3 - Le_Keylength=$4 - Le_RealCertPath=$5 - Le_RealKeyPath=$6 - Le_RealCACertPath=$7 - Le_ReloadCmd=$8 + Le_Webroot="$1" + Le_Domain="$2" + Le_Alt="$3" + Le_Keylength="$4" + Le_RealCertPath="$5" + Le_RealKeyPath="$6" + Le_RealCACertPath="$7" + Le_ReloadCmd="$8" if [ -z "$Le_Domain" ] ; then Le_Domain="$1" fi _initpath $Le_Domain - - DOMAIN_CONF=$WORKING_DIR/$Le_Domain/$Le_Domain.conf + if [ -f "$DOMAIN_CONF" ] ; then source "$DOMAIN_CONF" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then @@ -397,7 +396,7 @@ issue() { if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode server" - _startserver "$keyauthorization" 2>&1 >/dev/null & + _startserver "$keyauthorization" & serverproc="$!" sleep 2 _debug serverproc $serverproc @@ -463,26 +462,26 @@ issue() { Le_LinkCert="$(grep -i -o '^Location.*' $CURL_HEADER |sed 's/\r//g'| cut -d " " -f 2)" - _setopt $DOMAIN_CONF "Le_LinkCert" "=" "$Le_LinkCert" + _setopt "$DOMAIN_CONF" "Le_LinkCert" "=" "$Le_LinkCert" if [ "$Le_LinkCert" ] ; then - echo -----BEGIN CERTIFICATE----- > $CERT_PATH - curl --silent $Le_LinkCert | base64 >> $CERT_PATH - echo -----END CERTIFICATE----- >> $CERT_PATH + echo -----BEGIN CERTIFICATE----- > "$CERT_PATH" + curl --silent "$Le_LinkCert" | base64 >> "$CERT_PATH" + echo -----END CERTIFICATE----- >> "$CERT_PATH" _info "Cert success." - cat $CERT_PATH + cat "$CERT_PATH" _info "Your cert is in $CERT_PATH" fi - _setopt $DOMAIN_CONF "Le_Domain" "=" "$Le_Domain" - _setopt $DOMAIN_CONF "Le_Alt" "=" "$Le_Alt" - _setopt $DOMAIN_CONF "Le_Webroot" "=" "$Le_Webroot" - _setopt $DOMAIN_CONF "Le_Keylength" "=" "$Le_Keylength" - _setopt $DOMAIN_CONF "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" - _setopt $DOMAIN_CONF "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" - _setopt $DOMAIN_CONF "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" - _setopt $DOMAIN_CONF "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" + _setopt "$DOMAIN_CONF" "Le_Domain" "=" "$Le_Domain" + _setopt "$DOMAIN_CONF" "Le_Alt" "=" "$Le_Alt" + _setopt "$DOMAIN_CONF" "Le_Webroot" "=" "$Le_Webroot" + _setopt "$DOMAIN_CONF" "Le_Keylength" "=" "$Le_Keylength" + _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" + _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" @@ -491,60 +490,60 @@ issue() { fi Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') - _setopt $DOMAIN_CONF "Le_LinkIssuer" "=" "$Le_LinkIssuer" + _setopt "$DOMAIN_CONF" "Le_LinkIssuer" "=" "$Le_LinkIssuer" if [ "$Le_LinkIssuer" ] ; then - echo -----BEGIN CERTIFICATE----- > $CA_CERT_PATH - curl --silent $Le_LinkIssuer | base64 >> $CA_CERT_PATH - echo -----END CERTIFICATE----- >> $CA_CERT_PATH + echo -----BEGIN CERTIFICATE----- > "$CA_CERT_PATH" + curl --silent "$Le_LinkIssuer" | base64 >> "$CA_CERT_PATH" + echo -----END CERTIFICATE----- >> "$CA_CERT_PATH" _info "The intermediate CA cert is in $CA_CERT_PATH" fi Le_CertCreateTime=$(date -u "+%s") - _setopt $DOMAIN_CONF "Le_CertCreateTime" "=" "$Le_CertCreateTime" + _setopt "$DOMAIN_CONF" "Le_CertCreateTime" "=" "$Le_CertCreateTime" Le_CertCreateTimeStr=$(date -u "+%Y-%m-%d %H:%M:%S UTC") - _setopt $DOMAIN_CONF "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\"" + _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\"" if [ ! "$Le_RenewalDays" ] ; then Le_RenewalDays=50 fi - _setopt $DOMAIN_CONF "Le_RenewalDays" "=" "$Le_RenewalDays" + _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays" Le_NextRenewTime=$(date -u -d "+$Le_RenewalDays day" "+%s") - _setopt $DOMAIN_CONF "Le_NextRenewTime" "=" "$Le_NextRenewTime" + _setopt "$DOMAIN_CONF" "Le_NextRenewTime" "=" "$Le_NextRenewTime" Le_NextRenewTimeStr=$(date -u -d "+$Le_RenewalDays day" "+%Y-%m-%d %H:%M:%S UTC") - _setopt $DOMAIN_CONF "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\"" + _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\"" if [ "$Le_RealCertPath" ] ; then if [ -f "$Le_RealCertPath" ] ; then - rm -f $Le_RealCertPath + rm -f "$Le_RealCertPath" fi - ln -s $CERT_PATH $Le_RealCertPath + ln -s "$CERT_PATH" "$Le_RealCertPath" fi if [ "$Le_RealCACertPath" ] ; then if [ -f "$Le_RealCACertPath" ] ; then - rm -f $Le_RealCACertPath + rm -f "$Le_RealCACertPath" fi - ln -s $CA_CERT_PATH $Le_RealCACertPath + ln -s "$CA_CERT_PATH" "$Le_RealCACertPath" fi if [ "$Le_RealKeyPath" ] ; then if [ -f "$Le_RealKeyPath" ] ; then - rm -f $Le_RealKeyPath + rm -f "$Le_RealKeyPath" fi - ln -s $CERT_KEY_PATH $Le_RealKeyPath + ln -s "$CERT_KEY_PATH" "$Le_RealKeyPath" fi if [ "$Le_ReloadCmd" ] ; then _info "Run Le_ReloadCmd: $Le_ReloadCmd" - $Le_ReloadCmd + "$Le_ReloadCmd" fi } @@ -623,14 +622,11 @@ install() { _info "Installing to $WORKING_DIR" - - mkdir -p $WORKING_DIR/ - cp le.sh $WORKING_DIR/ - chmod +x $WORKING_DIR/le.sh - + if [ ! -f /bin/le.sh ] ; then - ln -s $WORKING_DIR/le.sh /bin/le.sh - ln -s $WORKING_DIR/le.sh /bin/le + cp le.sh "/bin/" + chmod +x "/bin/le.sh" + ln -s "/bin/le.sh" /bin/le fi _info "Installing cron job" @@ -682,12 +678,6 @@ showhelp() { if [ -z "$1" ] ; then showhelp +else + "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" fi - - - -$1 $2 $3 $4 $5 $6 $7 $8 - - - - From 4e1346dd1d7680c4ab8b987570502087d40b371b Mon Sep 17 00:00:00 2001 From: Neil Date: Thu, 7 Jan 2016 18:10:05 +0800 Subject: [PATCH 048/160] update params --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f7a704f7..300c40cd 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,11 @@ root@xvm:~# le Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall root@xvm:~# le issue -Usage: /bin/le webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd] +Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no ``` + +You can set "no" to the param to use default falue. # Just issue a cert: ``` @@ -61,14 +63,14 @@ The issued cert will be renewed every 50 days automatically. # Issue a cert, and install to apache/nginx ``` -le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "service apache2/nginx reload" +le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "/path/to/ca/certfil/apahce/nginx" "service apache2/nginx reload" ``` Which issues the cert and then links it to the production apache or nginx path. The cert will be renewed every 50 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` # Use Standalone server: -Same usage as above, just give `no` as the webroot. +Same usage as all above, just give `no` as the webroot. The tcp `80` port must be free to listen, otherwise you will be prompted to free the `80` port and try again. ``` From 2c03af204e64937f05ec9b6e63cebe9f1095ebd8 Mon Sep 17 00:00:00 2001 From: Neil Date: Thu, 7 Jan 2016 18:10:48 +0800 Subject: [PATCH 049/160] typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 300c40cd..0596a90b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]| ``` -You can set "no" to the param to use default falue. +You can set "no" to the param to use default value. # Just issue a cert: ``` From 23cb43c85fc0402a1e0ccbb1514b54dd95c218da Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 22:49:43 +0800 Subject: [PATCH 050/160] fix issue: sed contains "&" sign --- le.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/le.sh b/le.sh index bedd7f32..95c48ebf 100755 --- a/le.sh +++ b/le.sh @@ -196,6 +196,9 @@ _setopt() { fi if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then _debug OK + if [[ "$__val" == *"&"* ]] ; then + __val="$(echo $__val | sed 's/&/\\&/g')" + fi sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" "$__conf" else _debug APP From 4013bfd02a201f2dabc021a2c75d7c13aea40611 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 7 Jan 2016 23:17:15 +0800 Subject: [PATCH 051/160] remove dependency to "netstat" --- le.sh | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/le.sh b/le.sh index 95c48ebf..f59793c4 100755 --- a/le.sh +++ b/le.sh @@ -312,14 +312,11 @@ issue() { _err "Please install netcat(nc) tools first." return 1 fi - if ! command -v "netstat" > /dev/null ; then - _err "Please install netstat first." - return 1 - fi - netprc="$(netstat -ntpl | grep ':80 ')" + + netprc="$(ss -ntpl | grep ':80 ')" if [ "$netprc" ] ; then _err "$netprc" - _err "tcp port 80 is already used by $(echo "$netprc" | cut -d '/' -f 2)" + _err "tcp port 80 is already used by $(echo "$netprc" | cut -d : -f 4)" _err "Please stop it first" return 1 fi From 050038f872d444c56d806499cd36d6035cb65b57 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 8 Jan 2016 13:09:56 +0800 Subject: [PATCH 052/160] minor, save the config earlier. --- le.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/le.sh b/le.sh index f59793c4..c49b93f3 100755 --- a/le.sh +++ b/le.sh @@ -306,6 +306,15 @@ issue() { Le_ReloadCmd="" fi + _setopt "$DOMAIN_CONF" "Le_Domain" "=" "$Le_Domain" + _setopt "$DOMAIN_CONF" "Le_Alt" "=" "$Le_Alt" + _setopt "$DOMAIN_CONF" "Le_Webroot" "=" "$Le_Webroot" + _setopt "$DOMAIN_CONF" "Le_Keylength" "=" "$Le_Keylength" + _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" + _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" + if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode." if ! command -v "nc" > /dev/null ; then @@ -474,14 +483,7 @@ issue() { _info "Your cert is in $CERT_PATH" fi - _setopt "$DOMAIN_CONF" "Le_Domain" "=" "$Le_Domain" - _setopt "$DOMAIN_CONF" "Le_Alt" "=" "$Le_Alt" - _setopt "$DOMAIN_CONF" "Le_Webroot" "=" "$Le_Webroot" - _setopt "$DOMAIN_CONF" "Le_Keylength" "=" "$Le_Keylength" - _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" - _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" - _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" - _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" + if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" From 8172bdee87cf2566a14afee7945c9ec6e5b8efae Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 8 Jan 2016 21:44:32 +0800 Subject: [PATCH 053/160] typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0596a90b..ce59cc26 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The issued cert will be renewed every 50 days automatically. # Issue a cert, and install to apache/nginx ``` -le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx "/path/to/ca/certfil/apahce/nginx" "service apache2/nginx reload" +le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apahce/nginx "service apache2/nginx reload" ``` Which issues the cert and then links it to the production apache or nginx path. The cert will be renewed every 50 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` From 9a76ef2f325e6ba982151dcdf52379e385713d77 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 9 Jan 2016 23:26:11 +0800 Subject: [PATCH 054/160] apache plugin --- le.sh | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/le.sh b/le.sh index c49b93f3..c40754e5 100755 --- a/le.sh +++ b/le.sh @@ -234,7 +234,7 @@ _stopserver() { _initpath() { if [ -z "$WORKING_DIR" ]; then - WORKING_DIR=~/.le + WORKING_DIR=$HOME/.le fi domain="$1" @@ -256,8 +256,95 @@ _initpath() { CERT_PATH="$WORKING_DIR/$domain/$domain.cer" CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" + + if [ -z "$ACME_DIR" ] ; then + ACME_DIR="/home/.acme" + fi + + if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then + APACHE_CONF_BACKUP_DIR="$WORKING_DIR/" + fi + +} + + +_apachePath() { + httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | sed s/\"//g)" + httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | sed s/\"//g)" + httpdconf="$httpdroot/$httpdconfname" + if [ ! -f $httpdconf ] ; then + _err "Apache Config file not found" $httpdconf + return 1 + fi + return 0 } +_restoreApache() { + if ! _apachePath ; then + return 1 + fi + + if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ] ; then + _debug "No config file to restore." + return 0 + fi + + cp -p "$APACHE_CONF_BACKUP_DIR/$httpdconfname" "$httpdconf" + if ! apachectl -t ; then + _err "Sorry, restore apache config error, please contact me." + _restoreApache + return 1; + fi + rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" + return 0 +} + +_setApache() { + if ! _apachePath ; then + return 1 + fi + + #backup the conf + _debug "Backup apache config file" $httpdconf + cp -p $httpdconf $APACHE_CONF_BACKUP_DIR/ + _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname" + _info "In case there is an error that can not be restored automatically, you may try restore it yourself." + _info "The backup file will be deleted on sucess, just forget it." + + #add alias + echo " +Alias /.well-known/acme-challenge/ $ACME_DIR + + +Order allow,deny +Allow from all + + " >> $httpdconf + + if ! apachectl -t ; then + _err "Sorry, apache config error, please contact me." + _restoreApache + return 1; + fi + + if [ ! -d "$ACME_DIR" ] ; then + mkdir -p "$ACME_DIR" + chmod 755 "$ACME_DIR" + fi + + if ! apachectl graceful ; then + _err "Sorry, apachectl graceful error, please contact me." + _restoreApache + return 1; + fi + + return 0 +} + +_clearup () { + _stopserver $serverproc + _restoreApache +} issue() { if [ -z "$1" ] ; then @@ -330,6 +417,14 @@ issue() { return 1 fi fi + + if [ "$Le_Webroot" == "apache" ] ; then + if ! _setApache ; then + _err "set up apache error. Report error to me." + return 1 + fi + wellknown_path="$ACME_DIR" + fi createAccountKey $Le_Domain $Le_Keylength @@ -373,6 +468,7 @@ issue() { _info "Already registered" else _err "Register account Error." + _clearup return 1 fi @@ -388,6 +484,7 @@ issue() { if [ ! -z "$code" ] && [ ! "$code" == '201' ] ; then _err "new-authz error: $response" + _clearup return 1 fi @@ -410,7 +507,9 @@ issue() { sleep 2 _debug serverproc $serverproc else - wellknown_path="$Le_Webroot/.well-known/acme-challenge" + if [ -z "$wellknown_path" ] ; then + wellknown_path="$Le_Webroot/.well-known/acme-challenge" + fi _debug wellknown_path "$wellknown_path" mkdir -p "$wellknown_path" @@ -425,7 +524,7 @@ issue() { if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then _err "$d:Challenge error: $resource" - _stopserver $serverproc + _clearup return 1 fi @@ -436,7 +535,7 @@ issue() { if ! _get $uri ; then _err "$d:Verify error:$resource" - _stopserver $serverproc + _clearup return 1 fi @@ -449,7 +548,7 @@ issue() { if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) _err "$d:Verify error:$error" - _stopserver $serverproc + _clearup return 1; fi @@ -457,7 +556,7 @@ issue() { _info "Pending" else _err "$d:Verify error:$response" - _stopserver $serverproc + _clearup return 1 fi @@ -488,6 +587,7 @@ issue() { if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" + _clearup return 1 fi From b7b8311c3d6d6a5786a37458a502499ef20ea8ae Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 9 Jan 2016 23:36:25 +0800 Subject: [PATCH 055/160] minor --- le.sh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/le.sh b/le.sh index c40754e5..2a347429 100755 --- a/le.sh +++ b/le.sh @@ -237,6 +237,14 @@ _initpath() { WORKING_DIR=$HOME/.le fi + if [ -z "$ACME_DIR" ] ; then + ACME_DIR="/home/.acme" + fi + + if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then + APACHE_CONF_BACKUP_DIR="$WORKING_DIR/" + fi + domain="$1" mkdir -p "$WORKING_DIR" ACCOUNT_KEY_PATH="$WORKING_DIR/account.acc" @@ -257,13 +265,7 @@ _initpath() { CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" - if [ -z "$ACME_DIR" ] ; then - ACME_DIR="/home/.acme" - fi - - if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then - APACHE_CONF_BACKUP_DIR="$WORKING_DIR/" - fi + } @@ -280,6 +282,7 @@ _apachePath() { } _restoreApache() { + _initpath if ! _apachePath ; then return 1 fi @@ -300,6 +303,7 @@ _restoreApache() { } _setApache() { + _initpath if ! _apachePath ; then return 1 fi From ed68afac3960fc84f2bd8df3e170710eeb8ccd13 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 10 Jan 2016 10:31:09 +0800 Subject: [PATCH 056/160] fix --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 2a347429..c67100c6 100755 --- a/le.sh +++ b/le.sh @@ -317,7 +317,7 @@ _setApache() { #add alias echo " -Alias /.well-known/acme-challenge/ $ACME_DIR +Alias /.well-known/acme-challenge $ACME_DIR Order allow,deny From 4c1e55841309de61bb3ac5852f33d32b546a2bf9 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 10 Jan 2016 10:49:12 +0800 Subject: [PATCH 057/160] minor --- le.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/le.sh b/le.sh index c67100c6..4c76aeb2 100755 --- a/le.sh +++ b/le.sh @@ -1,6 +1,6 @@ #!/bin/bash - +PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" @@ -295,7 +295,6 @@ _restoreApache() { cp -p "$APACHE_CONF_BACKUP_DIR/$httpdconfname" "$httpdconf" if ! apachectl -t ; then _err "Sorry, restore apache config error, please contact me." - _restoreApache return 1; fi rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" From 2c75b3fd6bdca05feb6f22ef1cbf1994cfdc6999 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 10 Jan 2016 10:59:51 +0800 Subject: [PATCH 058/160] usage for Apache mode --- README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ce59cc26..710eab93 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # le Simplest shell script for LetsEncrypt free Certificate client -This is a shell version from https://github.com/diafygi/acme-tiny - Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client (https://github.com/letsencrypt/letsencrypt) Just one script, to issue, renew your certificates automatically. +This is a shell version from https://github.com/diafygi/acme-tiny, but without any dependencies. + Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt. @@ -15,6 +15,11 @@ Probably it's the smallest&easiest&smartest shell script to automatically issue 2. CentOS +#Supported Mode +1. Webroot mode +2. Standalone mode +3. Apache mode + #How to use 1. Clone this project: https://github.com/Neilpang/le.git @@ -37,7 +42,7 @@ root@xvm:~# le Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall root@xvm:~# le issue -Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no +Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no ``` @@ -77,6 +82,17 @@ The tcp `80` port must be free to listen, otherwise you will be prompted to free le issue no aa.com www.aa.com,cp.aa.com ``` +# Use Apache mode: +If you are running a web server, apache or nginx, it its recommended to use the Webroot mode. +Particularly, if you are running an apache server, you can use apache mode instead. Which doesn't write any file to your web root folder. + +Just set string "apache" to the first argument, it will use apache plugin automatically. + +``` +le issue apache aa.com www.aa.com +``` +All the other arguments are the same with previous. + #Under the Hood From bc1c69ff96d4df1264ed4c17ba165220777ac36f Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 10 Jan 2016 11:03:53 +0800 Subject: [PATCH 059/160] typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 710eab93..5a9a2364 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ le issue no aa.com www.aa.com,cp.aa.com ``` # Use Apache mode: -If you are running a web server, apache or nginx, it its recommended to use the Webroot mode. +If you are running a web server, apache or nginx, it is recommended to use the Webroot mode. Particularly, if you are running an apache server, you can use apache mode instead. Which doesn't write any file to your web root folder. Just set string "apache" to the first argument, it will use apache plugin automatically. From bcbb64e5ce4bd7e66128608c4cf04200a7b81d6c Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 10 Jan 2016 13:02:00 +0800 Subject: [PATCH 060/160] explain 'no' value --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a9a2364..60776b30 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,16 @@ Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-l ``` -You can set "no" to the param to use default value. +Set the param value to "no" means you want to ignore it. + +For example, if you give "no" to "key-length", it will use default length 2048. + +And if you give 'no' to 'cert-file-path', it will not copy the issued cert to the "cert-file-path". + +In all the cases, the issued cert will be placed in "~/.le/domain.com/" + + + # Just issue a cert: ``` From a0a2fe3ada7a386f604100157c0479f5be817cfc Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 10 Jan 2016 13:11:02 +0800 Subject: [PATCH 061/160] clearup on success --- le.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/le.sh b/le.sh index 4c76aeb2..8a1edd99 100755 --- a/le.sh +++ b/le.sh @@ -566,7 +566,7 @@ issue() { done _stopserver $serverproc done - + _clearup _info "Verify finished, start to sign." der="$(openssl req -in $CSR_PATH -outform DER | base64 -w 0 | _b64)" _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" @@ -590,7 +590,6 @@ issue() { if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" - _clearup return 1 fi From 22f86e39db4af445150dc5308db45edb9ed46cea Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 10 Jan 2016 20:47:13 +0800 Subject: [PATCH 062/160] first version number 1.0.1 --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 8a1edd99..528fa792 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash - +VER=1.0.1 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" From e03fae00992c0ad1b25eba06e20dc59414d3981c Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 10 Jan 2016 20:49:58 +0800 Subject: [PATCH 063/160] minor --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 528fa792..f82c09c7 100755 --- a/le.sh +++ b/le.sh @@ -351,7 +351,7 @@ _clearup () { issue() { if [ -z "$1" ] ; then - echo "Usage: le issue webroot|no a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" + echo "Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" return 1 fi Le_Webroot="$1" From 5a148a02d76d3845518cdde5b7f24f218220635c Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 10 Jan 2016 21:02:39 +0800 Subject: [PATCH 064/160] fix apache mode for SAN certificate --- le.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index f82c09c7..c97f9dd7 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.0.1 +VER=1.0.2 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -516,8 +516,7 @@ issue() { _debug wellknown_path "$wellknown_path" mkdir -p "$wellknown_path" - wellknown_path="$wellknown_path/$token" - echo -n "$keyauthorization" > $wellknown_path + echo -n "$keyauthorization" > "$wellknown_path/$token" fi wellknown_url="http://$d/.well-known/acme-challenge/$token" _debug wellknown_url "$wellknown_url" From 5b11958279ec393093f2c9a67c7a7961f182c7d8 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 10 Jan 2016 21:06:38 +0800 Subject: [PATCH 065/160] minor show version --- le.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index c97f9dd7..26cd51ed 100755 --- a/le.sh +++ b/le.sh @@ -772,10 +772,13 @@ uninstall() { } - +version() { + _info "$PROJECT" + _info "v$VER" +} showhelp() { - echo "Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall" - + version + echo "Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall|version" } From ad5d2a685de059a25ca380726b8e6fef9b9e4477 Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 11 Jan 2016 13:18:26 +0800 Subject: [PATCH 066/160] fix Apache 2.4 compatibility --- le.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 26cd51ed..d2c15e2f 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.0.2 +VER=1.0.3 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -319,8 +319,7 @@ _setApache() { Alias /.well-known/acme-challenge $ACME_DIR -Order allow,deny -Allow from all +Require all granted " >> $httpdconf From 63f046759e22abcd51245c2f8273f1b1dfe07dea Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 11 Jan 2016 13:23:02 +0800 Subject: [PATCH 067/160] Acknowledgment --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60776b30..4815459b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt offic Just one script, to issue, renew your certificates automatically. -This is a shell version from https://github.com/diafygi/acme-tiny, but without any dependencies. - Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt. @@ -111,6 +109,13 @@ Speak ACME language with bash directly to Let's encrypt. TODO: +#Acknowledgment +1. Acme-tiny: https://github.com/diafygi/acme-tiny +2. ACME protocol: https://github.com/ietf-wg-acme/acme +3. letsencrypt: https://github.com/letsencrypt/letsencrypt + + + #License & Other License is GPLv3 From 4b853d3c1e86ae11cb5f4184d2adfa00f358bb18 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 11 Jan 2016 20:39:25 +0800 Subject: [PATCH 068/160] minor support to specify API --- le.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index d2c15e2f..16d24b56 100755 --- a/le.sh +++ b/le.sh @@ -5,8 +5,14 @@ PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" -API="$DEFAULT_CA" -AGREEMENT="$DEFAULT_AGREEMENT" + +if [ -z "$API" ] ; then + API="$DEFAULT_CA" +fi + +if [ -z "$AGREEMENT" ] ; then + AGREEMENT="$DEFAULT_AGREEMENT" +fi _debug() { From 889dbbc092bacea5c2c882c9d0b51786a3472425 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 11 Jan 2016 21:12:21 +0800 Subject: [PATCH 069/160] minor, set paths via env vars --- le.sh | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/le.sh b/le.sh index 16d24b56..9f329b0c 100755 --- a/le.sh +++ b/le.sh @@ -253,7 +253,10 @@ _initpath() { domain="$1" mkdir -p "$WORKING_DIR" - ACCOUNT_KEY_PATH="$WORKING_DIR/account.acc" + + if [ -z "$ACCOUNT_KEY_PATH" ] ; then + ACCOUNT_KEY_PATH="$WORKING_DIR/account.acc" + fi if [ -z "$domain" ] ; then return 0 @@ -261,16 +264,21 @@ _initpath() { mkdir -p "$WORKING_DIR/$domain" - DOMAIN_CONF="$WORKING_DIR/$domain/$Le_Domain.conf" - - CSR_PATH="$WORKING_DIR/$domain/$domain.csr" - - CERT_KEY_PATH="$WORKING_DIR/$domain/$domain.key" - - CERT_PATH="$WORKING_DIR/$domain/$domain.cer" - - CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" - + if [ -z "$DOMAIN_CONF" ] ; then + DOMAIN_CONF="$WORKING_DIR/$domain/$Le_Domain.conf" + fi + if [ -z "$CSR_PATH" ] ; then + CSR_PATH="$WORKING_DIR/$domain/$domain.csr" + fi + if [ -z "$CERT_KEY_PATH" ] ; then + CERT_KEY_PATH="$WORKING_DIR/$domain/$domain.key" + fi + if [ -z "$CERT_PATH" ] ; then + CERT_PATH="$WORKING_DIR/$domain/$domain.cer" + fi + if [ -z "$CA_CERT_PATH" ] ; then + CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" + fi } @@ -695,6 +703,12 @@ renewAll() { Le_ReloadCmd="" + DOMAIN_CONF="" + CSR_PATH="" + CERT_KEY_PATH="" + CERT_PATH="" + CA_CERT_PATH="" + ACCOUNT_KEY_PATH="" renew "$d" done From 1d9dcdd5b2960824a231e188e81850f5b6a6bd6b Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 11 Jan 2016 21:29:12 +0800 Subject: [PATCH 070/160] fix bug. Renewall --- le.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 9f329b0c..0a69b3c1 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.0.3 +VER=1.0.4 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -709,6 +709,9 @@ renewAll() { CERT_PATH="" CA_CERT_PATH="" ACCOUNT_KEY_PATH="" + + wellknown_path="" + renew "$d" done From 30dbdbbde91f71a5cf851705af04eb630a1750d2 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 11 Jan 2016 21:50:03 +0800 Subject: [PATCH 071/160] fix issue for apache --- le.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 0a69b3c1..c18990e1 100755 --- a/le.sh +++ b/le.sh @@ -296,6 +296,9 @@ _apachePath() { } _restoreApache() { + if [ -z "$usingApache" ] ; then + return 0 + fi _initpath if ! _apachePath ; then return 1 @@ -353,7 +356,7 @@ Require all granted _restoreApache return 1; fi - + usingApache="1" return 0 } @@ -440,6 +443,8 @@ issue() { return 1 fi wellknown_path="$ACME_DIR" + else + usingApache="" fi createAccountKey $Le_Domain $Le_Keylength From 7bc5e3bb5cd6c8485cd049c0517a1df4b9a4fa95 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 13 Jan 2016 11:54:00 +0800 Subject: [PATCH 072/160] avoid unnecessary kill & fix reload command --- le.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index c18990e1..019cc746 100755 --- a/le.sh +++ b/le.sh @@ -362,6 +362,7 @@ Require all granted _clearup () { _stopserver $serverproc + serverproc="" _restoreApache } @@ -582,6 +583,7 @@ issue() { done _stopserver $serverproc + serverproc="" done _clearup _info "Verify finished, start to sign." @@ -664,7 +666,7 @@ issue() { if [ "$Le_ReloadCmd" ] ; then _info "Run Le_ReloadCmd: $Le_ReloadCmd" - "$Le_ReloadCmd" + $Le_ReloadCmd fi } From 93900a7a4822f4b05acadd88684c4a484bfb4dfe Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 13 Jan 2016 13:06:55 +0800 Subject: [PATCH 073/160] compatible with Proxmox, use cp instead of ln to update certs --- le.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/le.sh b/le.sh index 019cc746..465c2fdd 100755 --- a/le.sh +++ b/le.sh @@ -643,25 +643,25 @@ issue() { if [ "$Le_RealCertPath" ] ; then if [ -f "$Le_RealCertPath" ] ; then - rm -f "$Le_RealCertPath" + cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak fi - ln -s "$CERT_PATH" "$Le_RealCertPath" + cat "$CERT_PATH" > "$Le_RealCertPath" fi if [ "$Le_RealCACertPath" ] ; then if [ -f "$Le_RealCACertPath" ] ; then - rm -f "$Le_RealCACertPath" + cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak fi - ln -s "$CA_CERT_PATH" "$Le_RealCACertPath" + cat "$CA_CERT_PATH" > "$Le_RealCACertPath" fi if [ "$Le_RealKeyPath" ] ; then if [ -f "$Le_RealKeyPath" ] ; then - rm -f "$Le_RealKeyPath" + cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak fi - ln -s "$CERT_KEY_PATH" "$Le_RealKeyPath" + cat "$CERT_KEY_PATH" > "$Le_RealKeyPath" fi if [ "$Le_ReloadCmd" ] ; then From ff88fa31d6ef5f4f1e2ec8ca8ff13b468e550e71 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 13 Jan 2016 13:24:49 +0100 Subject: [PATCH 074/160] fix typo in output string --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 465c2fdd..7e863c6f 100755 --- a/le.sh +++ b/le.sh @@ -389,7 +389,7 @@ issue() { if [ -f "$DOMAIN_CONF" ] ; then source "$DOMAIN_CONF" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then - _info "Skip, Next renwal time is: $Le_NextRenewTimeStr" + _info "Skip, Next renewal time is: $Le_NextRenewTimeStr" return 2 fi fi From 5f68af17d2816543e34c31c1f46d932c7d28c829 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 14 Jan 2016 22:46:50 +0800 Subject: [PATCH 075/160] load domain.conf only when renewal. --- le.sh | 54 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/le.sh b/le.sh index 7e863c6f..89204b58 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.0.4 +VER=1.0.5 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -83,8 +83,15 @@ createDomainKey() { fi _initpath $domain - if [ -f "$CERT_KEY_PATH" ] ; then - _info "Domain key exists, skip" + if [ -f "$CERT_KEY_PATH" ] && ! [ "$FORCE" ] ; then + if [ "$IS_RENEW" ] ; then + _info "Domain key exists, skip" + return 0 + else + _err "Domain key exists, do you want to overwrite the key?" + _err "Set FORCE=1, and try again." + return 1 + fi else #generate account key openssl genrsa $length > "$CERT_KEY_PATH" @@ -103,7 +110,7 @@ createCSR() { domainlist=$2 - if [ -f "$CSR_PATH" ] ; then + if [ -f "$CSR_PATH" ] && [ "$IS_RENEW" ]; then _info "CSR exists, skip" return fi @@ -367,8 +374,8 @@ _clearup () { } issue() { - if [ -z "$1" ] ; then - echo "Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" + if [ -z "$2" ] ; then + _err "Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" return 1 fi Le_Webroot="$1" @@ -379,17 +386,14 @@ issue() { Le_RealKeyPath="$6" Le_RealCACertPath="$7" Le_ReloadCmd="$8" - - if [ -z "$Le_Domain" ] ; then - Le_Domain="$1" - fi + _initpath $Le_Domain - + if [ -f "$DOMAIN_CONF" ] ; then - source "$DOMAIN_CONF" + Le_NextRenewTime=$(grep "^Le_NextRenewTime=" "$DOMAIN_CONF" | cut -d '=' -f 2) if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then - _info "Skip, Next renewal time is: $Le_NextRenewTimeStr" + _info "Skip, Next renewal time is: $(grep "^Le_NextRenewTimeStr" "$DOMAIN_CONF" | cut -d '=' -f 2)" return 2 fi fi @@ -450,9 +454,15 @@ issue() { createAccountKey $Le_Domain $Le_Keylength - createDomainKey $Le_Domain $Le_Keylength + if ! createDomainKey $Le_Domain $Le_Keylength ; then + _err "Create domain key error." + return 1 + fi - createCSR $Le_Domain $Le_Alt + if ! createCSR $Le_Domain $Le_Alt ; then + _err "Create CSR error." + return 1 + fi pub_exp=$(openssl rsa -in $ACCOUNT_KEY_PATH -noout -text | grep "^publicExponent:"| cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) if [ "${#pub_exp}" == "5" ] ; then @@ -674,12 +684,22 @@ issue() { renew() { Le_Domain="$1" if [ -z "$Le_Domain" ] ; then - echo Usage: $0 domain.com + _err "Usage: $0 domain.com" return 1 fi - issue $Le_Domain + _initpath $Le_Domain + if [ -f "$DOMAIN_CONF" ] ; then + source "$DOMAIN_CONF" + if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then + _info "Skip, Next renewal time is: $Le_NextRenewTimeStr" + return 2 + fi + fi + IS_RENEW="1" + issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" + IS_RENEW="" } renewAll() { From 282eae22a263d61781dc41e6b541fee607fdc615 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 14 Jan 2016 23:04:07 +0800 Subject: [PATCH 076/160] change default renewall days to 80 days. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 89204b58..c4ac8a4f 100755 --- a/le.sh +++ b/le.sh @@ -639,7 +639,7 @@ issue() { _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\"" if [ ! "$Le_RenewalDays" ] ; then - Le_RenewalDays=50 + Le_RenewalDays=80 fi _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays" From 9877d46620fb6e56595216e90ad30b0512864604 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 14 Jan 2016 23:16:53 +0800 Subject: [PATCH 077/160] make the nc exit automatically, don't use kill anymore. --- le.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/le.sh b/le.sh index c4ac8a4f..13efdf1d 100755 --- a/le.sh +++ b/le.sh @@ -222,13 +222,13 @@ _setopt() { _startserver() { content="$1" - while true ; do +# while true ; do if [ "$DEBUG" ] ; then echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 else echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 > /dev/null fi - done +# done } _stopserver() { @@ -236,11 +236,11 @@ _stopserver() { if [ "$pid" ] ; then if [ "$DEBUG" ] ; then kill -s 9 $pid - killall -s 9 nc +# killall -s 9 nc else kill -s 9 $pid > /dev/null wait $pid 2>/dev/null - killall -s 9 nc > /dev/null +# killall -s 9 nc > /dev/null fi fi } From 6586a86902f3b84cc8884acc1254ca31dabb1b40 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 14 Jan 2016 23:19:31 +0800 Subject: [PATCH 078/160] nc exit automatically, not use kill anymore. --- le.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 13efdf1d..a2eb4384 100755 --- a/le.sh +++ b/le.sh @@ -235,11 +235,11 @@ _stopserver() { pid="$1" if [ "$pid" ] ; then if [ "$DEBUG" ] ; then - kill -s 9 $pid +# kill -s 9 $pid # killall -s 9 nc else - kill -s 9 $pid > /dev/null - wait $pid 2>/dev/null +# kill -s 9 $pid > /dev/null +# wait $pid 2>/dev/null # killall -s 9 nc > /dev/null fi fi From 2bd7774b8f9e362acdd244d5dba7c693a4a595c7 Mon Sep 17 00:00:00 2001 From: neil Date: Thu, 14 Jan 2016 23:21:11 +0800 Subject: [PATCH 079/160] remove unnecessary kill --- le.sh | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/le.sh b/le.sh index a2eb4384..a230e392 100755 --- a/le.sh +++ b/le.sh @@ -233,16 +233,7 @@ _startserver() { _stopserver() { pid="$1" - if [ "$pid" ] ; then - if [ "$DEBUG" ] ; then -# kill -s 9 $pid -# killall -s 9 nc - else -# kill -s 9 $pid > /dev/null -# wait $pid 2>/dev/null -# killall -s 9 nc > /dev/null - fi - fi + } _initpath() { From 4b70d690292bdb2885ff2011598901fbe2364658 Mon Sep 17 00:00:00 2001 From: Lucas Rolff Date: Sun, 17 Jan 2016 12:46:24 +0100 Subject: [PATCH 080/160] Chown directories to parent - You might configure web-servers to not allow reading files owned by root (or user you execute as), modified script to try chowning the .well-known recursively - If you do not have chown rights it will work anyway --- le.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/le.sh b/le.sh index a230e392..e7ae5495 100755 --- a/le.sh +++ b/le.sh @@ -537,6 +537,11 @@ issue() { mkdir -p "$wellknown_path" echo -n "$keyauthorization" > "$wellknown_path/$token" + + webroot_owner=$(stat -c '%U' $Le_Webroot) + _debug "Changing owner of .well-known to $webroot_owner" + chown -R $webroot_owner. "$Le_Webroot/.well-known" + fi wellknown_url="http://$d/.well-known/acme-challenge/$token" _debug wellknown_url "$wellknown_url" From 5248c5177098a8a1365ef569119091ae20e6bbb6 Mon Sep 17 00:00:00 2001 From: tombii Date: Wed, 20 Jan 2016 15:57:57 +0800 Subject: [PATCH 081/160] Update le.sh If we regenerate the key by using FORCE=1 then we also need to regenerate the CSR, otherwise the key will not match the certificate. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index a230e392..17b22f27 100755 --- a/le.sh +++ b/le.sh @@ -110,7 +110,7 @@ createCSR() { domainlist=$2 - if [ -f "$CSR_PATH" ] && [ "$IS_RENEW" ]; then + if [ -f "$CSR_PATH" ] && [ "$IS_RENEW" ] && ! [ "$FORCE" ]; then _info "CSR exists, skip" return fi From edcbe247cff9164cf4bf1f9aa5ab63ab9975ca9a Mon Sep 17 00:00:00 2001 From: Lucas Rolff Date: Wed, 20 Jan 2016 19:05:46 +0100 Subject: [PATCH 082/160] Also changing group of .well-known directory --- le.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index e7ae5495..a1770949 100755 --- a/le.sh +++ b/le.sh @@ -538,9 +538,9 @@ issue() { mkdir -p "$wellknown_path" echo -n "$keyauthorization" > "$wellknown_path/$token" - webroot_owner=$(stat -c '%U' $Le_Webroot) - _debug "Changing owner of .well-known to $webroot_owner" - chown -R $webroot_owner. "$Le_Webroot/.well-known" + webroot_owner=$(stat -c '%U:%G' $Le_Webroot) + _debug "Changing owner/group of .well-known to $webroot_owner" + chown -R $webroot_owner "$Le_Webroot/.well-known" fi wellknown_url="http://$d/.well-known/acme-challenge/$token" From e9840e61460f79bb3ce5bf7670a9b8a89ab6c888 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 22 Jan 2016 00:03:03 +0800 Subject: [PATCH 083/160] Support DNS-01 manually. Automatic api support is on its way. --- le.sh | 177 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 55 deletions(-) diff --git a/le.sh b/le.sh index bc869593..d3ff2d21 100755 --- a/le.sh +++ b/le.sh @@ -1,14 +1,14 @@ #!/bin/bash -VER=1.0.5 +VER=1.1.0 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" +STAGE_CA="https://acme-staging.api.letsencrypt.org" -if [ -z "$API" ] ; then - API="$DEFAULT_CA" -fi +VTYPE_HTTP="http-01" +VTYPE_DNS="dns-01" if [ -z "$AGREEMENT" ] ; then AGREEMENT="$DEFAULT_AGREEMENT" @@ -23,15 +23,15 @@ _debug() { if [ -z "$2" ] ; then echo $1 else - echo $1:$2 + echo "$1"="$2" fi } _info() { if [ -z "$2" ] ; then - echo $1 + echo "$1" else - echo $1:$2 + echo "$1"="$2" fi } @@ -39,7 +39,7 @@ _err() { if [ -z "$2" ] ; then echo "$1" >&2 else - echo "$1:$2" >&2 + echo "$1"="$2" >&2 fi } @@ -237,6 +237,16 @@ _stopserver() { } _initpath() { + + if [ -z "$API" ] ; then + if [ -z "$STAGE" ] ; then + API="$DEFAULT_CA" + else + API="$STAGE_CA" + _info "Using stage api:$API" + fi + fi + if [ -z "$WORKING_DIR" ]; then WORKING_DIR=$HOME/.le fi @@ -366,7 +376,7 @@ _clearup () { issue() { if [ -z "$2" ] ; then - _err "Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" + _err "Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" return 1 fi Le_Webroot="$1" @@ -442,7 +452,7 @@ issue() { else usingApache="" fi - + createAccountKey $Le_Domain $Le_Keylength if ! createDomainKey $Le_Domain $Le_Keylength ; then @@ -495,58 +505,113 @@ issue() { return 1 fi + vtype="$VTYPE_HTTP" + if [[ "$Le_Webroot" == "dns"* ]] ; then + vtype="$VTYPE_DNS" + fi + + vlist="$Le_Vlist" # verify each domain _info "Verify each domain" - - alldomains=$(echo "$Le_Domain,$Le_Alt" | sed "s/,/ /g") - for d in $alldomains - do - _info "Verifing domain" $d - - _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}" - - if [ ! -z "$code" ] && [ ! "$code" == '201' ] ; then - _err "new-authz error: $response" - _clearup + sep='#' + if [ -z "$vlist" ] ; then + alldomains=$(echo "$Le_Domain,$Le_Alt" | sed "s/,/ /g") + for d in $alldomains + do + _info "Geting token for domain" $d + _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}" + if [ ! -z "$code" ] && [ ! "$code" == '201' ] ; then + _err "new-authz error: $response" + _clearup + return 1 + fi + + entry=$(echo $response | egrep -o '{[^{]*"type":"'$vtype'"[^}]*') + _debug entry "$entry" + + token=$(echo "$entry" | sed 's/,/\n'/g| grep '"token":'| cut -d : -f 2|sed 's/"//g') + _debug token $token + + uri=$(echo "$entry" | sed 's/,/\n'/g| grep '"uri":'| cut -d : -f 2,3|sed 's/"//g') + _debug uri $uri + + keyauthorization="$token.$thumbprint" + _debug keyauthorization "$keyauthorization" + + dvlist="$d$sep$keyauthorization$sep$uri" + _debug dvlist "$dvlist" + + vlist="$vlist$dvlist," + + done + + #add entry + dnsadded="" + ventries=$(echo "$vlist" | sed "s/,/ /g") + for ventry in $ventries + do + d=$(echo $ventry | cut -d $sep -f 1) + keyauthorization=$(echo $ventry | cut -d $sep -f 2) + + if [ "$vtype" == "$VTYPE_DNS" ] ; then + dnsadded='0' + txtdomain="_acme-challenge.$d" + _debug txtdomain "$txtdomain" + txt="$(echo -e -n $keyauthorization | sha256sum | xxd -r -p | base64 -w 0 | _b64)" + _debug txt "$txt" + #dns + #1. check use api + _err "Add the following txt record:" + _err "Domain:$txtdomain" + _err "Txt value:$txt" + #dnsadded='1' + fi + done + + if [ "$dnsadded" == '0' ] ; then + _setopt "$DOMAIN_CONF" "Le_Vlist" "=" "\"$vlist\"" + _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit." + _err "Please add the txt records to the domains, and retry again." return 1 fi - http01=$(echo $response | egrep -o '{[^{]*"type":"http-01"[^}]*') - _debug http01 "$http01" - - token=$(echo "$http01" | sed 's/,/\n'/g| grep '"token":'| cut -d : -f 2|sed 's/"//g') - _debug token $token - - uri=$(echo "$http01" | sed 's/,/\n'/g| grep '"uri":'| cut -d : -f 2,3|sed 's/"//g') - _debug uri $uri - - keyauthorization="$token.$thumbprint" - _debug keyauthorization "$keyauthorization" + fi + + + _debug "ok, let's start to verify" + ventries=$(echo "$vlist" | sed "s/,/ /g") + for ventry in $ventries + do + d=$(echo $ventry | cut -d $sep -f 1) + keyauthorization=$(echo $ventry | cut -d $sep -f 2) + uri=$(echo $ventry | cut -d $sep -f 3) + _info "Verifying:$d" + _debug "d" "$d" + _debug "keyauthorization" "$keyauthorization" + _debug "uri" "$uri" - if [ "$Le_Webroot" == "no" ] ; then - _info "Standalone mode server" - _startserver "$keyauthorization" & - serverproc="$!" - sleep 2 - _debug serverproc $serverproc - else - if [ -z "$wellknown_path" ] ; then - wellknown_path="$Le_Webroot/.well-known/acme-challenge" + if [ "$vtype" == "$VTYPE_HTTP" ] ; then + if [ "$Le_Webroot" == "no" ] ; then + _info "Standalone mode server" + _startserver "$keyauthorization" & + serverproc="$!" + sleep 2 + _debug serverproc $serverproc + else + if [ -z "$wellknown_path" ] ; then + wellknown_path="$Le_Webroot/.well-known/acme-challenge" + fi + _debug wellknown_path "$wellknown_path" + + webroot_owner=$(stat -c '%U:%G' $Le_Webroot) + _debug "Changing owner/group of .well-known to $webroot_owner" + chown -R $webroot_owner "$Le_Webroot/.well-known" + + mkdir -p "$wellknown_path" + echo -n "$keyauthorization" > "$wellknown_path/$token" fi - _debug wellknown_path "$wellknown_path" - - mkdir -p "$wellknown_path" - echo -n "$keyauthorization" > "$wellknown_path/$token" - - webroot_owner=$(stat -c '%U:%G' $Le_Webroot) - _debug "Changing owner/group of .well-known to $webroot_owner" - chown -R $webroot_owner "$Le_Webroot/.well-known" - fi - wellknown_url="http://$d/.well-known/acme-challenge/$token" - _debug wellknown_url "$wellknown_url" - _debug challenge "$challenge" _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}" if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then @@ -590,7 +655,8 @@ issue() { done _stopserver $serverproc serverproc="" - done + done + _clearup _info "Verify finished, start to sign." der="$(openssl req -in $CSR_PATH -outform DER | base64 -w 0 | _b64)" @@ -611,13 +677,14 @@ issue() { fi - if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | base64 -d)" _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi + _setopt "$DOMAIN_CONF" 'Le_Vlist' '=' "\"\"" + Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') _setopt "$DOMAIN_CONF" "Le_LinkIssuer" "=" "$Le_LinkIssuer" From a947dbc6356389cea2590d7c26650af9bb103c0b Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 22 Jan 2016 00:16:43 +0800 Subject: [PATCH 084/160] Support dns-01 challenge --- README.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4815459b..cc64e92b 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ root@xvm:~# le Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall root@xvm:~# le issue -Usage: le issue webroot|no|apache a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no +Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no ``` @@ -101,6 +101,38 @@ le issue apache aa.com www.aa.com All the other arguments are the same with previous. +# Use DNS mode: +Support the latest dns-01 challenge. + +``` +le issue dns aa.com www.aa.com +``` + +Use domain api to automatically add dns record is not finished yet. +So, you must manually add the txt record to finish verify. + +You will got the output like bellow: +``` +Add the following txt record: +Domain:_acme-challenge.aa.com +Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c + +Add the following txt record: +Domain:_acme-challenge.www.aa.com +Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +Please add those txt records to the domains. Waiting for the dns to take effect. + +Then just retry with 'renew' command + +``` +le renew aa.com +``` + +Ok, it's finished. + + #Under the Hood From 52639149902e68c57f307e90a0d2eda7d650d3f0 Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 22 Jan 2016 00:17:20 +0800 Subject: [PATCH 085/160] Dns mode --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cc64e92b..678f78da 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Probably it's the smallest&easiest&smartest shell script to automatically issue 1. Webroot mode 2. Standalone mode 3. Apache mode +4. Dns mode #How to use From 6e89f811394601d54110abf037dc4fc0873e317c Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 22 Jan 2016 00:19:54 +0800 Subject: [PATCH 086/160] typos --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 678f78da..dc9dc22f 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,9 @@ le issue dns aa.com www.aa.com ``` Use domain api to automatically add dns record is not finished yet. -So, you must manually add the txt record to finish verify. +So, you must manually add the txt record to finish verifying. -You will got the output like bellow: +You will get the output like bellow: ``` Add the following txt record: Domain:_acme-challenge.aa.com @@ -125,7 +125,7 @@ Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Please add those txt records to the domains. Waiting for the dns to take effect. -Then just retry with 'renew' command +Then just retry with 'renew' command: ``` le renew aa.com From 79c2453a2c253f86a204741ea1a1b2e208d62184 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 09:38:08 +0800 Subject: [PATCH 087/160] separate "installcert" from "issue" command. --- le.sh | 75 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/le.sh b/le.sh index d3ff2d21..30ff2940 100755 --- a/le.sh +++ b/le.sh @@ -712,36 +712,10 @@ issue() { Le_NextRenewTimeStr=$(date -u -d "+$Le_RenewalDays day" "+%Y-%m-%d %H:%M:%S UTC") _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\"" - - - if [ "$Le_RealCertPath" ] ; then - if [ -f "$Le_RealCertPath" ] ; then - cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak - fi - cat "$CERT_PATH" > "$Le_RealCertPath" - fi - - - if [ "$Le_RealCACertPath" ] ; then - if [ -f "$Le_RealCACertPath" ] ; then - cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak - fi - cat "$CA_CERT_PATH" > "$Le_RealCACertPath" - fi - - if [ "$Le_RealKeyPath" ] ; then - if [ -f "$Le_RealKeyPath" ] ; then - cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak - fi - cat "$CERT_KEY_PATH" > "$Le_RealKeyPath" - fi - - if [ "$Le_ReloadCmd" ] ; then - _info "Run Le_ReloadCmd: $Le_ReloadCmd" - $Le_ReloadCmd - fi - + + installcert $Le_Domain "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" + } renew() { @@ -807,6 +781,47 @@ renewAll() { } +installcert() { + Le_Domain="$1" + if [ -z "$Le_Domain" ] ; then + _err "Usage: $0 domain.com [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" + return 1 + fi + + Le_RealCertPath="$2" + Le_RealKeyPath="$3" + Le_RealCACertPath="$4" + Le_ReloadCmd="$5" + + _initpath $Le_Domain + + _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\"" + _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" + _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" + + if [ "$Le_RealCACertPath" ] ; then + if [ -f "$Le_RealCACertPath" ] ; then + cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak + fi + cat "$CA_CERT_PATH" > "$Le_RealCACertPath" + fi + + + if [ "$Le_RealKeyPath" ] ; then + if [ -f "$Le_RealKeyPath" ] ; then + cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak + fi + cat "$CERT_KEY_PATH" > "$Le_RealKeyPath" + fi + + if [ "$Le_ReloadCmd" ] ; then + _info "Run Le_ReloadCmd: $Le_ReloadCmd" + $Le_ReloadCmd + fi + +} + install() { _initpath if ! command -v "curl" > /dev/null ; then @@ -890,7 +905,7 @@ version() { } showhelp() { version - echo "Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall|version" + echo "Usage: issue|installcert|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall|version" } From f074cb1036c42d12da375ab53316a707eaa30208 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 09:59:18 +0800 Subject: [PATCH 088/160] fix bug for webroot mode. The token was missing. --- le.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 30ff2940..e024395a 100755 --- a/le.sh +++ b/le.sh @@ -602,13 +602,17 @@ issue() { wellknown_path="$Le_Webroot/.well-known/acme-challenge" fi _debug wellknown_path "$wellknown_path" - + + token="$(echo -e -n "$keyauthorization" | cut -d '.' -f 1)" + _debug "writing token:$token to $wellknown_path/$token" + + mkdir -p "$wellknown_path" + echo -n "$keyauthorization" > "$wellknown_path/$token" + webroot_owner=$(stat -c '%U:%G' $Le_Webroot) _debug "Changing owner/group of .well-known to $webroot_owner" chown -R $webroot_owner "$Le_Webroot/.well-known" - mkdir -p "$wellknown_path" - echo -n "$keyauthorization" > "$wellknown_path/$token" fi fi From ebcf30d02ffcfd2839f599876603cc69052275de Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 10:49:38 +0800 Subject: [PATCH 089/160] remove ".well-known" folder after verification --- le.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/le.sh b/le.sh index e024395a..3badaa3c 100755 --- a/le.sh +++ b/le.sh @@ -374,6 +374,32 @@ _clearup () { _restoreApache } +# webroot removelevel tokenfile +_clearupwebbroot() { + __webroot="$1" + if [ -z "$__webroot" ] ; then + _debug "no webroot specified, skip" + return 0 + fi + + if [ "$2" == '1' ] ; then + _debug "remove $__webroot/.well-known" + rm -rf "$__webroot/.well-known" + elif [ "$2" == '2' ] ; then + _debug "remove $__webroot/.well-known/acme-challenge" + rm -rf "$__webroot/.well-known/acme-challenge" + elif [ "$2" == '3' ] ; then + _debug "remove $__webroot/.well-known/acme-challenge/$3" + rm -rf "$__webroot/.well-known/acme-challenge/$3" + else + _err "removelevel invalid: $2" + return 1 + fi + + return 0 + +} + issue() { if [ -z "$2" ] ; then _err "Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" @@ -589,7 +615,8 @@ issue() { _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" - + removelevel= "" + token="" if [ "$vtype" == "$VTYPE_HTTP" ] ; then if [ "$Le_Webroot" == "no" ] ; then _info "Standalone mode server" @@ -602,7 +629,15 @@ issue() { wellknown_path="$Le_Webroot/.well-known/acme-challenge" fi _debug wellknown_path "$wellknown_path" - + + if [ ! -d "$Le_Webroot/.well-known" ] ; then + removelevel='1' + elif [ ! -d "$Le_Webroot/.well-known/acme-challenge" ] ; then + removelevel='2' + else + removelevel='3' + fi + token="$(echo -e -n "$keyauthorization" | cut -d '.' -f 1)" _debug "writing token:$token to $wellknown_path/$token" @@ -620,6 +655,7 @@ issue() { if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then _err "$d:Challenge error: $resource" + _clearupwebbroot "$Le_Webroot" "$removelevel" "$token" _clearup return 1 fi @@ -631,6 +667,7 @@ issue() { if ! _get $uri ; then _err "$d:Verify error:$resource" + _clearupwebbroot "$Le_Webroot" "$removelevel" "$token" _clearup return 1 fi @@ -638,12 +675,16 @@ issue() { status=$(echo $response | egrep -o '"status":"[^"]+"' | cut -d : -f 2 | sed 's/"//g') if [ "$status" == "valid" ] ; then _info "Success" + _stopserver $serverproc + serverproc="" + _clearupwebbroot "$Le_Webroot" "$removelevel" "$token" break; fi if [ "$status" == "invalid" ] ; then error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4) _err "$d:Verify error:$error" + _clearupwebbroot "$Le_Webroot" "$removelevel" "$token" _clearup return 1; fi @@ -652,13 +693,13 @@ issue() { _info "Pending" else _err "$d:Verify error:$response" + _clearupwebbroot "$Le_Webroot" "$removelevel" "$token" _clearup return 1 fi done - _stopserver $serverproc - serverproc="" + done _clearup @@ -669,7 +710,7 @@ issue() { Le_LinkCert="$(grep -i -o '^Location.*' $CURL_HEADER |sed 's/\r//g'| cut -d " " -f 2)" _setopt "$DOMAIN_CONF" "Le_LinkCert" "=" "$Le_LinkCert" - + if [ "$Le_LinkCert" ] ; then echo -----BEGIN CERTIFICATE----- > "$CERT_PATH" curl --silent "$Le_LinkCert" | base64 >> "$CERT_PATH" From 9a66cdb6a08bfe7c27b9387547b28f5263c9791e Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 14:41:10 +0800 Subject: [PATCH 090/160] make le more friendly to no-root user. 1. separate installcronjob/uninstallcronjob. no-root users can use cron job without installing le.sh 2. add cron command for cron only. 3. polish help messages. 4. move le from /bin/le to /usr/local/bin/le 5. only root can install to /usr/local/bin/le. non-root users can use ether /usr/local/bin/le or ~/.le/le.sh instead 6. WORKING_DIR can be specified when install/cronjob --- le.sh | 132 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 37 deletions(-) diff --git a/le.sh b/le.sh index 3badaa3c..241ff9fb 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.0 +VER=1.1.1 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -238,6 +238,12 @@ _stopserver() { _initpath() { + if command -v sudo > /dev/null ; then + if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then + SUDO=sudo + fi + fi + if [ -z "$API" ] ; then if [ -z "$STAGE" ] ; then API="$DEFAULT_CA" @@ -867,6 +873,35 @@ installcert() { } +installcronjob() { + _initpath + _info "Installing cron job" + if ! crontab -l | grep 'le.sh cron' ; then + if command -v "le.sh" > /dev/null ; then + lesh="$(which le.sh)" + elif [ -f "$WORKING_DIR/le.sh" ] ; then + lesh="\"$WORKING_DIR\"/le.sh" + else + _err "Can not install cronjob, le.sh not found." + return 1 + fi + crontab -l | { cat; echo "0 0 * * * $SUDO WORKING_DIR=\"$WORKING_DIR\" $lesh cron > /dev/null"; } | crontab - + fi + return 0 +} + +uninstallcronjob() { + _info "Removing cron job" + cr="$(crontab -l | grep 'le.sh cron')" + if [ "$cr" ] ; then + crontab -l | sed "/le.sh cron/d" | crontab - + WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 7 | cut -d '=' -f 2 | tr -d '"')" + _info WORKING_DIR "$WORKING_DIR" + fi + _initpath + +} + install() { _initpath if ! command -v "curl" > /dev/null ; then @@ -893,64 +928,87 @@ install() { _err "CentOs: yum install vim-common" return 1 fi - - - + _info "Installing to $WORKING_DIR" - - if [ ! -f /bin/le.sh ] ; then - cp le.sh "/bin/" - chmod +x "/bin/le.sh" - ln -s "/bin/le.sh" /bin/le - fi - - _info "Installing cron job" - if command -v sudo > /dev/null ; then - if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then - SUDO=sudo - fi - fi - if ! crontab -l | grep 'le renewAll' ; then - crontab -l | { cat; echo "0 0 * * * $SUDO le renewAll > /dev/null"; } | crontab - - if command -v crond > /dev/null ; then - service crond reload >/dev/null + + #try install to /bin if is root + if [ ! -f /usr/local/bin/le.sh ] ; then + #if root + if $SUDO cp le.sh /usr/local/bin/le.sh ; then + $SUDO chmod 755 /usr/local/bin/le.sh + $SUDO ln -s "/usr/local/bin/le.sh" /usr/local/bin/le + rm -f $WORKING_DIR/le.sh + $SUDO ln -s /usr/local/bin/le.sh $WORKING_DIR/le.sh + _info "Installed to /usr/local/bin/le" else - service cron reload >/dev/null + #install to home, for non root user + cp le.sh $WORKING_DIR/ + chmod +x $WORKING_DIR/le.sh + _info "Installed to $WORKING_DIR/le" fi - fi - + fi + rm -f $WORKING_DIR/le + ln -s $WORKING_DIR/le.sh $WORKING_DIR/le + + installcronjob _info OK } uninstall() { + uninstallcronjob _initpath - _info "Removing cron job" - if crontab -l | grep 'le.*renewAll' ; then - crontab -l | sed "/le.*renewAll/d" | crontab - - if command -v crond > /dev/null ; then - service crond reload >/dev/null - else - service cron reload >/dev/null + if [ -f "/usr/local/bin/le.sh" ] ; then + _info "Removing /usr/local/bin/le.sh" + if $SUDO rm -f /usr/local/bin/le.sh ; then + $SUDO rm -f /usr/local/bin/le fi - fi + fi - _info "Removing /bin/le.sh" - rm -f /bin/le - rm -f /bin/le.sh - _info "The keys and certs are in $WORKING_DIR, you can remove them by yourself." } +cron() { + renewAll +} + version() { _info "$PROJECT" _info "v$VER" } + showhelp() { version - echo "Usage: issue|installcert|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall|version" + echo "Usage: le.sh [command] ...[args].... +Avalible commands: + +install: + Install le.sh to your system. +issue: + Issue a cert. +installcert: + Install the issued cert to apache/nginx or any other server. +renew: + Renew a cert. +renewAll: + Renew all the certs. +uninstall: + Uninstall le.sh, and uninstall the cron job. +version: + Show version info. +installcronjob: + Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. +uninstallcronjob: + Uninstall the cron job. The 'uninstall' command can do this automatically. +createAccountKey: + Create an account private key, professional use. +createDomainKey: + Create an domain private key, professional use. +createCSR: + Create CSR , professional use. + " } From 649fc386d1947557d14d651e09719ebf8de7c9a7 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 14:51:45 +0800 Subject: [PATCH 091/160] minor: fix install message. --- le.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index 241ff9fb..0fcaaeb0 100755 --- a/le.sh +++ b/le.sh @@ -934,7 +934,7 @@ install() { #try install to /bin if is root if [ ! -f /usr/local/bin/le.sh ] ; then #if root - if $SUDO cp le.sh /usr/local/bin/le.sh ; then + if $SUDO cp le.sh /usr/local/bin/le.sh 2>&1 > /dev/null; then $SUDO chmod 755 /usr/local/bin/le.sh $SUDO ln -s "/usr/local/bin/le.sh" /usr/local/bin/le rm -f $WORKING_DIR/le.sh @@ -944,7 +944,7 @@ install() { #install to home, for non root user cp le.sh $WORKING_DIR/ chmod +x $WORKING_DIR/le.sh - _info "Installed to $WORKING_DIR/le" + _info "Installed to $WORKING_DIR/le.sh" fi fi rm -f $WORKING_DIR/le From 2276a9ec978e87455b949cff0df4d36ec3f097fc Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 23 Jan 2016 12:03:46 +0100 Subject: [PATCH 092/160] remove extra space leading to an error The line was parsed as: execute command "" with removelevel being cleared. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 0fcaaeb0..117bbd55 100755 --- a/le.sh +++ b/le.sh @@ -621,7 +621,7 @@ issue() { _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" - removelevel= "" + removelevel="" token="" if [ "$vtype" == "$VTYPE_HTTP" ] ; then if [ "$Le_Webroot" == "no" ] ; then From ab5ec2a2d2d500687bf1877c5645837a2eecc949 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 22:29:06 +0800 Subject: [PATCH 093/160] fix bug: Le_RealCertPath was missing from installcert command --- le.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/le.sh b/le.sh index 117bbd55..fe3b7846 100755 --- a/le.sh +++ b/le.sh @@ -851,6 +851,13 @@ installcert() { _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\"" _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\"" + if [ "$Le_RealCertPath" ] ; then + if [ -f "$Le_RealCertPath" ] ; then + cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak + fi + cat "$CERT_PATH" > "$Le_RealCertPath" + fi + if [ "$Le_RealCACertPath" ] ; then if [ -f "$Le_RealCACertPath" ] ; then cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak From c24e86a69702241d7c48a94bd34e80ebff976cf2 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 22:52:43 +0800 Subject: [PATCH 094/160] hide un-recommended parms from 'issue' command. It's recommended to set "cert-file-path" etc via 'installcert' command. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index fe3b7846..5a7827a9 100755 --- a/le.sh +++ b/le.sh @@ -408,7 +408,7 @@ _clearupwebbroot() { issue() { if [ -z "$2" ] ; then - _err "Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no" + _err "Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no" return 1 fi Le_Webroot="$1" From 7a894c4cda8b4b27d47a6b889951546709fa5164 Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 23 Jan 2016 23:04:42 +0800 Subject: [PATCH 095/160] update usage for the new separated features --- README.md | 74 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index dc9dc22f..7069f040 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# le +# le: means simp`Le` Simplest shell script for LetsEncrypt free Certificate client -Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client (https://github.com/letsencrypt/letsencrypt) - +Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client. Just one script, to issue, renew your certificates automatically. Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt. +Do NOT require to be `root/sudoer`. -#Supported OS +#Tested OS 1. Ubuntu/Debian. 2. CentOS @@ -27,21 +27,53 @@ Probably it's the smallest&easiest&smartest shell script to automatically issue ``` ./le.sh install ``` +You don't have to be root then, altough it is recommended. + Which does 3 jobs: * create and copy `le.sh` to your home dir: `~/.le` All the certs will be placed in this folder. -* create symbol link: `/bin/le -> ~/.le/le.sh` +* create symbol link: `/usr/local/bin/le -> ~/.le/le.sh` . (You must be root to do so.) * create everyday cron job to check and renew the cert if needed. Ok, you are ready to issue cert now. Show help message: ``` -root@xvm:~# le -Usage: issue|renew|renewAll|createAccountKey|createDomainKey|createCSR|install|uninstall +root@v1:~# le.sh +https://github.com/Neilpang/le +v1.1.1 +Usage: le.sh [command] ...[args].... +Avalible commands: + +install: + Install le.sh to your system. +issue: + Issue a cert. +installcert: + Install the issued cert to apache/nginx or any other server. +renew: + Renew a cert. +renewAll: + Renew all the certs. +uninstall: + Uninstall le.sh, and uninstall the cron job. +version: + Show version info. +installcronjob: + Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. +uninstallcronjob: + Uninstall the cron job. The 'uninstall' command can do this automatically. +createAccountKey: + Create an account private key, professional use. +createDomainKey: + Create an domain private key, professional use. +createCSR: + Create CSR , professional use. + + +root@v1:~/le# le issue +Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no -root@xvm:~# le issue -Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no ``` @@ -53,8 +85,6 @@ And if you give 'no' to 'cert-file-path', it will not copy the issued cert to th In all the cases, the issued cert will be placed in "~/.le/domain.com/" - - # Just issue a cert: ``` @@ -70,19 +100,19 @@ You must point and bind all the domains to the same webroot dir:`/home/wwwroot/a The cert will be placed in `~/.le/aa.com/` +The issued cert will be renewed every 80 days automatically. -The issued cert will be renewed every 50 days automatically. - - -# Issue a cert, and install to apache/nginx +# Install issued cert to apache/nginx etc. ``` -le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com 2048 /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apahce/nginx "service apache2/nginx reload" +le installcert aa.com /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apahce/nginx "service apache2|nginx reload" ``` -Which issues the cert and then links it to the production apache or nginx path. -The cert will be renewed every 50 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` + +Install the issued cert/key to the production apache or nginx path. + +The cert will be renewed every 80 days by default (which is configurable), Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: ` service apache2 reload` or `service nginx reload` -# Use Standalone server: +# Use Standalone server to issue cert( requires you be root/sudoer, or you have permission to listen tcp 80 port): Same usage as all above, just give `no` as the webroot. The tcp `80` port must be free to listen, otherwise you will be prompted to free the `80` port and try again. @@ -90,14 +120,14 @@ The tcp `80` port must be free to listen, otherwise you will be prompted to free le issue no aa.com www.aa.com,cp.aa.com ``` -# Use Apache mode: +# Use Apache mode(requires you be root/sudoer, since it is required to interact with apache server): If you are running a web server, apache or nginx, it is recommended to use the Webroot mode. Particularly, if you are running an apache server, you can use apache mode instead. Which doesn't write any file to your web root folder. Just set string "apache" to the first argument, it will use apache plugin automatically. ``` -le issue apache aa.com www.aa.com +le issue apache aa.com www.aa.com,user.aa.com ``` All the other arguments are the same with previous. @@ -106,7 +136,7 @@ All the other arguments are the same with previous. Support the latest dns-01 challenge. ``` -le issue dns aa.com www.aa.com +le issue dns aa.com www.aa.com,user.aa.com ``` Use domain api to automatically add dns record is not finished yet. From 34d91b9f97943df897bae76057d628a28cc8e488 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 23 Jan 2016 23:23:44 +0800 Subject: [PATCH 096/160] support fullchain. If the real cert path equals to the ca cert path, we will append the ca cert to the domain cert, which makes it a fullchain. --- le.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 5a7827a9..33652625 100755 --- a/le.sh +++ b/le.sh @@ -862,7 +862,12 @@ installcert() { if [ -f "$Le_RealCACertPath" ] ; then cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak fi - cat "$CA_CERT_PATH" > "$Le_RealCACertPath" + if [ "$Le_RealCACertPath" == "$Le_RealCertPath" ] ; then + echo "" >> "$Le_RealCACertPath" + cat "$CA_CERT_PATH" >> "$Le_RealCACertPath" + else + cat "$CA_CERT_PATH" > "$Le_RealCACertPath" + fi fi From 17c100d6ff02227767f2a15ce23914bcd8f8012f Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 10:03:35 +0800 Subject: [PATCH 097/160] minor, remove unnecessary error message. --- le.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/le.sh b/le.sh index 33652625..f57d7e0b 100755 --- a/le.sh +++ b/le.sh @@ -398,8 +398,7 @@ _clearupwebbroot() { _debug "remove $__webroot/.well-known/acme-challenge/$3" rm -rf "$__webroot/.well-known/acme-challenge/$3" else - _err "removelevel invalid: $2" - return 1 + _info "skip for removelevel:$2" fi return 0 From 18629d0fddb382271a712ecddd5f9884c3f034cd Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 10:28:53 +0800 Subject: [PATCH 098/160] minor, use WORKING_DIR/le.sh to run cronjob. removes the dependency to "which" command. --- le.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/le.sh b/le.sh index f57d7e0b..ec3139a0 100755 --- a/le.sh +++ b/le.sh @@ -888,9 +888,7 @@ installcronjob() { _initpath _info "Installing cron job" if ! crontab -l | grep 'le.sh cron' ; then - if command -v "le.sh" > /dev/null ; then - lesh="$(which le.sh)" - elif [ -f "$WORKING_DIR/le.sh" ] ; then + if [ -f "$WORKING_DIR/le.sh" ] ; then lesh="\"$WORKING_DIR\"/le.sh" else _err "Can not install cronjob, le.sh not found." From a6f744edb1635beaa281d726b571c581369f294f Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 10:40:08 +0800 Subject: [PATCH 099/160] use nmap Ncat for centOS --- le.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index ec3139a0..e4e93cdb 100755 --- a/le.sh +++ b/le.sh @@ -222,11 +222,15 @@ _setopt() { _startserver() { content="$1" + _NC="nc -q 1" + if nc -h | grep "nmap.org/ncat" >/dev/null ; then + _NC="nc" + fi # while true ; do if [ "$DEBUG" ] ; then - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p 80 -vv else - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | nc -q 1 -l -p 80 > /dev/null + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p 80 > /dev/null fi # done } From 8ad71801eb6d542838e5788eb83ea00d623b16c3 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 16:15:53 +0800 Subject: [PATCH 100/160] minor, uninstall le.sh in the workingdir. --- le.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index e4e93cdb..1081a597 100755 --- a/le.sh +++ b/le.sh @@ -978,7 +978,8 @@ uninstall() { $SUDO rm -f /usr/local/bin/le fi fi - + rm -f $WORKING_DIR/le + rm -f $WORKING_DIR/le.sh _info "The keys and certs are in $WORKING_DIR, you can remove them by yourself." } From 5de16116286b1935ee9f6495253e7a54b7d4be19 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 21:16:09 +0800 Subject: [PATCH 101/160] minor fix output message. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 1081a597..5e5757b7 100755 --- a/le.sh +++ b/le.sh @@ -223,7 +223,7 @@ _setopt() { _startserver() { content="$1" _NC="nc -q 1" - if nc -h | grep "nmap.org/ncat" >/dev/null ; then + if nc -h 2>&1 | grep "nmap.org/ncat" >/dev/null ; then _NC="nc" fi # while true ; do From de8090f57e502ab641579bc011a05ff500deb668 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 24 Jan 2016 21:28:44 +0800 Subject: [PATCH 102/160] minor fix error message. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 5e5757b7..ef3b0388 100755 --- a/le.sh +++ b/le.sh @@ -947,7 +947,7 @@ install() { #try install to /bin if is root if [ ! -f /usr/local/bin/le.sh ] ; then #if root - if $SUDO cp le.sh /usr/local/bin/le.sh 2>&1 > /dev/null; then + if $SUDO cp le.sh /usr/local/bin/le.sh > /dev/null 2>&1; then $SUDO chmod 755 /usr/local/bin/le.sh $SUDO ln -s "/usr/local/bin/le.sh" /usr/local/bin/le rm -f $WORKING_DIR/le.sh From a179e5fc979cf752da8a3605739d7c43af5bb71c Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 26 Jan 2016 00:00:09 +0800 Subject: [PATCH 103/160] h2b: my own hex to bin. remove dependency to xxd tool --- le.sh | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/le.sh b/le.sh index ef3b0388..1a635a32 100755 --- a/le.sh +++ b/le.sh @@ -43,6 +43,21 @@ _err() { fi } +_h2b() { + hex=$(cat) + i=1 + j=2 + while [ '1' ] ; do + h=$(printf $hex | cut -c $i-$j) + if [ -z "$h" ] ; then + break; + fi + printf "\x$h" + let "i+=2" + let "j+=2" + done +} + #domain [2048] createAccountKey() { if [ -z "$1" ] ; then @@ -506,11 +521,11 @@ issue() { fi _debug pub_exp "$pub_exp" - e=$(echo $pub_exp | xxd -r -p | base64) + e=$(echo $pub_exp | _h2b | base64) _debug e "$e" modulus=$(openssl rsa -in $ACCOUNT_KEY_PATH -modulus -noout | cut -d '=' -f 2 ) - n=$(echo $modulus| xxd -r -p | base64 -w 0 | _b64 ) + n=$(echo $modulus| _h2b | base64 -w 0 | _b64 ) jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}' @@ -519,7 +534,7 @@ issue() { _debug HEADER "$HEADER" accountkey_json=$(echo -n "$jwk" | sed "s/ //g") - thumbprint=$(echo -n "$accountkey_json" | sha256sum | xxd -r -p | base64 -w 0 | _b64) + thumbprint=$(echo -n "$accountkey_json" | sha256sum | _h2b | base64 -w 0 | _b64) _info "Registering account" @@ -592,7 +607,7 @@ issue() { dnsadded='0' txtdomain="_acme-challenge.$d" _debug txtdomain "$txtdomain" - txt="$(echo -e -n $keyauthorization | sha256sum | xxd -r -p | base64 -w 0 | _b64)" + txt="$(echo -e -n $keyauthorization | sha256sum | _h2b | base64 -w 0 | _b64)" _debug txt "$txt" #dns #1. check use api @@ -935,12 +950,6 @@ install() { _err "CentOs: yum -y install openssl" return 1 fi - - if ! command -v "xxd" > /dev/null ; then - _err "Please install xxd first." - _err "CentOs: yum install vim-common" - return 1 - fi _info "Installing to $WORKING_DIR" From 0fda2a1dfb778e1696fac481a523b7a44d36916a Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 26 Jan 2016 14:22:12 +0800 Subject: [PATCH 104/160] minor, polish install message --- le.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/le.sh b/le.sh index ef3b0388..c10eee5e 100755 --- a/le.sh +++ b/le.sh @@ -241,13 +241,7 @@ _stopserver() { } _initpath() { - - if command -v sudo > /dev/null ; then - if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then - SUDO=sudo - fi - fi - + SUDO="$(command -v sudo | grep -o 'sudo')" if [ -z "$API" ] ; then if [ -z "$STAGE" ] ; then API="$DEFAULT_CA" @@ -917,22 +911,33 @@ uninstallcronjob() { install() { _initpath + + if command -v yum > /dev/null ; then + YUM="1" + INSTALL="$SUDO yum install -y " + elif command -v apt-get > /dev/null ; then + INSTALL="$SUDO apt-get install -y " + fi + if ! command -v "curl" > /dev/null ; then _err "Please install curl first." - _err "Ubuntu: sudo apt-get install curl" - _err "CentOS: yum install curl" + _err "$INSTALL curl" return 1 fi if ! command -v "crontab" > /dev/null ; then _err "Please install crontab first." - _err "CentOs: yum -y install crontabs" + if [ "$YUM" ] ; then + _err "$INSTALL crontabs" + else + _err "$INSTALL crontab" + fi return 1 fi if ! command -v "openssl" > /dev/null ; then _err "Please install openssl first." - _err "CentOs: yum -y install openssl" + _err "$INSTALL openssl" return 1 fi From 0a94c619735f6d1419387f81ce304db15a7d7b60 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 26 Jan 2016 21:04:24 +0800 Subject: [PATCH 105/160] remove dependency to "sha256sum" and "base64" --- le.sh | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/le.sh b/le.sh index 1a635a32..707d6948 100755 --- a/le.sh +++ b/le.sh @@ -58,6 +58,10 @@ _h2b() { done } +_base64() { + openssl base64 -e | tr -d '\n' +} + #domain [2048] createAccountKey() { if [ -z "$1" ] ; then @@ -162,7 +166,7 @@ _send_signed_request() { if [ "$DEBUG" ] ; then CURL="$CURL --trace-ascii $dp " fi - payload64=$(echo -n $payload | base64 -w 0 | _b64) + payload64=$(echo -n $payload | _base64 | _b64) _debug payload64 $payload64 nonceurl="$API/directory" @@ -173,17 +177,17 @@ _send_signed_request() { protected=$(echo -n "$HEADERPLACE" | sed "s/NONCE/$nonce/" ) _debug protected "$protected" - protected64=$( echo -n $protected | base64 -w 0 | _b64) + protected64=$( echo -n $protected | _base64 | _b64) _debug protected64 "$protected64" - sig=$(echo -n "$protected64.$payload64" | openssl dgst -sha256 -sign $ACCOUNT_KEY_PATH | base64 -w 0 | _b64) + sig=$(echo -n "$protected64.$payload64" | openssl dgst -sha256 -sign $ACCOUNT_KEY_PATH | _base64 | _b64) _debug sig "$sig" body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug body "$body" if [ "$needbase64" ] ; then - response="$($CURL -X POST --data "$body" $url | base64 -w 0)" + response="$($CURL -X POST --data "$body" $url | _base64)" else response="$($CURL -X POST --data "$body" $url)" fi @@ -521,11 +525,11 @@ issue() { fi _debug pub_exp "$pub_exp" - e=$(echo $pub_exp | _h2b | base64) + e=$(echo $pub_exp | _h2b | _base64) _debug e "$e" modulus=$(openssl rsa -in $ACCOUNT_KEY_PATH -modulus -noout | cut -d '=' -f 2 ) - n=$(echo $modulus| _h2b | base64 -w 0 | _b64 ) + n=$(echo $modulus| _h2b | _base64 | _b64 ) jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}' @@ -534,7 +538,7 @@ issue() { _debug HEADER "$HEADER" accountkey_json=$(echo -n "$jwk" | sed "s/ //g") - thumbprint=$(echo -n "$accountkey_json" | sha256sum | _h2b | base64 -w 0 | _b64) + thumbprint=$(echo -n "$accountkey_json" | openssl sha -sha256 -binary | _base64 | _b64) _info "Registering account" @@ -607,7 +611,7 @@ issue() { dnsadded='0' txtdomain="_acme-challenge.$d" _debug txtdomain "$txtdomain" - txt="$(echo -e -n $keyauthorization | sha256sum | _h2b | base64 -w 0 | _b64)" + txt="$(echo -e -n $keyauthorization | openssl sha -sha256 -binary | _base64 | _b64)" _debug txt "$txt" #dns #1. check use api @@ -728,7 +732,7 @@ issue() { _clearup _info "Verify finished, start to sign." - der="$(openssl req -in $CSR_PATH -outform DER | base64 -w 0 | _b64)" + der="$(openssl req -in $CSR_PATH -outform DER | _base64 | _b64)" _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" @@ -737,7 +741,7 @@ issue() { if [ "$Le_LinkCert" ] ; then echo -----BEGIN CERTIFICATE----- > "$CERT_PATH" - curl --silent "$Le_LinkCert" | base64 >> "$CERT_PATH" + curl --silent "$Le_LinkCert" | openssl base64 -e >> "$CERT_PATH" echo -----END CERTIFICATE----- >> "$CERT_PATH" _info "Cert success." cat "$CERT_PATH" @@ -747,7 +751,7 @@ issue() { if [ -z "$Le_LinkCert" ] ; then - response="$(echo $response | base64 -d)" + response="$(echo $response | openssl base64 -d)" _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi @@ -759,7 +763,7 @@ issue() { if [ "$Le_LinkIssuer" ] ; then echo -----BEGIN CERTIFICATE----- > "$CA_CERT_PATH" - curl --silent "$Le_LinkIssuer" | base64 >> "$CA_CERT_PATH" + curl --silent "$Le_LinkIssuer" | openssl base64 -e >> "$CA_CERT_PATH" echo -----END CERTIFICATE----- >> "$CA_CERT_PATH" _info "The intermediate CA cert is in $CA_CERT_PATH" fi From b3a2aac82903f73e052a59964e69c5ad5522df64 Mon Sep 17 00:00:00 2001 From: neil Date: Wed, 27 Jan 2016 13:15:12 +0800 Subject: [PATCH 106/160] minor #check if there is sudo installed, AND if the current user is a sudoer. --- le.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 36618686..fe2c30cb 100755 --- a/le.sh +++ b/le.sh @@ -260,7 +260,14 @@ _stopserver() { } _initpath() { - SUDO="$(command -v sudo | grep -o 'sudo')" + + #check if there is sudo installed, AND if the current user is a sudoer. + if command -v sudo > /dev/null ; then + if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then + SUDO=sudo + fi + fi + if [ -z "$API" ] ; then if [ -z "$STAGE" ] ; then API="$DEFAULT_CA" From d4d645908fea676ff014121a0c003e35d0369fc2 Mon Sep 17 00:00:00 2001 From: raunsbaekdk Date: Fri, 29 Jan 2016 23:35:34 +0100 Subject: [PATCH 107/160] Updated DNS description Making it clearer that you are supposed to create the TXT record under the _acme-challenge subdomain --- le.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/le.sh b/le.sh index fe2c30cb..493e4438 100755 --- a/le.sh +++ b/le.sh @@ -616,9 +616,11 @@ issue() { _debug txt "$txt" #dns #1. check use api - _err "Add the following txt record:" - _err "Domain:$txtdomain" - _err "Txt value:$txt" + _err "Add the following TXT record:" + _err "Domain: $txtdomain" + _err "TXT value: $txt" + _err "Please be aware that you prepend _acme-challenge. before your domain" + _err "so the resulting subdomain will be: $txtdomain" #dnsadded='1' fi done @@ -626,7 +628,7 @@ issue() { if [ "$dnsadded" == '0' ] ; then _setopt "$DOMAIN_CONF" "Le_Vlist" "=" "\"$vlist\"" _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit." - _err "Please add the txt records to the domains, and retry again." + _err "Please add the TXT records to the domains, and retry again." return 1 fi From 137f5f8b786cea3bf2dc11aab357680eee884a92 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 10:24:41 +0800 Subject: [PATCH 108/160] minor,add info message. --- le.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/le.sh b/le.sh index 493e4438..6bacce2f 100755 --- a/le.sh +++ b/le.sh @@ -64,6 +64,7 @@ _base64() { #domain [2048] createAccountKey() { + _info "Creating account key" if [ -z "$1" ] ; then echo Usage: $0 account-domain [2048] return @@ -89,6 +90,7 @@ createAccountKey() { #domain length createDomainKey() { + _info "Creating domain key" if [ -z "$1" ] ; then echo Usage: $0 domain [2048] return @@ -120,6 +122,7 @@ createDomainKey() { # domain domainlist createCSR() { + _info "Creating csr" if [ -z "$1" ] ; then echo Usage: $0 domain [domainlist] return From bb2294e76119e454f77cad5321e66502fc532b3b Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 10:39:02 +0800 Subject: [PATCH 109/160] fix issue: do not re-generate domain key when call renew command --- le.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/le.sh b/le.sh index 6bacce2f..f90ebe3e 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.1 +VER=1.1.2 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -104,7 +104,10 @@ createDomainKey() { fi _initpath $domain - if [ -f "$CERT_KEY_PATH" ] && ! [ "$FORCE" ] ; then + if [ ! -f "$CERT_KEY_PATH" ] || ( [ "$FORCE" ] && ! [ "$IS_RENEW" ] ); then + #generate account key + openssl genrsa $length > "$CERT_KEY_PATH" + else if [ "$IS_RENEW" ] ; then _info "Domain key exists, skip" return 0 @@ -113,9 +116,6 @@ createDomainKey() { _err "Set FORCE=1, and try again." return 1 fi - else - #generate account key - openssl genrsa $length > "$CERT_KEY_PATH" fi } From 175c9decd7189f15f47f8175f2cad9656a4dddcf Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 21:00:36 +0800 Subject: [PATCH 110/160] init dnsapi --- dnsapi/dns-cf.sh | 139 +++++++++++++++++++++++++++++++++++++++++++++++ le.sh | 61 ++++++++++++++++----- 2 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 dnsapi/dns-cf.sh diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh new file mode 100644 index 00000000..40987a21 --- /dev/null +++ b/dnsapi/dns-cf.sh @@ -0,0 +1,139 @@ +#!/bin/bash + + +# +#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#CF_Email="xxxx@sss.com" + + +CF_Api="https://api.cloudflare.com/client/v4/" + +#Usage: _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns-cf-add() { + fulldomain=$1 + txtvalue=$2 + + _info "first detect the root zone" + if ! _get_root $fulldomain > /dev/null ; then + _err "invalid domain" + return 1 + fi + + _cf_rest GET "/zones/$_domain_id/dns_records?type=TXT&name=$fulldomain" + + if [ "$?" != "0" ] || ! printf $response | grep \"success\":true > /dev/null ; then + _err "Error" + return 1 + fi + + count=$(printf $response | grep -o \"count\":[^,]* | cut -d : -f 2) + + if [ "$count" == "0" ] ; then + _info "Adding record" + if _cf_rest GET "/zones/$_domain_id/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" ; then + _info "Added, sleeping 10 seconds" + sleep 10 + return 0 + fi + _err "Add txt record error." + else + _info "Updating record" + record_id=$(printf $response | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + _info "record_id" $record_id + + _cf_rest PUT "/zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}" + if [ "$?" == "0" ]; then + _info "Updated, sleeping 10 seconds" + sleep 10 + return 0; + fi + _err "Update error" + return 1 + fi + +} + + +#_acme-challenge.www.domain.com +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=2 + p=1 + while [ '1' ] ; do + h=$(printf $domain | cut -d . -f $i-100) + if [ -z "$h" ] ; then + #not valid + return 1; + fi + + if ! _cf_get "zones?name=$h" ; then + return 1 + fi + + if printf $response | grep \"name\":\"$h\" ; then + _domain_id=$(printf $response | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + if [ "$_domain_id" ] ; then + _sub_domain=$(printf $domain | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + let "i+=1" + done + return 1 +} + + +_cf_rest() { + m=$1 + ep="$2" + echo $ep + if [ "$3" ] ; then + data="--data \"$3\"" + fi + response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json" $data)" + if [ "$?" != "0" ] ; then + echo $error $ep + return 1 + fi + echo $response + return 0 +} + + +_debug() { + + if [ -z "$DEBUG" ] ; then + return + fi + + if [ -z "$2" ] ; then + echo $1 + else + echo "$1"="$2" + fi +} + +_info() { + if [ -z "$2" ] ; then + echo "$1" + else + echo "$1"="$2" + fi +} + +_err() { + if [ -z "$2" ] ; then + echo "$1" >&2 + else + echo "$1"="$2" >&2 + fi +} + + diff --git a/le.sh b/le.sh index f90ebe3e..8e70b04f 100755 --- a/le.sh +++ b/le.sh @@ -320,7 +320,6 @@ _initpath() { if [ -z "$CA_CERT_PATH" ] ; then CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" fi - } @@ -619,12 +618,44 @@ issue() { _debug txt "$txt" #dns #1. check use api - _err "Add the following TXT record:" - _err "Domain: $txtdomain" - _err "TXT value: $txt" - _err "Please be aware that you prepend _acme-challenge. before your domain" - _err "so the resulting subdomain will be: $txtdomain" - #dnsadded='1' + d_api="" + if [ -f "$WORKING_DIR/$d/$Le_Webroot" ] ; then + d_api="$WORKING_DIR/$d/$Le_Webroot" + elif [ -f "$WORKING_DIR/$d/$Le_Webroot.sh" ] ; then + d_api="$WORKING_DIR/$d/$Le_Webroot.sh" + elif [ -f "$WORKING_DIR/$Le_Webroot" ] ; then + d_api="$WORKING_DIR/$Le_Webroot" + elif [ -f "$WORKING_DIR/$Le_Webroot.sh" ] ; then + d_api="$WORKING_DIR/$Le_Webroot.sh" + fi + + if [ "$d_api" ]; then + _info "Found domain api file: $d_api" + else + _err "Add the following TXT record:" + _err "Domain: $txtdomain" + _err "TXT value: $txt" + _err "Please be aware that you prepend _acme-challenge. before your domain" + _err "so the resulting subdomain will be: $txtdomain" + continue + fi + + if ! source $d_api ; then + _err "Load file $d_api error. Please check your api file and try again." + return 1 + fi + + addcommand="$Le_Webroot-add" + if ! command -v $addcommand ; then + _err "It seems that your api file is not correct, it must have a function named: $Le_Webroot" + return 1 + fi + + if ! $addcommand $txtdomain $txt ; then + _err "Error add txt for domain:$txtdomain" + return 1 + fi + dnsadded='1' fi done @@ -806,13 +837,17 @@ renew() { _initpath $Le_Domain - if [ -f "$DOMAIN_CONF" ] ; then - source "$DOMAIN_CONF" - if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then - _info "Skip, Next renewal time is: $Le_NextRenewTimeStr" - return 2 - fi + if [ ! -f "$DOMAIN_CONF" ] ; then + _err "$Le_Domain is not a issued domain, skip." + return 1; + fi + + source "$DOMAIN_CONF" + if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then + _info "Skip, Next renewal time is: $Le_NextRenewTimeStr" + return 2 fi + IS_RENEW="1" issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" IS_RENEW="" From 23bcf2c62371cbe669d1f8e7cec635eb02c15aab Mon Sep 17 00:00:00 2001 From: root Date: Sat, 30 Jan 2016 16:16:29 +0300 Subject: [PATCH 111/160] exec --- dnsapi/dns-cf.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dnsapi/dns-cf.sh diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh old mode 100644 new mode 100755 From b4a156da60d8a6d08c43185b4beb8696976b57e2 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 21:08:43 +0800 Subject: [PATCH 112/160] install dnsapi --- le.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/le.sh b/le.sh index 8e70b04f..d8a4a81f 100755 --- a/le.sh +++ b/le.sh @@ -1028,6 +1028,8 @@ install() { rm -f $WORKING_DIR/le ln -s $WORKING_DIR/le.sh $WORKING_DIR/le + cp -r dnsapi $WORKING_DIR/dnsapi + installcronjob _info OK From 611f6877392d56945d2037c916d981c7b68654da Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 21:14:41 +0800 Subject: [PATCH 113/160] dnsapi folder --- le.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/le.sh b/le.sh index d8a4a81f..c4437822 100755 --- a/le.sh +++ b/le.sh @@ -627,7 +627,12 @@ issue() { d_api="$WORKING_DIR/$Le_Webroot" elif [ -f "$WORKING_DIR/$Le_Webroot.sh" ] ; then d_api="$WORKING_DIR/$Le_Webroot.sh" + elif [ -f "$WORKING_DIR/dnsapi/$Le_Webroot" ] ; then + d_api="$WORKING_DIR/dnsapi/$Le_Webroot" + elif [ -f "$WORKING_DIR/dnsapi/$Le_Webroot.sh" ] ; then + d_api="$WORKING_DIR/dnsapi/$Le_Webroot.sh" fi + _debug d_api "$d_api" if [ "$d_api" ]; then _info "Found domain api file: $d_api" From 1b5bd0e03ef7749d3b32f358e815f44dbfc453b0 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 22:11:09 +0800 Subject: [PATCH 114/160] minor fix --- dnsapi/dns-cf.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index 40987a21..547a077b 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -14,12 +14,13 @@ dns-cf-add() { fulldomain=$1 txtvalue=$2 - _info "first detect the root zone" - if ! _get_root $fulldomain > /dev/null ; then + _debug "First detect the root zone" + if ! _get_root $fulldomain ; then _err "invalid domain" return 1 fi + _debug "Getting txt records" _cf_rest GET "/zones/$_domain_id/dns_records?type=TXT&name=$fulldomain" if [ "$?" != "0" ] || ! printf $response | grep \"success\":true > /dev/null ; then @@ -31,7 +32,7 @@ dns-cf-add() { if [ "$count" == "0" ] ; then _info "Adding record" - if _cf_rest GET "/zones/$_domain_id/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" ; then + if _cf_rest POST "/zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then _info "Added, sleeping 10 seconds" sleep 10 return 0 @@ -56,6 +57,7 @@ dns-cf-add() { #_acme-challenge.www.domain.com +#returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg @@ -70,7 +72,7 @@ _get_root() { return 1; fi - if ! _cf_get "zones?name=$h" ; then + if ! _cf_rest GET "zones?name=$h" ; then return 1 fi @@ -95,7 +97,8 @@ _cf_rest() { ep="$2" echo $ep if [ "$3" ] ; then - data="--data \"$3\"" + data="--data \'$3\'" + _debug data "$data" fi response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json" $data)" if [ "$?" != "0" ] ; then From 638b9a0559d50088db1888b8d97e53e4a7c1786b Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 22:34:35 +0800 Subject: [PATCH 115/160] fix bugs --- dnsapi/dns-cf.sh | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index 547a077b..ac3643c4 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -9,7 +9,9 @@ CF_Api="https://api.cloudflare.com/client/v4/" -#Usage: _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns-cf-add() { fulldomain=$1 txtvalue=$2 @@ -33,15 +35,20 @@ dns-cf-add() { if [ "$count" == "0" ] ; then _info "Adding record" if _cf_rest POST "/zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - _info "Added, sleeping 10 seconds" - sleep 10 - return 0 + if printf $response | grep $fulldomain > /dev/null ; then + _info "Added, sleeping 10 seconds" + sleep 1 + return 0 + else + _err "Add txt record error." + return 1 + fi fi _err "Add txt record error." else _info "Updating record" record_id=$(printf $response | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") - _info "record_id" $record_id + _debug "record_id" $record_id _cf_rest PUT "/zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}" if [ "$?" == "0" ]; then @@ -56,6 +63,10 @@ dns-cf-add() { } + + + +#################### Private functions bellow ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -95,17 +106,20 @@ _get_root() { _cf_rest() { m=$1 ep="$2" - echo $ep + _debug $ep if [ "$3" ] ; then - data="--data \'$3\'" + data="$3" _debug data "$data" + response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json" --data $data)" + else + response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json")" fi - response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json" $data)" + if [ "$?" != "0" ] ; then - echo $error $ep + _err "error $ep" return 1 fi - echo $response + _debug response "$response" return 0 } From 0ed4c9391e2956769bdb1be64c8a05df1e312293 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 22:47:22 +0800 Subject: [PATCH 116/160] sleep to wait dns record to take effect. --- dnsapi/dns-cf.sh | 4 +++- le.sh | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index ac3643c4..888e9b3f 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -37,7 +37,8 @@ dns-cf-add() { if _cf_rest POST "/zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if printf $response | grep $fulldomain > /dev/null ; then _info "Added, sleeping 10 seconds" - sleep 1 + sleep 10 + #todo: check if the record takes effect return 0 else _err "Add txt record error." @@ -54,6 +55,7 @@ dns-cf-add() { if [ "$?" == "0" ]; then _info "Updated, sleeping 10 seconds" sleep 10 + #todo: check if the record takes effect return 0; fi _err "Update error" diff --git a/le.sh b/le.sh index c4437822..a6122418 100755 --- a/le.sh +++ b/le.sh @@ -673,6 +673,10 @@ issue() { fi + if [ "$dnsadded" == '1' ] ; then + _info "Sleep 60 seconds for the txt records to take effect" + sleep 60 + fi _debug "ok, let's start to verify" ventries=$(echo "$vlist" | sed "s/,/ /g") From a28b3a653cdb8fd9cf041365ae6e488d8bbd07ad Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 22:51:36 +0800 Subject: [PATCH 117/160] install dnsapi --- le.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index a6122418..768efcf7 100755 --- a/le.sh +++ b/le.sh @@ -1037,7 +1037,8 @@ install() { rm -f $WORKING_DIR/le ln -s $WORKING_DIR/le.sh $WORKING_DIR/le - cp -r dnsapi $WORKING_DIR/dnsapi + mkdir -p $WORKING_DIR/dnsapi + cp dnsapi/* $WORKING_DIR/dnsapi/ installcronjob From ab49796192c752ec10253ae9a6b1bb308f65e97d Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 30 Jan 2016 23:15:30 +0800 Subject: [PATCH 118/160] how to use cloudflare api and create custom api --- README.md | 45 ++++++++++++++++++++++++++++++--- dnsapi/dns-cf.sh | 6 +++++ dnsapi/dns-myapi.sh | 61 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 dnsapi/dns-myapi.sh diff --git a/README.md b/README.md index 7069f040..f3e18b7e 100644 --- a/README.md +++ b/README.md @@ -139,9 +139,6 @@ Support the latest dns-01 challenge. le issue dns aa.com www.aa.com,user.aa.com ``` -Use domain api to automatically add dns record is not finished yet. -So, you must manually add the txt record to finish verifying. - You will get the output like bellow: ``` Add the following txt record: @@ -165,6 +162,48 @@ Ok, it's finished. +# Use CloudFlare domain api to automatically issue cert + +For now, we support clourflare integeration. + +First you need to login to your clourflare account to get you apikey. + +Then open `~/.le/dnsapi/dns-cf.sh`, and fill your api key and email there: +and uncomment the lines: +``` +CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" + +CF_Email="xxxx@sss.com" + +``` + +Ok, let's issue cert now: +``` +le.sh issue dns-cf aa.com www.aa.com +``` + +More api integerations are coming. Godaddy, Dnspod, etc.... + + +# Use custom api + +If your api is not supported yet, you can write your own dns api. + +Let's assume you want to name it 'myapi', + +1. Create a bash script named `~/.le/dns-myapi.sh`, +2. In the scrypt, you must have a function named `dns-myapi-add()`. Which will be called by le.sh to add dns records. +3. Then you can use your api to issue cert like: + +``` +le.sh issue dns-myapi aa.com www.aa.com +``` + +For more details, please check our sample script: `dnsapi/dns-myapi.sh` + + + + #Under the Hood Speak ACME language with bash directly to Let's encrypt. diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index 888e9b3f..7cb086b8 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -16,6 +16,12 @@ dns-cf-add() { fulldomain=$1 txtvalue=$2 + if [ -z "$CF_Key" ] || [ -z "$CF_Email" ] ; then + _err "You don't specify cloudflare api key and email yet." + _err "Please create you key and try again." + return 1 + fi + _debug "First detect the root zone" if ! _get_root $fulldomain ; then _err "invalid domain" diff --git a/dnsapi/dns-myapi.sh b/dnsapi/dns-myapi.sh new file mode 100644 index 00000000..463a44c9 --- /dev/null +++ b/dnsapi/dns-myapi.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +#Here is a sample custom api script. +#This file name is "dhs-myapi.sh" +#So, here must be a method dhs-myapi-add() +#Which will be called by le.sh to add the txt record to your api system. +#returns 0 meanst success, otherwise error. + + + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns-myapi-add() { + fulldomain=$1 + txtvalue=$2 + _err "Not implemented!" + return 1; +} + + + + + + + + + +#################### Private functions bellow ################################## + + +_debug() { + + if [ -z "$DEBUG" ] ; then + return + fi + + if [ -z "$2" ] ; then + echo $1 + else + echo "$1"="$2" + fi +} + +_info() { + if [ -z "$2" ] ; then + echo "$1" + else + echo "$1"="$2" + fi +} + +_err() { + if [ -z "$2" ] ; then + echo "$1" >&2 + else + echo "$1"="$2" >&2 + fi +} + + From e9209938cb292203154423a68ce26c0df54058b3 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 16:30:01 +0800 Subject: [PATCH 119/160] support account config file to save dns api key --- dnsapi/dns-cf.sh | 4 ++++ le.sh | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index 7cb086b8..b1e4b47d 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -22,6 +22,10 @@ dns-cf-add() { return 1 fi + #save the api key and email to the account conf file. + _saveaccountconf CF_Key "$CF_Key" + _saveaccountconf CF_Email "$CF_Email" + _debug "First detect the root zone" if ! _get_root $fulldomain ; then _err "invalid domain" diff --git a/le.sh b/le.sh index 768efcf7..1646d6f2 100755 --- a/le.sh +++ b/le.sh @@ -242,6 +242,29 @@ _setopt() { _debug "$(grep -H -n "^$__opt$__sep" $__conf)" } +#_savedomainconf key value +#save to domain.conf +_savedomainconf() { + key="$1" + value="$2" + if [ "$DOMAIN_CONF" ] ; then + _setopt $DOMAIN_CONF "$key" "=" "$value" + else + _debug "DOMAIN_CONF is empty, can not save $key=$value" + fi +} + +#_saveaccountconf key value +_saveaccountconf() { + key="$1" + value="$2" + if [ "$ACCOUNT_CONF_PATH" ] ; then + _setopt $ACCOUNT_CONF_PATH "$key" "=" "$value" + else + _debug "ACCOUNT_CONF_PATH is empty, can not save $key=$value" + fi +} + _startserver() { content="$1" _NC="nc -q 1" @@ -296,7 +319,15 @@ _initpath() { mkdir -p "$WORKING_DIR" if [ -z "$ACCOUNT_KEY_PATH" ] ; then - ACCOUNT_KEY_PATH="$WORKING_DIR/account.acc" + ACCOUNT_KEY_PATH="$WORKING_DIR/account.key" + fi + + if [ -z "$ACCOUNT_CONF_PATH" ] ; then + ACCOUNT_CONF_PATH="$WORKING_DIR/account.conf" + fi + + if [ -f "$ACCOUNT_CONF_PATH" ] ; then + source "$ACCOUNT_CONF_PATH" fi if [ -z "$domain" ] ; then From b25d22b05a6e199817e3baf42de31dd2b908b1ee Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 16:38:52 +0800 Subject: [PATCH 120/160] keep compatible --- le.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 1646d6f2..b2d7f1f9 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.2 +VER=1.1.3 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -1071,6 +1071,11 @@ install() { mkdir -p $WORKING_DIR/dnsapi cp dnsapi/* $WORKING_DIR/dnsapi/ + #to keep compatible mv the .acc file to .key file + if [ -f "$WORKING_DIR/account.acc" ] ; then + mv "$WORKING_DIR/account.acc" "$WORKING_DIR/account.key" + fi + installcronjob _info OK From a925dd56b7acc3436da41cd55039f0dd7ec0dfdd Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 31 Jan 2016 16:48:21 +0800 Subject: [PATCH 121/160] new usage of cloudflare api key --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f3e18b7e..dc090f3b 100644 --- a/README.md +++ b/README.md @@ -166,14 +166,12 @@ Ok, it's finished. For now, we support clourflare integeration. -First you need to login to your clourflare account to get you apikey. +First you need to login to your clourflare account to get your api key. -Then open `~/.le/dnsapi/dns-cf.sh`, and fill your api key and email there: -and uncomment the lines: ``` -CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" -CF_Email="xxxx@sss.com" +export CF_Email="xxxx@sss.com" ``` @@ -182,6 +180,9 @@ Ok, let's issue cert now: le.sh issue dns-cf aa.com www.aa.com ``` +The `CF_Key` and `CF_Email` will be saved in `~/.le/account.conf`, when next time you use cloudflare api, it will reuse this key. + + More api integerations are coming. Godaddy, Dnspod, etc.... From 525997ee045ac2f75634bbe2396a3ecce0299183 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 17:15:02 +0800 Subject: [PATCH 122/160] do not write /usr/loca/bin/ anymore. install to home. --- le.sh | 164 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 63 deletions(-) diff --git a/le.sh b/le.sh index b2d7f1f9..2bb78f0c 100755 --- a/le.sh +++ b/le.sh @@ -163,8 +163,8 @@ _send_signed_request() { _debug url $url _debug payload "$payload" - CURL_HEADER="$WORKING_DIR/curl.header" - dp="$WORKING_DIR/curl.dump" + CURL_HEADER="$LE_WORKING_DIR/curl.header" + dp="$LE_WORKING_DIR/curl.dump" CURL="curl --silent --dump-header $CURL_HEADER " if [ "$DEBUG" ] ; then CURL="$CURL --trace-ascii $dp " @@ -303,8 +303,8 @@ _initpath() { fi fi - if [ -z "$WORKING_DIR" ]; then - WORKING_DIR=$HOME/.le + if [ -z "$LE_WORKING_DIR" ]; then + LE_WORKING_DIR=$HOME/.le fi if [ -z "$ACME_DIR" ] ; then @@ -312,18 +312,18 @@ _initpath() { fi if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then - APACHE_CONF_BACKUP_DIR="$WORKING_DIR/" + APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR/" fi domain="$1" - mkdir -p "$WORKING_DIR" + mkdir -p "$LE_WORKING_DIR" if [ -z "$ACCOUNT_KEY_PATH" ] ; then - ACCOUNT_KEY_PATH="$WORKING_DIR/account.key" + ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key" fi if [ -z "$ACCOUNT_CONF_PATH" ] ; then - ACCOUNT_CONF_PATH="$WORKING_DIR/account.conf" + ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf" fi if [ -f "$ACCOUNT_CONF_PATH" ] ; then @@ -334,22 +334,22 @@ _initpath() { return 0 fi - mkdir -p "$WORKING_DIR/$domain" + mkdir -p "$LE_WORKING_DIR/$domain" if [ -z "$DOMAIN_CONF" ] ; then - DOMAIN_CONF="$WORKING_DIR/$domain/$Le_Domain.conf" + DOMAIN_CONF="$LE_WORKING_DIR/$domain/$Le_Domain.conf" fi if [ -z "$CSR_PATH" ] ; then - CSR_PATH="$WORKING_DIR/$domain/$domain.csr" + CSR_PATH="$LE_WORKING_DIR/$domain/$domain.csr" fi if [ -z "$CERT_KEY_PATH" ] ; then - CERT_KEY_PATH="$WORKING_DIR/$domain/$domain.key" + CERT_KEY_PATH="$LE_WORKING_DIR/$domain/$domain.key" fi if [ -z "$CERT_PATH" ] ; then - CERT_PATH="$WORKING_DIR/$domain/$domain.cer" + CERT_PATH="$LE_WORKING_DIR/$domain/$domain.cer" fi if [ -z "$CA_CERT_PATH" ] ; then - CA_CERT_PATH="$WORKING_DIR/$domain/ca.cer" + CA_CERT_PATH="$LE_WORKING_DIR/$domain/ca.cer" fi } @@ -584,7 +584,7 @@ issue() { if [ "$code" == "" ] || [ "$code" == '201' ] ; then _info "Registered" - echo $response > $WORKING_DIR/account.json + echo $response > $LE_WORKING_DIR/account.json elif [ "$code" == '409' ] ; then _info "Already registered" else @@ -650,18 +650,18 @@ issue() { #dns #1. check use api d_api="" - if [ -f "$WORKING_DIR/$d/$Le_Webroot" ] ; then - d_api="$WORKING_DIR/$d/$Le_Webroot" - elif [ -f "$WORKING_DIR/$d/$Le_Webroot.sh" ] ; then - d_api="$WORKING_DIR/$d/$Le_Webroot.sh" - elif [ -f "$WORKING_DIR/$Le_Webroot" ] ; then - d_api="$WORKING_DIR/$Le_Webroot" - elif [ -f "$WORKING_DIR/$Le_Webroot.sh" ] ; then - d_api="$WORKING_DIR/$Le_Webroot.sh" - elif [ -f "$WORKING_DIR/dnsapi/$Le_Webroot" ] ; then - d_api="$WORKING_DIR/dnsapi/$Le_Webroot" - elif [ -f "$WORKING_DIR/dnsapi/$Le_Webroot.sh" ] ; then - d_api="$WORKING_DIR/dnsapi/$Le_Webroot.sh" + if [ -f "$LE_WORKING_DIR/$d/$Le_Webroot" ] ; then + d_api="$LE_WORKING_DIR/$d/$Le_Webroot" + elif [ -f "$LE_WORKING_DIR/$d/$Le_Webroot.sh" ] ; then + d_api="$LE_WORKING_DIR/$d/$Le_Webroot.sh" + elif [ -f "$LE_WORKING_DIR/$Le_Webroot" ] ; then + d_api="$LE_WORKING_DIR/$Le_Webroot" + elif [ -f "$LE_WORKING_DIR/$Le_Webroot.sh" ] ; then + d_api="$LE_WORKING_DIR/$Le_Webroot.sh" + elif [ -f "$LE_WORKING_DIR/dnsapi/$Le_Webroot" ] ; then + d_api="$LE_WORKING_DIR/dnsapi/$Le_Webroot" + elif [ -f "$LE_WORKING_DIR/dnsapi/$Le_Webroot.sh" ] ; then + d_api="$LE_WORKING_DIR/dnsapi/$Le_Webroot.sh" fi _debug d_api "$d_api" @@ -897,7 +897,7 @@ renewAll() { _initpath _info "renewAll" - for d in $(ls -F $WORKING_DIR | grep '/$') ; do + for d in $(ls -F $LE_WORKING_DIR | grep '/$') ; do d=$(echo $d | cut -d '/' -f 1) _info "renew $d" @@ -992,13 +992,13 @@ installcronjob() { _initpath _info "Installing cron job" if ! crontab -l | grep 'le.sh cron' ; then - if [ -f "$WORKING_DIR/le.sh" ] ; then - lesh="\"$WORKING_DIR\"/le.sh" + if [ -f "$LE_WORKING_DIR/le.sh" ] ; then + lesh="\"$LE_WORKING_DIR\"/le.sh" else _err "Can not install cronjob, le.sh not found." return 1 fi - crontab -l | { cat; echo "0 0 * * * $SUDO WORKING_DIR=\"$WORKING_DIR\" $lesh cron > /dev/null"; } | crontab - + crontab -l | { cat; echo "0 0 * * * $SUDO LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"; } | crontab - fi return 0 } @@ -1008,13 +1008,53 @@ uninstallcronjob() { cr="$(crontab -l | grep 'le.sh cron')" if [ "$cr" ] ; then crontab -l | sed "/le.sh cron/d" | crontab - - WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 7 | cut -d '=' -f 2 | tr -d '"')" - _info WORKING_DIR "$WORKING_DIR" + LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 7 | cut -d '=' -f 2 | tr -d '"')" + _info LE_WORKING_DIR "$LE_WORKING_DIR" fi _initpath } + +# Detect profile file if not specified as environment variable +_detect_profile() { + if [ -n "$PROFILE" -a -f "$PROFILE" ]; then + echo "$PROFILE" + return + fi + + local DETECTED_PROFILE + DETECTED_PROFILE='' + local SHELLTYPE + SHELLTYPE="$(basename "/$SHELL")" + + if [ "$SHELLTYPE" = "bash" ]; then + if [ -f "$HOME/.bashrc" ]; then + DETECTED_PROFILE="$HOME/.bashrc" + elif [ -f "$HOME/.bash_profile" ]; then + DETECTED_PROFILE="$HOME/.bash_profile" + fi + elif [ "$SHELLTYPE" = "zsh" ]; then + DETECTED_PROFILE="$HOME/.zshrc" + fi + + if [ -z "$DETECTED_PROFILE" ]; then + if [ -f "$HOME/.profile" ]; then + DETECTED_PROFILE="$HOME/.profile" + elif [ -f "$HOME/.bashrc" ]; then + DETECTED_PROFILE="$HOME/.bashrc" + elif [ -f "$HOME/.bash_profile" ]; then + DETECTED_PROFILE="$HOME/.bash_profile" + elif [ -f "$HOME/.zshrc" ]; then + DETECTED_PROFILE="$HOME/.zshrc" + fi + fi + + if [ ! -z "$DETECTED_PROFILE" ]; then + echo "$DETECTED_PROFILE" + fi +} + install() { _initpath @@ -1047,33 +1087,33 @@ install() { return 1 fi - _info "Installing to $WORKING_DIR" + _info "Installing to $LE_WORKING_DIR" - #try install to /bin if is root - if [ ! -f /usr/local/bin/le.sh ] ; then - #if root - if $SUDO cp le.sh /usr/local/bin/le.sh > /dev/null 2>&1; then - $SUDO chmod 755 /usr/local/bin/le.sh - $SUDO ln -s "/usr/local/bin/le.sh" /usr/local/bin/le - rm -f $WORKING_DIR/le.sh - $SUDO ln -s /usr/local/bin/le.sh $WORKING_DIR/le.sh - _info "Installed to /usr/local/bin/le" - else - #install to home, for non root user - cp le.sh $WORKING_DIR/ - chmod +x $WORKING_DIR/le.sh - _info "Installed to $WORKING_DIR/le.sh" - fi + _info "Installed to $LE_WORKING_DIR/le.sh" + cp le.sh $LE_WORKING_DIR/ + chmod +x $LE_WORKING_DIR/le.sh + + _profile="$(_detect_profile)" + if [ "$_profile" ] ; then + _debug "Found profile: $_profile" + + echo "LE_WORKING_DIR=$LE_WORKING_DIR +alias le=\"$LE_WORKING_DIR/le.sh\" +alias le.sh=\"$LE_WORKING_DIR/le.sh\" + " > "$LE_WORKING_DIR/le.env" + + _setopt "$_profile" "source \"$LE_WORKING_DIR/le.env\"" + + else + _info "No profile is found, you will need to go into $LE_WORKING_DIR to use le.sh" fi - rm -f $WORKING_DIR/le - ln -s $WORKING_DIR/le.sh $WORKING_DIR/le - mkdir -p $WORKING_DIR/dnsapi - cp dnsapi/* $WORKING_DIR/dnsapi/ + mkdir -p $LE_WORKING_DIR/dnsapi + cp dnsapi/* $LE_WORKING_DIR/dnsapi/ #to keep compatible mv the .acc file to .key file - if [ -f "$WORKING_DIR/account.acc" ] ; then - mv "$WORKING_DIR/account.acc" "$WORKING_DIR/account.key" + if [ -f "$LE_WORKING_DIR/account.acc" ] ; then + mv "$LE_WORKING_DIR/account.acc" "$LE_WORKING_DIR/account.key" fi installcronjob @@ -1085,15 +1125,13 @@ uninstall() { uninstallcronjob _initpath - if [ -f "/usr/local/bin/le.sh" ] ; then - _info "Removing /usr/local/bin/le.sh" - if $SUDO rm -f /usr/local/bin/le.sh ; then - $SUDO rm -f /usr/local/bin/le - fi + _profile="$(_detect_profile)" + if [ "$_profile" ] ; then + sed -i "/source \"$LE_WORKING_DIR/le.env\"/d" "$_profile" fi - rm -f $WORKING_DIR/le - rm -f $WORKING_DIR/le.sh - _info "The keys and certs are in $WORKING_DIR, you can remove them by yourself." + + rm -f $LE_WORKING_DIR/le.sh + _info "The keys and certs are in $LE_WORKING_DIR, you can remove them by yourself." } From 4ac8d68d060eff0504570643436d3058ff5348fc Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 17:38:44 +0800 Subject: [PATCH 123/160] uninstall alias --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 2bb78f0c..670000e2 100755 --- a/le.sh +++ b/le.sh @@ -1127,7 +1127,7 @@ uninstall() { _profile="$(_detect_profile)" if [ "$_profile" ] ; then - sed -i "/source \"$LE_WORKING_DIR/le.env\"/d" "$_profile" + sed -i /le.env/d "$_profile" fi rm -f $LE_WORKING_DIR/le.sh From bf3135ef847c8d6c7f5403a4dd19a413af732d87 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 17:43:32 +0800 Subject: [PATCH 124/160] add alias after install --- le.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 670000e2..faf4ae2e 100755 --- a/le.sh +++ b/le.sh @@ -1092,7 +1092,10 @@ install() { _info "Installed to $LE_WORKING_DIR/le.sh" cp le.sh $LE_WORKING_DIR/ chmod +x $LE_WORKING_DIR/le.sh - + + alias le=\"$LE_WORKING_DIR/le.sh\" + alias le.sh=\"$LE_WORKING_DIR/le.sh\" + _profile="$(_detect_profile)" if [ "$_profile" ] ; then _debug "Found profile: $_profile" From b4e2604c824482289c65afde7f4bd795baa88b1d Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 31 Jan 2016 17:53:19 +0800 Subject: [PATCH 125/160] info: Close and reopen your terminal to start using le --- le.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/le.sh b/le.sh index faf4ae2e..f21cc52d 100755 --- a/le.sh +++ b/le.sh @@ -1093,9 +1093,6 @@ install() { cp le.sh $LE_WORKING_DIR/ chmod +x $LE_WORKING_DIR/le.sh - alias le=\"$LE_WORKING_DIR/le.sh\" - alias le.sh=\"$LE_WORKING_DIR/le.sh\" - _profile="$(_detect_profile)" if [ "$_profile" ] ; then _debug "Found profile: $_profile" @@ -1106,7 +1103,7 @@ alias le.sh=\"$LE_WORKING_DIR/le.sh\" " > "$LE_WORKING_DIR/le.env" _setopt "$_profile" "source \"$LE_WORKING_DIR/le.env\"" - + _info "OK, Close and reopen your terminal to start using le" else _info "No profile is found, you will need to go into $LE_WORKING_DIR to use le.sh" fi From 9b292d584e1eeec854fae38d8814e43d1dcb83c7 Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 31 Jan 2016 23:48:23 +0800 Subject: [PATCH 126/160] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc090f3b..f9ab7311 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ You don't have to be root then, altough it is recommended. Which does 3 jobs: * create and copy `le.sh` to your home dir: `~/.le` All the certs will be placed in this folder. -* create symbol link: `/usr/local/bin/le -> ~/.le/le.sh` . (You must be root to do so.) +* create alias : `le.sh=~/.le/le.sh` and `le=~/.le/le.sh`. * create everyday cron job to check and renew the cert if needed. +After install, you must close current terminal and reopen again to make the alias take effect. Ok, you are ready to issue cert now. Show help message: From 55ce170842339aa4ac8734623c307c1931125e85 Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 1 Feb 2016 09:39:58 +0800 Subject: [PATCH 127/160] remove unnecessary error message if the folder is not a domain remove unnecessary error message if the folder is not a domain. The error message output made the cron send mail. --- le.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/le.sh b/le.sh index f21cc52d..52cb4c6e 100755 --- a/le.sh +++ b/le.sh @@ -878,8 +878,8 @@ renew() { _initpath $Le_Domain if [ ! -f "$DOMAIN_CONF" ] ; then - _err "$Le_Domain is not a issued domain, skip." - return 1; + _info "$Le_Domain is not a issued domain, skip." + return 0; fi source "$DOMAIN_CONF" From 94917d1e0b969b7d1f6f95f126affc08ac825861 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 2 Feb 2016 13:05:54 +0800 Subject: [PATCH 128/160] load account.conf at a earlier time, so that "STAGE" macro can be placed in the conf file. --- le.sh | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/le.sh b/le.sh index 52cb4c6e..6603c3c6 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.3 +VER=1.1.4 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -293,6 +293,18 @@ _initpath() { SUDO=sudo fi fi + + if [ -z "$LE_WORKING_DIR" ]; then + LE_WORKING_DIR=$HOME/.le + fi + + if [ -z "$ACCOUNT_CONF_PATH" ] ; then + ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf" + fi + + if [ -f "$ACCOUNT_CONF_PATH" ] ; then + source "$ACCOUNT_CONF_PATH" + fi if [ -z "$API" ] ; then if [ -z "$STAGE" ] ; then @@ -303,10 +315,6 @@ _initpath() { fi fi - if [ -z "$LE_WORKING_DIR" ]; then - LE_WORKING_DIR=$HOME/.le - fi - if [ -z "$ACME_DIR" ] ; then ACME_DIR="/home/.acme" fi @@ -322,13 +330,7 @@ _initpath() { ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key" fi - if [ -z "$ACCOUNT_CONF_PATH" ] ; then - ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf" - fi - - if [ -f "$ACCOUNT_CONF_PATH" ] ; then - source "$ACCOUNT_CONF_PATH" - fi + if [ -z "$domain" ] ; then return 0 @@ -829,7 +831,7 @@ issue() { if [ -z "$Le_LinkCert" ] ; then response="$(echo $response | openssl base64 -d)" - _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" + _err "Sign failed: $response" return 1 fi From 4e76098bdc83c83a3016467c8bed853f9d5266e6 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 2 Feb 2016 13:08:41 +0800 Subject: [PATCH 129/160] Support Windows --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f9ab7311..98c5953e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Do NOT require to be `root/sudoer`. #Tested OS 1. Ubuntu/Debian. 2. CentOS +3. Windows (cygwin with curl, openssl and crontab included) #Supported Mode From 36d5db158a7ff6cd79fd1ffc91d30cc91648ab79 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 2 Feb 2016 13:49:09 +0800 Subject: [PATCH 130/160] typos --- dnsapi/dns-myapi.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns-myapi.sh b/dnsapi/dns-myapi.sh index 463a44c9..af7dda7a 100644 --- a/dnsapi/dns-myapi.sh +++ b/dnsapi/dns-myapi.sh @@ -1,8 +1,8 @@ #!/bin/bash #Here is a sample custom api script. -#This file name is "dhs-myapi.sh" -#So, here must be a method dhs-myapi-add() +#This file name is "dns-myapi.sh" +#So, here must be a method dns-myapi-add() #Which will be called by le.sh to add the txt record to your api system. #returns 0 meanst success, otherwise error. From d0064bc30e83dbe45a8b1e80080b469573ebe9f1 Mon Sep 17 00:00:00 2001 From: dawsonbotsford Date: Tue, 2 Feb 2016 00:34:56 -0700 Subject: [PATCH 131/160] Spelling correction automated from the Spelling Bee --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98c5953e..f7b89cca 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Do NOT require to be `root/sudoer`. ``` ./le.sh install ``` -You don't have to be root then, altough it is recommended. +You don't have to be root then, although it is recommended. Which does 3 jobs: * create and copy `le.sh` to your home dir: `~/.le` @@ -45,7 +45,7 @@ root@v1:~# le.sh https://github.com/Neilpang/le v1.1.1 Usage: le.sh [command] ...[args].... -Avalible commands: +Available commands: install: Install le.sh to your system. @@ -106,7 +106,7 @@ The issued cert will be renewed every 80 days automatically. # Install issued cert to apache/nginx etc. ``` -le installcert aa.com /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apahce/nginx "service apache2|nginx reload" +le installcert aa.com /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apache/nginx "service apache2|nginx reload" ``` Install the issued cert/key to the production apache or nginx path. From 18ab2c5c5b3491ad8105de163079b0e9f58d4353 Mon Sep 17 00:00:00 2001 From: Dawson Botsford Date: Tue, 2 Feb 2016 00:36:43 -0700 Subject: [PATCH 132/160] Manual spacing fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7b89cca..42fb6743 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ License is GPLv3 Please Star and Fork me. -Issues and pullrequests are welcomed. +Issues and pull requests are welcomed. From 9773342ad15733dbafa9a479c7ecd1986c7e0f55 Mon Sep 17 00:00:00 2001 From: neil Date: Tue, 2 Feb 2016 23:54:49 +0800 Subject: [PATCH 133/160] support dnspod.cn api --- dnsapi/dns-dp.sh | 229 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 dnsapi/dns-dp.sh diff --git a/dnsapi/dns-dp.sh b/dnsapi/dns-dp.sh new file mode 100644 index 00000000..cc213f72 --- /dev/null +++ b/dnsapi/dns-dp.sh @@ -0,0 +1,229 @@ +#!/bin/bash + +# Dnspod.cn Domain api +# +#DP_Id="1234" +# +#DP_Key="sADDsdasdgdsf" + + +DP_Api="https://dnsapi.cn" + + +#REST_API +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns-dp-add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$DP_Id" ] || [ -z "$DP_Key" ] ; then + _err "You don't specify cloudflare api key and email yet." + _err "Please create you key and try again." + return 1 + fi + + REST_API=$DP_Api + + #save the api key and email to the account conf file. + _saveaccountconf DP_Id "$DP_Id" + _saveaccountconf DP_Key "$DP_Key" + + + _debug "First detect the root zone" + if ! _get_root $fulldomain ; then + _err "invalid domain" + return 1 + fi + + existing_records $_domain $_sub_domain + _debug count "$count" + if [ "$?" != "0" ] ; then + _err "Error get existing records." + return 1 + fi + + if [ "$count" == "0" ] ; then + add_record $_domain $_sub_domain $txtvalue + else + update_record $_domain $_sub_domain $txtvalue + fi +} + +#usage: root sub +#return if the sub record already exists. +#echos the existing records count. +# '0' means doesn't exist +existing_records() { + _debug "Getting txt records" + root=$1 + sub=$2 + + if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&domain_id=$_domain_id&sub_domain=$_sub_domain"; then + return 1 + fi + + if printf "$response" | grep 'No records' ; then + count=0; + return 0 + fi + + if printf "$response" | grep "Action completed successful" >/dev/null ; then + count=$(printf "$response" | grep 'TXT' | wc -l) + + record_id=$(printf "$response" | grep '^' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) + return 0 + else + _err "get existing records error." + return 1 + fi + + + count=0 +} + +#add the txt record. +#usage: root sub txtvalue +add_record() { + root=$1 + sub=$2 + txtvalue=$3 + fulldomain=$sub.$$root + + _info "Adding record" + + if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then + return 1 + fi + + if printf "$response" | grep "Action completed successful" ; then + + return 0 + fi + + + return 1 #error +} + +#update the txt record +#Usage: root sub txtvalue +update_record() { + root=$1 + sub=$2 + txtvalue=$3 + fulldomain=$sub.$root + + _info "Updating record" + + if ! _rest POST "Record.Modify" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认&record_id=$record_id"; then + return 1 + fi + + if printf "$response" | grep "Action completed successful" ; then + + return 0 + fi + + return 1 #error +} + + + + +#################### Private functions bellow ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=2 + p=1 + while [ '1' ] ; do + h=$(printf $domain | cut -d . -f $i-100) + if [ -z "$h" ] ; then + #not valid + return 1; + fi + + if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then + return 1 + fi + + if printf "$response" | grep "Action completed successful" ; then + _domain_id=$(printf "$response" | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + _debug _domain_id "$_domain_id" + if [ "$_domain_id" ] ; then + _sub_domain=$(printf $domain | cut -d . -f 1-$p) + _debug _sub_domain $_sub_domain + _domain=$h + _debug _domain $_domain + return 0 + fi + return 1 + fi + p=$i + let "i+=1" + done + return 1 +} + + +#Usage: method URI data +_rest() { + m=$1 + ep="$2" + _debug $ep + url="$REST_API/$ep" + + _debug url "$url" + + if [ "$3" ] ; then + data="$3" + _debug data "$data" + response="$(curl --silent -X $m "$url" -d $data)" + else + response="$(curl --silent -X $m "$url" )" + fi + + if [ "$?" != "0" ] ; then + _err "error $ep" + return 1 + fi + _debug response "$response" + return 0 +} + + +_debug() { + + if [ -z "$DEBUG" ] ; then + return + fi + + if [ -z "$2" ] ; then + echo $1 + else + echo "$1"="$2" + fi +} + +_info() { + if [ -z "$2" ] ; then + echo "$1" + else + echo "$1"="$2" + fi +} + +_err() { + if [ -z "$2" ] ; then + echo "$1" >&2 + else + echo "$1"="$2" >&2 + fi +} + + From 03bf3c2da14a3fe5345a78d70647b5d9b8254548 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 3 Feb 2016 00:00:14 +0800 Subject: [PATCH 134/160] support dnspod.cn api --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42fb6743..ccff7feb 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,28 @@ le.sh issue dns-cf aa.com www.aa.com The `CF_Key` and `CF_Email` will be saved in `~/.le/account.conf`, when next time you use cloudflare api, it will reuse this key. -More api integerations are coming. Godaddy, Dnspod, etc.... +More api integerations are coming. Godaddy, etc.... + +# Use Dnspod.cn domain api to automatically issue cert + +For now, we support dnspod.cn integeration. + +First you need to login to your dnspod.cn account to get your api key and key id. + +``` +export DP_Id="1234" + +export DP_Key="sADDsdasdgdsf" + +``` + +Ok, let's issue cert now: +``` +le.sh issue dns-dp aa.com www.aa.com +``` + +The `DP_Id` and `DP_Key` will be saved in `~/.le/account.conf`, when next time you use dnspod.cn api, it will reuse this key. + # Use custom api From 7e3cbb4661f6a962dc353767f428cd4a32642c07 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 Feb 2016 13:32:46 +0800 Subject: [PATCH 135/160] fix issue, decode the error message. --- le.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/le.sh b/le.sh index 6603c3c6..111caca9 100755 --- a/le.sh +++ b/le.sh @@ -457,7 +457,7 @@ _clearupwebbroot() { _debug "remove $__webroot/.well-known/acme-challenge/$3" rm -rf "$__webroot/.well-known/acme-challenge/$3" else - _info "skip for removelevel:$2" + _info "Skip for removelevel:$2" fi return 0 @@ -830,8 +830,8 @@ issue() { if [ -z "$Le_LinkCert" ] ; then - response="$(echo $response | openssl base64 -d)" - _err "Sign failed: $response" + response="$(echo $response | openssl base64 -d -A)" + _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')" return 1 fi From 65785b5fef2b0d2006827af1a0a2f7f52580dce5 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 Feb 2016 19:35:15 +0800 Subject: [PATCH 136/160] typos --- dnsapi/dns-dp.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns-dp.sh b/dnsapi/dns-dp.sh index cc213f72..4786b2ed 100644 --- a/dnsapi/dns-dp.sh +++ b/dnsapi/dns-dp.sh @@ -19,7 +19,7 @@ dns-dp-add() { txtvalue=$2 if [ -z "$DP_Id" ] || [ -z "$DP_Key" ] ; then - _err "You don't specify cloudflare api key and email yet." + _err "You don't specify dnspod api key and key id yet." _err "Please create you key and try again." return 1 fi From b47723adbb6743c116150b37524de2ff6e9d58ff Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 Feb 2016 23:06:05 +0800 Subject: [PATCH 137/160] minor, renewAll skip 'dnsapi' folder. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 111caca9..d2bc962b 100755 --- a/le.sh +++ b/le.sh @@ -899,7 +899,7 @@ renewAll() { _initpath _info "renewAll" - for d in $(ls -F $LE_WORKING_DIR | grep '/$') ; do + for d in $(ls -F $LE_WORKING_DIR | grep [^.].*[.].*/$ ) ; do d=$(echo $d | cut -d '/' -f 1) _info "renew $d" From ce56c1bbfc34b30b49d040da9dcb3d941396746e Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 6 Feb 2016 23:23:00 +0800 Subject: [PATCH 138/160] generate default sample account.conf for the first time install --- le.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/le.sh b/le.sh index d2bc962b..d0a06f93 100755 --- a/le.sh +++ b/le.sh @@ -1057,6 +1057,35 @@ _detect_profile() { fi } +_initconf() { + _initpath + if [ ! -f "$ACCOUNT_CONF_PATH" ] ; then + echo "#Account configurations: +#Here are the supported macros, uncomment them to make them take effect. +#ACCOUNT_EMAIL=aaa@aaa.com # the account email used to register account. + +#STAGE=1 # Use the staging api +#FORCE=1 # Force to issue cert + +#dns api +####################### +#Cloudflare: +#api key +#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +#account email +#CF_Email="xxxx@sss.com" + +####################### +#Dnspod.cn: +#api key id +#DP_Id="1234" +#api key +#DP_Key="sADDsdasdgdsf" + + " > $ACCOUNT_CONF_PATH + fi +} + install() { _initpath @@ -1120,6 +1149,9 @@ alias le.sh=\"$LE_WORKING_DIR/le.sh\" installcronjob + if [ ! -f "$ACCOUNT_CONF_PATH" ] ; then + _initconf + fi _info OK } From 08094865292a856ddf6001c40603dd0464a72060 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Feb 2016 18:26:12 +0800 Subject: [PATCH 139/160] support cloudxns.com api --- README.md | 68 ++------------ dnsapi/dns-cx.sh | 234 +++++++++++++++++++++++++++++++++++++++++++++++ dnsapi/dns-dp.sh | 2 +- le.sh | 9 +- 4 files changed, 253 insertions(+), 60 deletions(-) create mode 100644 dnsapi/dns-cx.sh diff --git a/README.md b/README.md index ccff7feb..497dce3e 100644 --- a/README.md +++ b/README.md @@ -163,69 +163,21 @@ le renew aa.com Ok, it's finished. +#Automatic dns api integeration -# Use CloudFlare domain api to automatically issue cert +If your dns provider support api access, we can use api to automatically issue certs. +You don't have do anything manually. -For now, we support clourflare integeration. +Current we support: +## Cloudflare.com api +## Dnspod.cn api +## Cloudxns.com api -First you need to login to your clourflare account to get your api key. - -``` -export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" - -export CF_Email="xxxx@sss.com" - -``` - -Ok, let's issue cert now: -``` -le.sh issue dns-cf aa.com www.aa.com -``` - -The `CF_Key` and `CF_Email` will be saved in `~/.le/account.conf`, when next time you use cloudflare api, it will reuse this key. - - -More api integerations are coming. Godaddy, etc.... - -# Use Dnspod.cn domain api to automatically issue cert - -For now, we support dnspod.cn integeration. - -First you need to login to your dnspod.cn account to get your api key and key id. - -``` -export DP_Id="1234" - -export DP_Key="sADDsdasdgdsf" - -``` - -Ok, let's issue cert now: -``` -le.sh issue dns-dp aa.com www.aa.com -``` - -The `DP_Id` and `DP_Key` will be saved in `~/.le/account.conf`, when next time you use dnspod.cn api, it will reuse this key. - - - -# Use custom api - -If your api is not supported yet, you can write your own dns api. - -Let's assume you want to name it 'myapi', - -1. Create a bash script named `~/.le/dns-myapi.sh`, -2. In the scrypt, you must have a function named `dns-myapi-add()`. Which will be called by le.sh to add dns records. -3. Then you can use your api to issue cert like: - -``` -le.sh issue dns-myapi aa.com www.aa.com -``` - -For more details, please check our sample script: `dnsapi/dns-myapi.sh` +More apis are comming soon.... +##If your dns provider is not in the supported list above, you write your own script api easily. +For more details: [How to use dns api](/Neilpang/le/blob/master/dnsapi/README.md) #Under the Hood diff --git a/dnsapi/dns-cx.sh b/dnsapi/dns-cx.sh new file mode 100644 index 00000000..07c9cf08 --- /dev/null +++ b/dnsapi/dns-cx.sh @@ -0,0 +1,234 @@ +#!/bin/bash + +# Cloudxns.com Domain api +# +#CX_Key="1234" +# +#CX_Secret="sADDsdasdgdsf" + + +CX_Api="https://www.cloudxns.net/api2" + + +#REST_API +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns-cx-add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ] ; then + _err "You don't specify cloudxns.com api key or secret yet." + _err "Please create you key and try again." + return 1 + fi + + REST_API=$CX_Api + + #save the api key and email to the account conf file. + _saveaccountconf CX_Key "$CX_Key" + _saveaccountconf CX_Secret "$CX_Secret" + + + _debug "First detect the root zone" + if ! _get_root $fulldomain ; then + _err "invalid domain" + return 1 + fi + + existing_records $_domain $_sub_domain + _debug count "$count" + if [ "$?" != "0" ] ; then + _err "Error get existing records." + return 1 + fi + + if [ "$count" == "0" ] ; then + add_record $_domain $_sub_domain $txtvalue + else + update_record $_domain $_sub_domain $txtvalue + fi + + if [ "$?" == "0" ] ; then + return 0 + fi + return 1 +} + +#usage: root sub +#return if the sub record already exists. +#echos the existing records count. +# '0' means doesn't exist +existing_records() { + _debug "Getting txt records" + root=$1 + sub=$2 + + if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100" ; then + return 1 + fi + count=0 + seg=$(printf "$response" | grep -o "{[^{]*host\":\"$_sub_domain[^}]*}") + _debug seg "$seg" + if [ -z "$seg" ] ; then + return 0 + fi + + if printf "$response" | grep '"type":"TXT"' > /dev/null ; then + count=1 + record_id=$(printf "$seg" | grep -o \"record_id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + _debug record_id "$record_id" + return 0 + fi + +} + +#add the txt record. +#usage: root sub txtvalue +add_record() { + root=$1 + sub=$2 + txtvalue=$3 + fulldomain=$sub.$root + + _info "Adding record" + + if ! _rest POST "record" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then + return 1 + fi + + return 0 +} + +#update the txt record +#Usage: root sub txtvalue +update_record() { + root=$1 + sub=$2 + txtvalue=$3 + fulldomain=$sub.$root + + _info "Updating record" + + if _rest PUT "record/$record_id" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}" ; then + return 0 + fi + + return 1 +} + + + + +#################### Private functions bellow ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=2 + p=1 + + if ! _rest GET "domain" ; then + return 1 + fi + + while [ '1' ] ; do + h=$(printf $domain | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ] ; then + #not valid + return 1; + fi + + if printf "$response" | grep "$h." ; then + seg=$(printf "$response" | grep -o "{[^{]*$h\.[^}]*\}" ) + _debug seg "$seg" + _domain_id=$(printf "$seg" | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + _debug _domain_id "$_domain_id" + if [ "$_domain_id" ] ; then + _sub_domain=$(printf $domain | cut -d . -f 1-$p) + _debug _sub_domain $_sub_domain + _domain=$h + _debug _domain $_domain + return 0 + fi + return 1 + fi + p=$i + let "i+=1" + done + return 1 +} + + +#Usage: method URI data +_rest() { + m=$1 + ep="$2" + _debug $ep + url="$REST_API/$ep" + _debug url "$url" + + cdate=$(date -u "+%Y-%m-%d %H:%M:%S UTC") + _debug cdate "$cdate" + + data="$3" + _debug data "$data" + + sec="$CX_Key$url$data$cdate$CX_Secret" + _debug sec "$sec" + hmac=$(printf "$sec"| openssl md5 |cut -d " " -f 2) + _debug hmac "$hmac" + + if [ "$3" ] ; then + response="$(curl --silent -X $m "$url" -H "API-KEY: $CX_Key" -H "API-REQUEST-DATE: $cdate" -H "API-HMAC: $hmac" -H 'Content-Type: application/json' -d "$data")" + else + response="$(curl --silent -X $m "$url" -H "API-KEY: $CX_Key" -H "API-REQUEST-DATE: $cdate" -H "API-HMAC: $hmac" -H 'Content-Type: application/json')" + fi + + if [ "$?" != "0" ] ; then + _err "error $ep" + return 1 + fi + _debug response "$response" + if ! printf "$response" | grep '"message":"success"' > /dev/null ; then + return 1 + fi + return 0 +} + + +_debug() { + + if [ -z "$DEBUG" ] ; then + return + fi + + if [ -z "$2" ] ; then + echo $1 + else + echo "$1"="$2" + fi +} + +_info() { + if [ -z "$2" ] ; then + echo "$1" + else + echo "$1"="$2" + fi +} + +_err() { + if [ -z "$2" ] ; then + echo "$1" >&2 + else + echo "$1"="$2" >&2 + fi +} + + diff --git a/dnsapi/dns-dp.sh b/dnsapi/dns-dp.sh index 4786b2ed..b39e3c40 100644 --- a/dnsapi/dns-dp.sh +++ b/dnsapi/dns-dp.sh @@ -89,7 +89,7 @@ add_record() { root=$1 sub=$2 txtvalue=$3 - fulldomain=$sub.$$root + fulldomain=$sub.$root _info "Adding record" diff --git a/le.sh b/le.sh index d0a06f93..64f6cede 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.4 +VER=1.1.5 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -1066,6 +1066,7 @@ _initconf() { #STAGE=1 # Use the staging api #FORCE=1 # Force to issue cert +#DEBUG=1 # Debug mode #dns api ####################### @@ -1082,6 +1083,12 @@ _initconf() { #api key #DP_Key="sADDsdasdgdsf" +####################### +#Cloudxns.com: +#CX_Key="1234" +# +#CX_Secret="sADDsdasdgdsf" + " > $ACCOUNT_CONF_PATH fi } From 39c6df299caa7aad0b3fef1fc951cf930f1fc140 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Feb 2016 18:29:28 +0800 Subject: [PATCH 140/160] readme for using api --- README.md | 12 +++---- dnsapi/README.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 dnsapi/README.md diff --git a/README.md b/README.md index 497dce3e..bc0d133b 100644 --- a/README.md +++ b/README.md @@ -165,17 +165,17 @@ Ok, it's finished. #Automatic dns api integeration -If your dns provider support api access, we can use api to automatically issue certs. +If your dns provider supports api access, we can use api to automatically issue certs. You don't have do anything manually. -Current we support: -## Cloudflare.com api -## Dnspod.cn api -## Cloudxns.com api +Currently we support: +1. Cloudflare.com api +2. Dnspod.cn api +3. Cloudxns.com api More apis are comming soon.... -##If your dns provider is not in the supported list above, you write your own script api easily. +If your dns provider is not in the supported list above, you can write your own script api easily. For more details: [How to use dns api](/Neilpang/le/blob/master/dnsapi/README.md) diff --git a/dnsapi/README.md b/dnsapi/README.md new file mode 100644 index 00000000..005b8434 --- /dev/null +++ b/dnsapi/README.md @@ -0,0 +1,86 @@ +# How to use dns api + +## Use CloudFlare domain api to automatically issue cert + +For now, we support clourflare integeration. + +First you need to login to your clourflare account to get your api key. + +``` +export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" + +export CF_Email="xxxx@sss.com" + +``` + +Ok, let's issue cert now: +``` +le.sh issue dns-cf aa.com www.aa.com +``` + +The `CF_Key` and `CF_Email` will be saved in `~/.le/account.conf`, when next time you use cloudflare api, it will reuse this key. + + + +## Use Dnspod.cn domain api to automatically issue cert + +For now, we support dnspod.cn integeration. + +First you need to login to your dnspod.cn account to get your api key and key id. + +``` +export DP_Id="1234" + +export DP_Key="sADDsdasdgdsf" + +``` + +Ok, let's issue cert now: +``` +le.sh issue dns-dp aa.com www.aa.com +``` + +The `DP_Id` and `DP_Key` will be saved in `~/.le/account.conf`, when next time you use dnspod.cn api, it will reuse this key. + + +## Use Cloudxns.com domain api to automatically issue cert + +For now, we support Cloudxns.com integeration. + +First you need to login to your Cloudxns.com account to get your api key and key secret. + +``` +export CX_Key="1234" + +export CX_Api="sADDsdasdgdsf" + +``` + +Ok, let's issue cert now: +``` +le.sh issue dns-cx aa.com www.aa.com +``` + +The `CX_Key` and `CX_Api` will be saved in `~/.le/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. + + + +# Use custom api + +If your api is not supported yet, you can write your own dns api. + +Let's assume you want to name it 'myapi', + +1. Create a bash script named `~/.le/dns-myapi.sh`, +2. In the scrypt, you must have a function named `dns-myapi-add()`. Which will be called by le.sh to add dns records. +3. Then you can use your api to issue cert like: + +``` +le.sh issue dns-myapi aa.com www.aa.com +``` + +For more details, please check our sample script: [dnsapi/dns-myapi.sh](README.md) + + + + From a1f0fb3eae2ba2f03e9cfb9921f1e8daa3651055 Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Feb 2016 18:33:04 +0800 Subject: [PATCH 141/160] fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc0d133b..62e407cf 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ More apis are comming soon.... If your dns provider is not in the supported list above, you can write your own script api easily. -For more details: [How to use dns api](/Neilpang/le/blob/master/dnsapi/README.md) +For more details: [How to use dns api](dnsapi/README.md) #Under the Hood From 855d9499859b1ad4c671d06e50cdb65ee772f4ee Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Feb 2016 18:37:04 +0800 Subject: [PATCH 142/160] fix links --- README.md | 5 +++-- dnsapi/README.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62e407cf..ee2854c7 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,8 @@ Ok, it's finished. If your dns provider supports api access, we can use api to automatically issue certs. You don't have do anything manually. -Currently we support: +###Currently we support: + 1. Cloudflare.com api 2. Dnspod.cn api 3. Cloudxns.com api @@ -177,7 +178,7 @@ More apis are comming soon.... If your dns provider is not in the supported list above, you can write your own script api easily. -For more details: [How to use dns api](dnsapi/README.md) +For more details: [How to use dns api](dnsapi) #Under the Hood diff --git a/dnsapi/README.md b/dnsapi/README.md index 005b8434..8b56ad43 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -79,7 +79,7 @@ Let's assume you want to name it 'myapi', le.sh issue dns-myapi aa.com www.aa.com ``` -For more details, please check our sample script: [dnsapi/dns-myapi.sh](README.md) +For more details, please check our sample script: [dns-myapi.sh](dns-myapi.sh) From 86c9e55ca2d8529e389d1275349c2305227273ff Mon Sep 17 00:00:00 2001 From: neil Date: Sun, 7 Feb 2016 20:14:53 +0800 Subject: [PATCH 143/160] fix doc error --- dnsapi/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/README.md b/dnsapi/README.md index 8b56ad43..3588dd44 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -52,7 +52,7 @@ First you need to login to your Cloudxns.com account to get your api key and key ``` export CX_Key="1234" -export CX_Api="sADDsdasdgdsf" +export CX_Secret="sADDsdasdgdsf" ``` @@ -61,7 +61,7 @@ Ok, let's issue cert now: le.sh issue dns-cx aa.com www.aa.com ``` -The `CX_Key` and `CX_Api` will be saved in `~/.le/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. +The `CX_Key` and `CX_Secret` will be saved in `~/.le/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. From 2c06a14d19735d00c736ded5bf4416d48fa85373 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 11 Feb 2016 22:45:21 +0100 Subject: [PATCH 144/160] renew() should return the function result code of issue() currently the renew function does always return 0 even when the cert issuing failed. for now just return the function return code of issue(). maybe an additional offset (like +100) should be added to distinguish the issue error codes from the renew error codes. --- le.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/le.sh b/le.sh index 64f6cede..953136e3 100755 --- a/le.sh +++ b/le.sh @@ -892,7 +892,10 @@ renew() { IS_RENEW="1" issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" + local res=$? IS_RENEW="" + + return $res } renewAll() { From 0f71a9fe96c7be6f8d8961a3ac12b6b5e6fe2528 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 12 Feb 2016 08:54:28 +0800 Subject: [PATCH 145/160] Use openssl dgst -sha256 instead of openssl sha --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 953136e3..5e700397 100755 --- a/le.sh +++ b/le.sh @@ -574,7 +574,7 @@ issue() { _debug HEADER "$HEADER" accountkey_json=$(echo -n "$jwk" | sed "s/ //g") - thumbprint=$(echo -n "$accountkey_json" | openssl sha -sha256 -binary | _base64 | _b64) + thumbprint=$(echo -n "$accountkey_json" | openssl dgst -sha256 -binary | _base64 | _b64) _info "Registering account" From 1add47a6b61b86f451d984fc22a2b67f0f2063f7 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 12 Feb 2016 17:56:50 +0800 Subject: [PATCH 146/160] support ECC key, ECDSA certificate --- README.md | 19 +++++++++++++++ le.sh | 71 +++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ee2854c7..5851e179 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,25 @@ If your dns provider is not in the supported list above, you can write your own For more details: [How to use dns api](dnsapi) +# Issue ECC certificate: +LetsEncrypt now can issue ECDSA certificate. +And we also support it. + +Just set key length to the `length` paramiter with a prefix "ec-". +For example: +``` +le issue /home/wwwroot/aa.com aa.com www.aa.com ec-256 +``` +Please look at the last parameter above. + +Valid values are: + +1. ec-256 (prime256v1, "ECDSA P-256") +2. ec-384 (secp384r1, "ECDSA P-384") +3. ec-521 (secp521r1, "ECDSA P-521", not supported by letsencrypt yet.) + + + #Under the Hood Speak ACME language with bash directly to Let's encrypt. diff --git a/le.sh b/le.sh index 5e700397..76df5cd8 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.5 +VER=1.1.6 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -41,6 +41,7 @@ _err() { else echo "$1"="$2" >&2 fi + return 1 } _h2b() { @@ -66,12 +67,17 @@ _base64() { createAccountKey() { _info "Creating account key" if [ -z "$1" ] ; then - echo Usage: $0 account-domain [2048] + echo Usage: createAccountKey account-domain [2048] return fi account=$1 length=$2 + + if [[ "$length" == "ec-"* ]] ; then + length=2048 + fi + if [ -z "$2" ] ; then _info "Use default length 2048" length=2048 @@ -92,21 +98,50 @@ createAccountKey() { createDomainKey() { _info "Creating domain key" if [ -z "$1" ] ; then - echo Usage: $0 domain [2048] + echo Usage: createDomainKey domain [2048] return fi domain=$1 length=$2 - if [ -z "$2" ] ; then - _info "Use default length 2048" - length=2048 + isec="" + if [[ "$length" == "ec-"* ]] ; then + isec="1" + length=$(printf $length | cut -d '-' -f 2-100) + eccname="$length" + fi + + if [ -z "$length" ] ; then + if [ "$isec" ] ; then + length=256 + else + length=2048 + fi + fi + _info "Use length $length" + + if [ "$isec" ] ; then + if [ "$length" == "256" ] ; then + eccname="prime256v1" + fi + if [ "$length" == "384" ] ; then + eccname="secp384r1" + fi + if [ "$length" == "521" ] ; then + eccname="secp521r1" + fi + _info "Using ec name: $eccname" fi + _initpath $domain if [ ! -f "$CERT_KEY_PATH" ] || ( [ "$FORCE" ] && ! [ "$IS_RENEW" ] ); then #generate account key - openssl genrsa $length > "$CERT_KEY_PATH" + if [ "$isec" ] ; then + openssl ecparam -name $eccname -genkey 2>/dev/null > "$CERT_KEY_PATH" + else + openssl genrsa $length 2>/dev/null > "$CERT_KEY_PATH" + fi else if [ "$IS_RENEW" ] ; then _info "Domain key exists, skip" @@ -250,7 +285,7 @@ _savedomainconf() { if [ "$DOMAIN_CONF" ] ; then _setopt $DOMAIN_CONF "$key" "=" "$value" else - _debug "DOMAIN_CONF is empty, can not save $key=$value" + _err "DOMAIN_CONF is empty, can not save $key=$value" fi } @@ -261,7 +296,7 @@ _saveaccountconf() { if [ "$ACCOUNT_CONF_PATH" ] ; then _setopt $ACCOUNT_CONF_PATH "$key" "=" "$value" else - _debug "ACCOUNT_CONF_PATH is empty, can not save $key=$value" + _err "ACCOUNT_CONF_PATH is empty, can not save $key=$value" fi } @@ -329,31 +364,31 @@ _initpath() { if [ -z "$ACCOUNT_KEY_PATH" ] ; then ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key" fi - - if [ -z "$domain" ] ; then return 0 fi - mkdir -p "$LE_WORKING_DIR/$domain" + domainhome="$LE_WORKING_DIR/$domain" + mkdir -p "$domainhome" if [ -z "$DOMAIN_CONF" ] ; then - DOMAIN_CONF="$LE_WORKING_DIR/$domain/$Le_Domain.conf" + DOMAIN_CONF="$domainhome/$Le_Domain.conf" fi + if [ -z "$CSR_PATH" ] ; then - CSR_PATH="$LE_WORKING_DIR/$domain/$domain.csr" + CSR_PATH="$domainhome/$domain.csr" fi if [ -z "$CERT_KEY_PATH" ] ; then - CERT_KEY_PATH="$LE_WORKING_DIR/$domain/$domain.key" + CERT_KEY_PATH="$domainhome/$domain.key" fi if [ -z "$CERT_PATH" ] ; then - CERT_PATH="$LE_WORKING_DIR/$domain/$domain.cer" + CERT_PATH="$domainhome/$domain.cer" fi if [ -z "$CA_CERT_PATH" ] ; then - CA_CERT_PATH="$LE_WORKING_DIR/$domain/ca.cer" + CA_CERT_PATH="$domainhome/ca.cer" fi - + } From 02c0453b71fa3571e7a634b5ffea8de91c520de1 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 13 Feb 2016 11:43:57 +0800 Subject: [PATCH 147/160] support customized standalone server port for servers, that is behind HAProxy or load balance, default is 80. --- le.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/le.sh b/le.sh index 76df5cd8..7bfd2c29 100755 --- a/le.sh +++ b/le.sh @@ -308,9 +308,9 @@ _startserver() { fi # while true ; do if [ "$DEBUG" ] ; then - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p 80 -vv + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p $Le_HTTPPort -vv else - echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p 80 > /dev/null + echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p $Le_HTTPPort > /dev/null fi # done } @@ -558,11 +558,16 @@ issue() { _err "Please install netcat(nc) tools first." return 1 fi - - netprc="$(ss -ntpl | grep ':80 ')" + + if [ -z "$Le_HTTPPort" ] ; then + Le_HTTPPort=80 + fi + _setopt "$DOMAIN_CONF" "Le_HTTPPort" "=" "$Le_HTTPPort" + + netprc="$(ss -ntpl | grep :$Le_HTTPPort" ")" if [ "$netprc" ] ; then _err "$netprc" - _err "tcp port 80 is already used by $(echo "$netprc" | cut -d : -f 4)" + _err "tcp port $Le_HTTPPort is already used by $(echo "$netprc" | cut -d : -f 4)" _err "Please stop it first" return 1 fi From 69b67e401534e24c85c1c337198ec3fc5a81bb36 Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Wed, 17 Feb 2016 17:05:13 +0800 Subject: [PATCH 148/160] change "openssl sha" to "openssl dgst" --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 7bfd2c29..dbc21824 100755 --- a/le.sh +++ b/le.sh @@ -687,7 +687,7 @@ issue() { dnsadded='0' txtdomain="_acme-challenge.$d" _debug txtdomain "$txtdomain" - txt="$(echo -e -n $keyauthorization | openssl sha -sha256 -binary | _base64 | _b64)" + txt="$(echo -e -n $keyauthorization | openssl dgst -sha256 -binary | _base64 | _b64)" _debug txt "$txt" #dns #1. check use api From f9a1b64116fde1d2b04049b965c47f3a9075dc0a Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 19 Feb 2016 22:35:18 +0800 Subject: [PATCH 149/160] remove sudo from crontab --- le.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/le.sh b/le.sh index dbc21824..4fb8b4aa 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER=1.1.6 +VER=1.1.7 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -322,13 +322,6 @@ _stopserver() { _initpath() { - #check if there is sudo installed, AND if the current user is a sudoer. - if command -v sudo > /dev/null ; then - if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then - SUDO=sudo - fi - fi - if [ -z "$LE_WORKING_DIR" ]; then LE_WORKING_DIR=$HOME/.le fi @@ -1043,7 +1036,7 @@ installcronjob() { _err "Can not install cronjob, le.sh not found." return 1 fi - crontab -l | { cat; echo "0 0 * * * $SUDO LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"; } | crontab - + crontab -l | { cat; echo "0 0 * * * LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"; } | crontab - fi return 0 } @@ -1138,7 +1131,14 @@ _initconf() { install() { _initpath - + + #check if there is sudo installed, AND if the current user is a sudoer. + if command -v sudo > /dev/null ; then + if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then + SUDO=sudo + fi + fi + if command -v yum > /dev/null ; then YUM="1" INSTALL="$SUDO yum install -y " From 0f793850c62cb5a6e960dc6ce4d56d81625ce382 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 19 Feb 2016 22:58:10 +0800 Subject: [PATCH 150/160] fix uninstall crontab --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index 4fb8b4aa..b3848385 100755 --- a/le.sh +++ b/le.sh @@ -1046,7 +1046,7 @@ uninstallcronjob() { cr="$(crontab -l | grep 'le.sh cron')" if [ "$cr" ] ; then crontab -l | sed "/le.sh cron/d" | crontab - - LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 7 | cut -d '=' -f 2 | tr -d '"')" + LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 6 | cut -d '=' -f 2 | tr -d '"')" _info LE_WORKING_DIR "$LE_WORKING_DIR" fi _initpath From 0db15ce230c25ac6668e88ca5dd8b39d75e91b14 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 19 Feb 2016 23:48:55 +0800 Subject: [PATCH 151/160] more portable if bash is not in the default path. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index b3848385..90f8335f 100755 --- a/le.sh +++ b/le.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash VER=1.1.7 PROJECT="https://github.com/Neilpang/le" From aa692c0e702eac71874e70574a33118cd462498c Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 20 Feb 2016 23:58:36 +0800 Subject: [PATCH 152/160] Support FreeBSD --- le.sh | 70 ++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/le.sh b/le.sh index 90f8335f..b327a2fb 100755 --- a/le.sh +++ b/le.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -VER=1.1.7 +VER=1.1.8 PROJECT="https://github.com/Neilpang/le" DEFAULT_CA="https://acme-v01.api.letsencrypt.org" @@ -180,7 +180,8 @@ createCSR() { alt="DNS:$(echo $domainlist | sed "s/,/,DNS:/g")" #multi _info "Multi domain" "$alt" - openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -reqexts SAN -config <(printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt") -out "$CSR_PATH" + printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt" > "$DOMAIN_SSL_CONF" + openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -reqexts SAN -config "$DOMAIN_SSL_CONF" -out "$CSR_PATH" fi } @@ -190,6 +191,19 @@ _b64() { echo $__n | tr '/+' '_-' | tr -d '= ' } +_time2str() { + #BSD + if date -u -d@$1 2>/dev/null ; then + return + fi + + #Linux + if date -u -r $1 2>/dev/null ; then + return + fi + +} + _send_signed_request() { url=$1 payload=$2 @@ -208,14 +222,14 @@ _send_signed_request() { _debug payload64 $payload64 nonceurl="$API/directory" - nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | sed s/\\r//|sed s/\\n//| cut -d ' ' -f 2) + nonce="$($CURL -I $nonceurl | grep -o "^Replay-Nonce:.*$" | tr -d "\r\n" | cut -d ' ' -f 2)" - _debug nonce $nonce + _debug nonce "$nonce" - protected=$(echo -n "$HEADERPLACE" | sed "s/NONCE/$nonce/" ) + protected="$(printf "$HEADERPLACE" | sed "s/NONCE/$nonce/" )" _debug protected "$protected" - protected64=$( echo -n $protected | _base64 | _b64) + protected64="$(printf "$protected" | _base64 | _b64)" _debug protected64 "$protected64" sig=$(echo -n "$protected64.$payload64" | openssl dgst -sha256 -sign $ACCOUNT_KEY_PATH | _base64 | _b64) @@ -230,11 +244,11 @@ _send_signed_request() { response="$($CURL -X POST --data "$body" $url)" fi - responseHeaders="$(sed 's/\r//g' $CURL_HEADER)" + responseHeaders="$(cat $CURL_HEADER)" _debug responseHeaders "$responseHeaders" _debug response "$response" - code="$(grep ^HTTP $CURL_HEADER | tail -1 | cut -d " " -f 2)" + code="$(grep ^HTTP $CURL_HEADER | tail -1 | cut -d " " -f 2 | tr -d "\r\n" )" _debug code $code } @@ -269,7 +283,8 @@ _setopt() { if [[ "$__val" == *"&"* ]] ; then __val="$(echo $__val | sed 's/&/\\&/g')" fi - sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" "$__conf" + text="$(cat $__conf)" + printf "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf" else _debug APP echo "$__opt$__sep$__val$__end" >> "$__conf" @@ -368,7 +383,11 @@ _initpath() { if [ -z "$DOMAIN_CONF" ] ; then DOMAIN_CONF="$domainhome/$Le_Domain.conf" fi - + + if [ -z "$DOMAIN_SSL_CONF" ] ; then + DOMAIN_SSL_CONF="$domainhome/$Le_Domain.ssl.conf" + fi + if [ -z "$CSR_PATH" ] ; then CSR_PATH="$domainhome/$domain.csr" fi @@ -386,8 +405,8 @@ _initpath() { _apachePath() { - httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | sed s/\"//g)" - httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | sed s/\"//g)" + httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"' )" + httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"' )" httpdconf="$httpdroot/$httpdconfname" if [ ! -f $httpdconf ] ; then _err "Apache Config file not found" $httpdconf @@ -606,7 +625,7 @@ issue() { HEADERPLACE='{"nonce": "NONCE", "alg": "RS256", "jwk": '$jwk'}' _debug HEADER "$HEADER" - accountkey_json=$(echo -n "$jwk" | sed "s/ //g") + accountkey_json=$(echo -n "$jwk" | tr -d ' ' ) thumbprint=$(echo -n "$accountkey_json" | openssl dgst -sha256 -binary | _base64 | _b64) @@ -638,7 +657,7 @@ issue() { _info "Verify each domain" sep='#' if [ -z "$vlist" ] ; then - alldomains=$(echo "$Le_Domain,$Le_Alt" | sed "s/,/ /g") + alldomains=$(echo "$Le_Domain,$Le_Alt" | tr ',' ' ' ) for d in $alldomains do _info "Geting token for domain" $d @@ -649,13 +668,13 @@ issue() { return 1 fi - entry=$(echo $response | egrep -o '{[^{]*"type":"'$vtype'"[^}]*') + entry="$(printf $response | egrep -o '{[^{]*"type":"'$vtype'"[^}]*')" _debug entry "$entry" - token=$(echo "$entry" | sed 's/,/\n'/g| grep '"token":'| cut -d : -f 2|sed 's/"//g') + token="$(printf "$entry" | egrep -o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" _debug token $token - uri=$(echo "$entry" | sed 's/,/\n'/g| grep '"uri":'| cut -d : -f 2,3|sed 's/"//g') + uri="$(printf "$entry" | egrep -o '"uri":"[^"]*'| cut -d : -f 2,3 | tr -d '"' )" _debug uri $uri keyauthorization="$token.$thumbprint" @@ -670,7 +689,7 @@ issue() { #add entry dnsadded="" - ventries=$(echo "$vlist" | sed "s/,/ /g") + ventries=$(echo "$vlist" | tr ',' ' ' ) for ventry in $ventries do d=$(echo $ventry | cut -d $sep -f 1) @@ -745,7 +764,7 @@ issue() { fi _debug "ok, let's start to verify" - ventries=$(echo "$vlist" | sed "s/,/ /g") + ventries=$(echo "$vlist" | tr ',' ' ' ) for ventry in $ventries do d=$(echo $ventry | cut -d $sep -f 1) @@ -812,7 +831,7 @@ issue() { return 1 fi - status=$(echo $response | egrep -o '"status":"[^"]+"' | cut -d : -f 2 | sed 's/"//g') + status=$(echo $response | egrep -o '"status":"[^"]+"' | cut -d : -f 2 | tr -d '"') if [ "$status" == "valid" ] ; then _info "Success" _stopserver $serverproc @@ -848,7 +867,7 @@ issue() { _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" - Le_LinkCert="$(grep -i -o '^Location.*' $CURL_HEADER |sed 's/\r//g'| cut -d " " -f 2)" + Le_LinkCert="$(grep -i -o '^Location.*$' $CURL_HEADER | tr -d "\r\n" | cut -d " " -f 2)" _setopt "$DOMAIN_CONF" "Le_LinkCert" "=" "$Le_LinkCert" if [ "$Le_LinkCert" ] ; then @@ -870,7 +889,7 @@ issue() { _setopt "$DOMAIN_CONF" 'Le_Vlist' '=' "\"\"" - Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') + Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' ) _setopt "$DOMAIN_CONF" "Le_LinkIssuer" "=" "$Le_LinkIssuer" if [ "$Le_LinkIssuer" ] ; then @@ -883,7 +902,7 @@ issue() { Le_CertCreateTime=$(date -u "+%s") _setopt "$DOMAIN_CONF" "Le_CertCreateTime" "=" "$Le_CertCreateTime" - Le_CertCreateTimeStr=$(date -u "+%Y-%m-%d %H:%M:%S UTC") + Le_CertCreateTimeStr=$(date -u ) _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\"" if [ ! "$Le_RenewalDays" ] ; then @@ -892,10 +911,10 @@ issue() { _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays" - Le_NextRenewTime=$(date -u -d "+$Le_RenewalDays day" "+%s") + let "Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60" _setopt "$DOMAIN_CONF" "Le_NextRenewTime" "=" "$Le_NextRenewTime" - Le_NextRenewTimeStr=$(date -u -d "+$Le_RenewalDays day" "+%Y-%m-%d %H:%M:%S UTC") + Le_NextRenewTimeStr=$( _time2str $Le_NextRenewTime ) _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\"" @@ -960,6 +979,7 @@ renewAll() { Le_ReloadCmd="" DOMAIN_CONF="" + DOMAIN_SSL_CONF="" CSR_PATH="" CERT_KEY_PATH="" CERT_PATH="" From 58b138567e5c63095df5f0824864159455e8150c Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 21 Feb 2016 00:09:21 +0800 Subject: [PATCH 153/160] support FreeBSD --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5851e179..81ff33e8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Do NOT require to be `root/sudoer`. 1. Ubuntu/Debian. 2. CentOS 3. Windows (cygwin with curl, openssl and crontab included) +4. FreeBSD with bash #Supported Mode From e6d31b4e0cb3c83565f18c3b99153fd3a82669e8 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 29 Feb 2016 13:38:40 +0800 Subject: [PATCH 154/160] minor, add debug info --- dnsapi/dns-cf.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index b1e4b47d..da5bef72 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -31,9 +31,12 @@ dns-cf-add() { _err "invalid domain" return 1 fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" _debug "Getting txt records" - _cf_rest GET "/zones/$_domain_id/dns_records?type=TXT&name=$fulldomain" + _cf_rest GET "/zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain" if [ "$?" != "0" ] || ! printf $response | grep \"success\":true > /dev/null ; then _err "Error" From d250f9c715dd671a2d14066b184779cbd50b59d3 Mon Sep 17 00:00:00 2001 From: neil Date: Mon, 29 Feb 2016 22:42:26 +0800 Subject: [PATCH 155/160] fix bug for cloudflare api --- dnsapi/dns-cf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh index da5bef72..159969d2 100755 --- a/dnsapi/dns-cf.sh +++ b/dnsapi/dns-cf.sh @@ -103,7 +103,7 @@ _get_root() { fi if printf $response | grep \"name\":\"$h\" ; then - _domain_id=$(printf $response | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") + _domain_id=$(printf "$response" | grep -o \"id\":\"[^\"]*\" | head -1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ] ; then _sub_domain=$(printf $domain | cut -d . -f 1-$p) _domain=$h From 60a94daee1caeef160dee2329fdc98fa4b2d627b Mon Sep 17 00:00:00 2001 From: J Phani Mahesh Date: Wed, 2 Mar 2016 18:23:54 +0530 Subject: [PATCH 156/160] simplify call by using $@ $@ refers to all arguments, which is a nice way of saying $1 $2 ..., plus it doesn't need updating if we need more arguments. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index b327a2fb..c1a83c9f 100755 --- a/le.sh +++ b/le.sh @@ -1284,5 +1284,5 @@ createCSR: if [ -z "$1" ] ; then showhelp else - "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" + $@ fi From 8b92aab7d946be66e666e6429ebe8b4661f192ef Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 2 Mar 2016 22:53:07 +0800 Subject: [PATCH 157/160] typos --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 81ff33e8..dff70a8f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # le: means simp`Le` Simplest shell script for LetsEncrypt free Certificate client +Simple and Powerfull, you only need 3 minutes to learn. + Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client. Just one script, to issue, renew your certificates automatically. @@ -186,7 +188,7 @@ For more details: [How to use dns api](dnsapi) LetsEncrypt now can issue ECDSA certificate. And we also support it. -Just set key length to the `length` paramiter with a prefix "ec-". +Just set the `length` parameter with a prefix `ec-`. For example: ``` le issue /home/wwwroot/aa.com aa.com www.aa.com ec-256 @@ -197,7 +199,7 @@ Valid values are: 1. ec-256 (prime256v1, "ECDSA P-256") 2. ec-384 (secp384r1, "ECDSA P-384") -3. ec-521 (secp521r1, "ECDSA P-521", not supported by letsencrypt yet.) +3. ec-521 (secp521r1, "ECDSA P-521", which is not supported by letsencrypt yet.) From f0d0bd2518ec7f58e5c79b07ffdb5feb90e6fecd Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 2 Mar 2016 22:53:36 +0800 Subject: [PATCH 158/160] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dff70a8f..d9447fea 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # le: means simp`Le` Simplest shell script for LetsEncrypt free Certificate client -Simple and Powerfull, you only need 3 minutes to learn. +Simple and Powerful, you only need 3 minutes to learn. Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client. Just one script, to issue, renew your certificates automatically. From 0f36bb113061c1944c20f430eafed1429984bd61 Mon Sep 17 00:00:00 2001 From: BlueAnanas Date: Thu, 3 Mar 2016 00:18:19 +0100 Subject: [PATCH 159/160] Replaced $@ by quoted "$@" An $a without quotes is not putting quotes around expanded characters. The quotes around the server reload command get lost in the process. --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index c1a83c9f..caa123f2 100755 --- a/le.sh +++ b/le.sh @@ -1284,5 +1284,5 @@ createCSR: if [ -z "$1" ] ; then showhelp else - $@ + "$@" fi From c4e1faa481a3d84d351fb34f3cbbab176ca7c346 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 4 Mar 2016 16:40:34 +0800 Subject: [PATCH 160/160] minor: eval reload command --- le.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/le.sh b/le.sh index c1a83c9f..57763c49 100755 --- a/le.sh +++ b/le.sh @@ -1041,7 +1041,7 @@ installcert() { if [ "$Le_ReloadCmd" ] ; then _info "Run Le_ReloadCmd: $Le_ReloadCmd" - $Le_ReloadCmd + eval $Le_ReloadCmd fi }