diff --git a/README.md b/README.md
index 071680a7..d9447fea 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,27 @@
-# le
+# le: means simp`Le`
Simplest shell script for LetsEncrypt free Certificate client
-This is a shell version from https://github.com/diafygi/acme-tiny
+Simple and Powerful, you only need 3 minutes to learn.
-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.
+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`.
+
+#Tested OS
+1. Ubuntu/Debian.
+2. CentOS
+3. Windows (cygwin with curl, openssl and crontab included)
+4. FreeBSD with bash
-#Supported OS
-1. Tested on Ubuntu/Debian.
-2. CentOS is Not tested yet, It should work.
+#Supported Mode
+1. Webroot mode
+2. Standalone mode
+3. Apache mode
+4. Dns mode
#How to use
@@ -23,64 +31,199 @@ 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`
+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`
All the certs will be placed in this folder.
-* create symbol link: `/bin/le -> ~/.le/le.sh`
+* 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.
-3. Ok, you are ready to issue cert now.
+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]....
+Available 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: /bin/le webroot a.com [www.a.com,b.com,c.com] [key-length] [cert-file-path] [key-file-path] [reloadCmd]
```
+
+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:
```
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 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, which is 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/`
+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/apache/nginx "service apache2|nginx reload"
+```
+
+Install the issued cert/key to the production apache or nginx path.
-The issued cert will be renewed every 50 days automatically.
+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`
-# Issue a cert, and install to apache
+# 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.
+
```
-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 no aa.com www.aa.com,cp.aa.com
```
-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`
+# 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,user.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,user.aa.com
+```
+
+You will get 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.
+
+
+#Automatic dns api integeration
+
+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:
+
+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 can write your own script api easily.
+
+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 the `length` parameter 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", which is not supported by letsencrypt yet.)
#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:
+#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
Please Star and Fork me.
-Issues and pullrequests are welcomed.
+Issues and pull requests are welcomed.
diff --git a/dnsapi/README.md b/dnsapi/README.md
new file mode 100644
index 00000000..3588dd44
--- /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_Secret="sADDsdasdgdsf"
+
+```
+
+Ok, let's issue cert now:
+```
+le.sh issue dns-cx aa.com www.aa.com
+```
+
+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.
+
+
+
+# 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: [dns-myapi.sh](dns-myapi.sh)
+
+
+
+
diff --git a/dnsapi/dns-cf.sh b/dnsapi/dns-cf.sh
new file mode 100755
index 00000000..159969d2
--- /dev/null
+++ b/dnsapi/dns-cf.sh
@@ -0,0 +1,171 @@
+#!/bin/bash
+
+
+#
+#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#CF_Email="xxxx@sss.com"
+
+
+CF_Api="https://api.cloudflare.com/client/v4/"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+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
+
+ #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"
+ 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"
+
+ 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 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 10
+ #todo: check if the record takes effect
+ 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 \")
+ _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
+ _info "Updated, sleeping 10 seconds"
+ sleep 10
+ #todo: check if the record takes effect
+ return 0;
+ fi
+ _err "Update error"
+ return 1
+ fi
+
+}
+
+
+
+
+
+#################### 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 ! _cf_rest GET "zones?name=$h" ; then
+ return 1
+ fi
+
+ if printf $response | grep \"name\":\"$h\" ; then
+ _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
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ let "i+=1"
+ done
+ return 1
+}
+
+
+_cf_rest() {
+ m=$1
+ ep="$2"
+ _debug $ep
+ if [ "$3" ] ; then
+ 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
+
+ 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
+}
+
+
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
new file mode 100644
index 00000000..b39e3c40
--- /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 dnspod api key and key id 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
+}
+
+
diff --git a/dnsapi/dns-myapi.sh b/dnsapi/dns-myapi.sh
new file mode 100644
index 00000000..af7dda7a
--- /dev/null
+++ b/dnsapi/dns-myapi.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+#Here is a sample custom api script.
+#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.
+
+
+
+######## 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
+}
+
+
diff --git a/le.sh b/le.sh
index d523b60f..bb99dfd6 100755
--- a/le.sh
+++ b/le.sh
@@ -1,105 +1,163 @@
-#!/bin/bash
+#!/usr/bin/env bash
+VER=1.1.8
+PROJECT="https://github.com/Neilpang/le"
-
-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"
+DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
-API=$DEFAULT_CA
+STAGE_CA="https://acme-staging.api.letsencrypt.org"
-DEBUG=
+VTYPE_HTTP="http-01"
+VTYPE_DNS="dns-01"
+
+if [ -z "$AGREEMENT" ] ; then
+ AGREEMENT="$DEFAULT_AGREEMENT"
+fi
_debug() {
- if ! [ "DEBUG" ] ; then
+
+ if [ -z "$DEBUG" ] ; then
return
fi
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
}
+_err() {
+ if [ -z "$2" ] ; then
+ echo "$1" >&2
+ else
+ echo "$1"="$2" >&2
+ fi
+ return 1
+}
+
+_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
+}
+
+_base64() {
+ openssl base64 -e | tr -d '\n'
+}
+
#domain [2048]
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 [ -z "$2" ] ; then
- echo Use default length 2048
+
+ if [[ "$length" == "ec-"* ]] ; then
length=2048
fi
- mkdir -p $WORKING_DIR
- ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc
+ if [ -z "$2" ] ; then
+ _info "Use default length 2048"
+ length=2048
+ fi
+ _initpath
if [ -f "$ACCOUNT_KEY_PATH" ] ; then
- echo account key exists, skip
+ _info "Account key exists, skip"
return
else
#generate account key
- openssl genrsa $length > $ACCOUNT_KEY_PATH
+ openssl genrsa $length > "$ACCOUNT_KEY_PATH"
fi
}
#domain length
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
- echo Use default length 2048
- length=2048
+ isec=""
+ if [[ "$length" == "ec-"* ]] ; then
+ isec="1"
+ length=$(printf $length | cut -d '-' -f 2-100)
+ eccname="$length"
fi
- mkdir -p $WORKING_DIR/$domain
- CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key
+ 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
- if [ -f "$CERT_KEY_PATH" ] ; then
- echo domain key exists, skip
- else
+ _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"
+ return 0
+ else
+ _err "Domain key exists, do you want to overwrite the key?"
+ _err "Set FORCE=1, and try again."
+ return 1
+ fi
fi
}
# domain domainlist
createCSR() {
+ _info "Creating csr"
if [ -z "$1" ] ; then
echo Usage: $0 domain [domainlist]
return
@@ -109,92 +167,99 @@ createCSR() {
domainlist=$2
- if [ -f $CSR_PATH ] ; then
- echo CSR exists, skip
+ if [ -f "$CSR_PATH" ] && [ "$IS_RENEW" ] && ! [ "$FORCE" ]; then
+ _info "CSR exists, skip"
return
fi
if [ -z "$domainlist" ] ; then
#single domain
- echo single domain
- openssl req -new -sha256 -key $CERT_KEY_PATH -subj "/CN=$domain" > $CSR_PATH
+ _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")
+ 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
+ _info "Multi domain" "$alt"
+ 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
}
_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")
- __n=$(echo $__n | sed "s|=||g")
- echo $__n
+ __n=$(cat)
+ 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
+ needbase64=$3
- needbas64="$3"
+ _debug url $url
+ _debug payload "$payload"
- _info url $url
- _info 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
+ if [ "$DEBUG" ] ; then
CURL="$CURL --trace-ascii $dp "
fi
- payload64=$(echo -n $payload | base64 | _b64)
+ payload64=$(echo -n $payload | _base64 | _b64)
_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)
+ 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 [ "$needbas64" ] ; then
- response=$($CURL -X POST --data "$body" $url | base64)
+ if [ "$needbase64" ] ; then
+ response="$($CURL -X POST --data "$body" $url | _base64)"
else
- response=$($CURL -X POST --data "$body" $url)
+ response="$($CURL -X POST --data "$body" $url)"
fi
+
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
}
_get() {
url="$1"
- _info url $url
- response=$(curl --silent $url)
+ _debug url $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
}
@@ -210,79 +275,337 @@ _setopt() {
echo usage: $0 '"file" "opt" "=" "value" [";"]'
return
fi
+ if [ ! -f "$__conf" ] ; then
+ touch "$__conf"
+ fi
+ if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then
+ _debug OK
+ if [[ "$__val" == *"&"* ]] ; then
+ __val="$(echo $__val | sed 's/&/\\&/g')"
+ fi
+ text="$(cat $__conf)"
+ printf "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
+ else
+ _debug APP
+ echo "$__opt$__sep$__val$__end" >> "$__conf"
+ fi
+ _debug "$(grep -H -n "^$__opt$__sep" $__conf)"
+}
- if grep -H -n "^$__opt$__sep" $__conf ; then
- echo OK
- sed -i "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" $__conf
+#_savedomainconf key value
+#save to domain.conf
+_savedomainconf() {
+ key="$1"
+ value="$2"
+ if [ "$DOMAIN_CONF" ] ; then
+ _setopt $DOMAIN_CONF "$key" "=" "$value"
else
- echo APP
- echo "$__opt$__sep$__val$__end" >> $__conf
+ _err "DOMAIN_CONF is empty, can not save $key=$value"
fi
- grep -H -n "^$__opt$__sep" $__conf
+}
+
+#_saveaccountconf key value
+_saveaccountconf() {
+ key="$1"
+ value="$2"
+ if [ "$ACCOUNT_CONF_PATH" ] ; then
+ _setopt $ACCOUNT_CONF_PATH "$key" "=" "$value"
+ else
+ _err "ACCOUNT_CONF_PATH is empty, can not save $key=$value"
+ fi
+}
+
+_startserver() {
+ content="$1"
+ _NC="nc -q 1"
+ if nc -h 2>&1 | 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 -l -p $Le_HTTPPort -vv
+ else
+ echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -l -p $Le_HTTPPort > /dev/null
+ fi
+# done
+}
+
+_stopserver() {
+ pid="$1"
+
}
_initpath() {
- WORKING_DIR=~/.le
- domain=$1
- mkdir -p $WORKING_DIR
- ACCOUNT_KEY_PATH=$WORKING_DIR/account.acc
+
+ 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
+ API="$DEFAULT_CA"
+ else
+ API="$STAGE_CA"
+ _info "Using stage api:$API"
+ fi
+ fi
+
+ if [ -z "$ACME_DIR" ] ; then
+ ACME_DIR="/home/.acme"
+ fi
+
+ if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then
+ APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR/"
+ fi
+ domain="$1"
+ mkdir -p "$LE_WORKING_DIR"
+
+ if [ -z "$ACCOUNT_KEY_PATH" ] ; then
+ ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key"
+ fi
+
if [ -z "$domain" ] ; then
return 0
fi
- mkdir -p $WORKING_DIR/$domain
+ domainhome="$LE_WORKING_DIR/$domain"
+ mkdir -p "$domainhome"
+
+ if [ -z "$DOMAIN_CONF" ] ; then
+ DOMAIN_CONF="$domainhome/$Le_Domain.conf"
+ fi
- if [ -z "$CSR_PATH" ] ; then
- CSR_PATH=$WORKING_DIR/$domain/$domain.csr
+ 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
if [ -z "$CERT_KEY_PATH" ] ; then
- CERT_KEY_PATH=$WORKING_DIR/$domain/$domain.key
+ CERT_KEY_PATH="$domainhome/$domain.key"
fi
-
if [ -z "$CERT_PATH" ] ; then
- CERT_PATH=$WORKING_DIR/$domain/$domain.cer
+ CERT_PATH="$domainhome/$domain.cer"
+ fi
+ if [ -z "$CA_CERT_PATH" ] ; then
+ CA_CERT_PATH="$domainhome/ca.cer"
+ fi
+
+}
+
+
+_apachePath() {
+ 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
+ return 1
+ fi
+ return 0
+}
+
+_restoreApache() {
+ if [ -z "$usingApache" ] ; then
+ return 0
+ fi
+ _initpath
+ 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."
+ return 1;
+ fi
+ rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
+ return 0
}
-#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]"
+_setApache() {
+ _initpath
+ if ! _apachePath ; then
return 1
fi
- Le_Webroot=$1
- Le_Domain=$2
- Le_Alt=$3
- Le_Keylength=$4
+
+ #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
+
+
+Require all granted
+
+ " >> $httpdconf
+
+ if ! apachectl -t ; then
+ _err "Sorry, apache config error, please contact me."
+ _restoreApache
+ return 1;
+ fi
- if [ -z "$Le_Domain" ] ; then
- Le_Domain="$1"
+ 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
+ usingApache="1"
+ return 0
+}
+
+_clearup () {
+ _stopserver $serverproc
+ serverproc=""
+ _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
+ _info "Skip for removelevel:$2"
+ 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"
+ 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"
+
+
_initpath $Le_Domain
- 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"
+ 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: $(grep "^Le_NextRenewTimeStr" "$DOMAIN_CONF" | cut -d '=' -f 2)"
return 2
fi
fi
- if [ -z "$Le_Webroot" ] ; then
- echo Usage: $0 webroot a.com [b.com,c.com] [key-length]
- return 1
+ 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
+
+ _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
+ _err "Please install netcat(nc) tools first."
+ return 1
+ fi
+
+ 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 $Le_HTTPPort is already used by $(echo "$netprc" | cut -d : -f 4)"
+ _err "Please stop it first"
+ 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"
+ else
+ usingApache=""
fi
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
@@ -290,11 +613,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 | _b64 )
+ n=$(echo $modulus| _h2b | _base64 | _b64 )
jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
@@ -302,69 +625,197 @@ issue() {
HEADERPLACE='{"nonce": "NONCE", "alg": "RS256", "jwk": '$jwk'}'
_debug HEADER "$HEADER"
- accountkey_json=$(echo -n "$jwk" | sed "s/ //g")
- thumbprint=$(echo -n "$accountkey_json" | sha256sum | xxd -r -p | base64 | _b64)
+ accountkey_json=$(echo -n "$jwk" | tr -d ' ' )
+ thumbprint=$(echo -n "$accountkey_json" | openssl dgst -sha256 -binary | _base64 | _b64)
_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"
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
- _info "Register account Error."
+ _err "Register account Error."
+ _clearup
return 1
fi
- # verify each domain
- _info "verify each domain"
+ vtype="$VTYPE_HTTP"
+ if [[ "$Le_Webroot" == "dns"* ]] ; then
+ vtype="$VTYPE_DNS"
+ fi
- 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
- _info "new-authz error: $d"
+ vlist="$Le_Vlist"
+ # verify each domain
+ _info "Verify each domain"
+ sep='#'
+ if [ -z "$vlist" ] ; then
+ alldomains=$(echo "$Le_Domain,$Le_Alt" | tr ',' ' ' )
+ 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="$(printf $response | egrep -o '{[^{]*"type":"'$vtype'"[^}]*')"
+ _debug entry "$entry"
+
+ token="$(printf "$entry" | egrep -o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
+ _debug token $token
+
+ uri="$(printf "$entry" | egrep -o '"uri":"[^"]*'| cut -d : -f 2,3 | tr -d '"' )"
+ _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" | tr ',' ' ' )
+ 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 | openssl dgst -sha256 -binary | _base64 | _b64)"
+ _debug txt "$txt"
+ #dns
+ #1. check use api
+ d_api=""
+ 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"
+
+ 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
+
+ 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')
- _info token $token
-
- uri=$(echo "$http01" | sed 's/,/\n'/g| grep '"uri":'| cut -d : -f 2,3|sed 's/"//g')
- _info uri $uri
-
- 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
-
- wellknown_url="http://$d/.well-known/acme-challenge/$token"
- _debug wellknown_url "$wellknown_url"
+ 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" | tr ',' ' ' )
+ 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"
+ removelevel=""
+ token=""
+ 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"
+
+ 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"
+
+ 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
+ fi
- _debug challenge "$challenge"
_send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"
if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then
- _info "challenge error: $d"
+ _err "$d:Challenge error: $resource"
+ _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+ _clearup
return 1
fi
@@ -374,193 +825,464 @@ issue() {
_debug "checking"
if ! _get $uri ; then
- _info "verify error:$d"
+ _err "$d:Verify error:$resource"
+ _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+ _clearup
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 "verify success:$d"
+ _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)
- _info "verify error:$d"
- _debug $error
+ _err "$d:Verify error:$error"
+ _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+ _clearup
return 1;
fi
if [ "$status" == "pending" ] ; then
- _info "verify pending:$d"
+ _info "Pending"
else
- _info "verify error:$d"
+ _err "$d:Verify error:$response"
+ _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+ _clearup
return 1
fi
- done
- done
-
- _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"
+ done
+
+ done
+
+ _clearup
+ _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\"}" "needbase64"
- echo -----BEGIN CERTIFICATE----- > $CERT_PATH
- echo $response | sed "s/ /\n/g" >> $CERT_PATH
- echo -----END CERTIFICATE----- >> $CERT_PATH
- _info "Cert success."
- cat $CERT_PATH
- _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"
+ 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
+ echo -----BEGIN CERTIFICATE----- > "$CERT_PATH"
+ curl --silent "$Le_LinkCert" | openssl base64 -e >> "$CERT_PATH"
+ echo -----END CERTIFICATE----- >> "$CERT_PATH"
+ _info "Cert success."
+ cat "$CERT_PATH"
+
+ _info "Your cert is in $CERT_PATH"
+ fi
+
+ if [ -z "$Le_LinkCert" ] ; then
+ response="$(echo $response | openssl base64 -d -A)"
+ _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' | sed 's/>//g')
- _setopt $DOMAIN_CONF "Le_LinkIssuer" "=" "$Le_LinkIssuer"
+ Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' )
+ _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"
+ if [ "$Le_LinkIssuer" ] ; then
+ echo -----BEGIN CERTIFICATE----- > "$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
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\""
+ Le_CertCreateTimeStr=$(date -u )
+ _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\""
if [ ! "$Le_RenewalDays" ] ; then
- Le_RenewalDays=50
- fi
-
- _setopt $DOMAIN_CONF "Le_RenewalDays" "=" "$Le_RenewalDays"
-
- Le_NextRenewTime=$(date -u -d "+$Le_RenewalDays day" "+%s")
- _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_RealCertPath" "=" "\"$Le_RealCertPath\""
- if [ "$Le_RealCertPath" ] ; then
- if [ -f "$Le_RealCertPath" ] ; then
- rm -f $Le_RealCertPath
- fi
- ln -s $CERT_PATH $Le_RealCertPath
-
+ Le_RenewalDays=80
fi
- _setopt $DOMAIN_CONF "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\""
- 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\""
+ _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays"
- if [ "Le_ReloadCmd" ] ; then
- _info "Run Le_ReloadCmd: $Le_ReloadCmd"
- $Le_ReloadCmd
- fi
+ let "Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60"
+ _setopt "$DOMAIN_CONF" "Le_NextRenewTime" "=" "$Le_NextRenewTime"
-}
+ Le_NextRenewTimeStr=$( _time2str $Le_NextRenewTime )
+ _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\""
+
+ installcert $Le_Domain "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd"
+}
renew() {
Le_Domain="$1"
if [ -z "$Le_Domain" ] ; then
- echo Usage: $0 domain.com
+ _err "Usage: $0 domain.com"
return 1
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
+
+ _initpath $Le_Domain
+
+ if [ ! -f "$DOMAIN_CONF" ] ; then
+ _info "$Le_Domain is not a issued domain, skip."
+ return 0;
fi
- if [ -z "$Le_Webroot" ] ; then
- echo Le_Webroot can not found, please remove the conf file and issue a new cert
- return 1
+ 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
- issue $Le_Domain
+ 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() {
+ _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"
+
+ 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_RealCACertPath=""
+
+ Le_ReloadCmd=""
+
+ DOMAIN_CONF=""
+ DOMAIN_SSL_CONF=""
+ CSR_PATH=""
+ CERT_KEY_PATH=""
+ CERT_PATH=""
+ CA_CERT_PATH=""
+ ACCOUNT_KEY_PATH=""
+
+ wellknown_path=""
+
renew "$d"
done
}
+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_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
+ if [ "$Le_RealCACertPath" == "$Le_RealCertPath" ] ; then
+ echo "" >> "$Le_RealCACertPath"
+ cat "$CA_CERT_PATH" >> "$Le_RealCACertPath"
+ else
+ cat "$CA_CERT_PATH" > "$Le_RealCACertPath"
+ fi
+ 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"
+ eval $Le_ReloadCmd
+ fi
+
+}
+
+installcronjob() {
+ _initpath
+ _info "Installing cron job"
+ if ! crontab -l | grep 'le.sh cron' ; then
+ 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 * * * LE_WORKING_DIR=\"$LE_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 -
+ LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 6 | 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
+}
+
+_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
+#DEBUG=1 # Debug mode
+
+#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"
+
+#######################
+#Cloudxns.com:
+#CX_Key="1234"
+#
+#CX_Secret="sADDsdasdgdsf"
+
+ " > $ACCOUNT_CONF_PATH
+ fi
+}
+
install() {
_initpath
- if ! command -v "curl" ; then
- _info "Please install curl first."
- _info "sudo apt-get install curl"
+
+ #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 "
+ 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 "$INSTALL curl"
return 1
fi
- _info "Installing to $WORKING_DIR"
- mkdir -p $WORKING_DIR/
- cp le.sh $WORKING_DIR/
- chmod +x $WORKING_DIR/le.sh
+ if ! command -v "crontab" > /dev/null ; then
+ _err "Please install crontab first."
+ if [ "$YUM" ] ; then
+ _err "$INSTALL crontabs"
+ else
+ _err "$INSTALL crontab"
+ fi
+ return 1
+ fi
- if [ ! -f /bin/le.sh ] ; then
- ln -s $WORKING_DIR/le.sh /bin/le.sh
- ln -s $WORKING_DIR/le.sh /bin/le
+ if ! command -v "openssl" > /dev/null ; then
+ _err "Please install openssl first."
+ _err "$INSTALL openssl"
+ return 1
+ fi
+
+ _info "Installing to $LE_WORKING_DIR"
+
+ _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\""
+ _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
+
+ mkdir -p $LE_WORKING_DIR/dnsapi
+ cp dnsapi/* $LE_WORKING_DIR/dnsapi/
- _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
- fi
+ #to keep compatible mv the .acc file to .key file
+ if [ -f "$LE_WORKING_DIR/account.acc" ] ; then
+ mv "$LE_WORKING_DIR/account.acc" "$LE_WORKING_DIR/account.key"
+ fi
+ installcronjob
+ if [ ! -f "$ACCOUNT_CONF_PATH" ] ; then
+ _initconf
+ fi
_info OK
}
uninstall() {
+ uninstallcronjob
_initpath
- _info "Removing cron job"
- crontab -l | sed "/le.sh renewAll/d" | crontab -
-
- _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."
+
+ _profile="$(_detect_profile)"
+ if [ "$_profile" ] ; then
+ sed -i /le.env/d "$_profile"
+ fi
+
+ rm -f $LE_WORKING_DIR/le.sh
+ _info "The keys and certs are in $LE_WORKING_DIR, you can remove them by yourself."
}
+cron() {
+ renewAll
+}
+
+version() {
+ _info "$PROJECT"
+ _info "v$VER"
+}
showhelp() {
- echo "Usage: issue|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.
+ "
}
if [ -z "$1" ] ; then
showhelp
+else
+ "$@"
fi
-
-
-
-$1 $2 $3 $4 $5 $6 $7 $8
-
-
-
-