├── LICENSE ├── README.md ├── conf ├── letsencrypt.conf └── nginx.conf ├── manifest.json ├── scripts ├── install ├── remove └── upgrade └── sources └── certificateRenewer /LICENSE: -------------------------------------------------------------------------------- 1 | File containning the license of your package. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Let's encrypt 2 | ============= 3 | 4 | Automatic installation of Let's encrypt certificates on Yunohost 5 | 6 | ATTENTION - THIS APP IS NOW DEPRECIATED BECAUSE CERTIFICATE MANAGEMENT IS NOW 7 | INTEGRATED DIRECTLY INTO YUNOHOST SINCE 2.5. DO NOT INSTALL THIS APP IF YOU 8 | DON'T KNOW WHAT YOU'RE DOING ! 9 | 10 | **Disclaimer !** This is alpha/experimetal software, use at your own risks. 11 | Testers / feedbacks are welcome ! So far I tested the install on a Yunohost test 12 | VM, and a production server (RPi). It seems to work though the install takes a 13 | while. 14 | 15 | Features 16 | -------- 17 | 18 | - Automatic install of Let's encrypt ACME client 19 | - Automatic initial fetch of certificate(s) (one domain, or all domains) 20 | - Automatic renewal of soon-to-expire certificates through weekly cron job 21 | - Uninstall script if you want to fallback to self-signed certificates 22 | 23 | N.B. about the install for all domains : 24 | - if every fetch fails, install will be aborted ; 25 | - otherwise, it will simply show a warning if one fetch failed. 26 | 27 | To-do list 28 | ---------- 29 | 30 | - Upgrade/backup/restore ? 31 | - ... 32 | -------------------------------------------------------------------------------- /conf/letsencrypt.conf: -------------------------------------------------------------------------------- 1 | 2 | ################################# 3 | # Let's encrypt configuration # 4 | ################################# 5 | 6 | # Key size 7 | rsa-key-size = 4096 8 | 9 | # Email for notifications, or to recover stuff (?) 10 | email = ADMIN_EMAIL 11 | 12 | # Use text interface, agree to Terms of Service, renew the certificate 13 | text = True 14 | agree-tos = True 15 | 16 | # Use the webroot authenticator 17 | authenticator = webroot 18 | webroot-path = WEBROOT_PATH 19 | 20 | # (Staging server, for tests only !) 21 | # server = https://acme-staging.api.letsencrypt.org/directory 22 | 23 | 24 | -------------------------------------------------------------------------------- /conf/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | location '/.well-known/acme-challenge' { 4 | default_type "text/plain"; 5 | root /tmp/letsencrypt-auto; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Let's encrypt", 3 | "id": "letsencrypt", 4 | "description": { 5 | "en": "WARNING : DEPRECIATED APP ! A package that automatically install let's encrypt certificates" 6 | }, 7 | "licence": "free", 8 | "maintainer": { 9 | "name": "Alexandre Aubin", 10 | "email": "alex.aubin@mailoo.org", 11 | "url": "https://github.com/alexAubin" 12 | }, 13 | "multi_instance": "false", 14 | "arguments": { 15 | "install" : [ 16 | { 17 | "name": "depreciation_ack", 18 | "ask": { 19 | "en": "This application is deprecated because Yunohost 2.5 now includes built-in certificate management. Do you still to install it nevertheless ?", 20 | "fr": "Cette application est dépréciée car Yunohost 2.5 intègre maintenant de base un mécanisme de gestion des certificats. Voulez-vous quand même l'installer ?" 21 | }, 22 | "choices": ["Yes", "No"], 23 | "default": "No" 24 | }, 25 | { 26 | "name": "domain", 27 | "ask": { 28 | "en": "Choose a main domain", 29 | "fr": "Choisissez un domaine principal" 30 | }, 31 | "example": "example.com" 32 | }, 33 | { 34 | "name": "admin", 35 | "ask": { 36 | "en": "Choose an admin user", 37 | "fr": "Choisissez l'administrateur" 38 | }, 39 | "example": "johndoe" 40 | }, 41 | { 42 | "name": "installForAllDomains", 43 | "ask": { 44 | "en": "Install for all domains ?", 45 | "fr": "Installer pour tous les domaines ?" 46 | }, 47 | "choices": ["Yes", "No"], 48 | "default": "Yes" 49 | } 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #################################################### 4 | # Retrieve arguments / set global variables # 5 | #################################################### 6 | 7 | app=letsencrypt 8 | 9 | depreciation_ack=$1 10 | mainDomain=$2 11 | admin=$3 12 | installForAllDomains=$4 13 | 14 | #################################################### 15 | # Check that admin user is an existing account # 16 | #################################################### 17 | 18 | if [[ $depreciation_ack == "No" ]] 19 | then 20 | echo "Not installing because you didn't acknowledge the depreciation warning" >&2 21 | exit 1 22 | fi 23 | 24 | sudo yunohost user list --json | grep -q "\"username\": \"$admin\"" 25 | if [[ ! $? -eq 0 ]]; then 26 | echo "Error : the chosen admin user does not exist" 27 | exit 1 28 | fi 29 | 30 | sudo yunohost app setting $app admin -v $admin 31 | sudo yunohost app setting $app mainDomain -v $mainDomain 32 | 33 | #################################################### 34 | # Install letsencrypt # 35 | #################################################### 36 | 37 | installdir=/opt/yunohost/letsencrypt/ 38 | webrootdir=/tmp/letsencrypt-auto 39 | 40 | # Clone the git repo, call letencrypt to init stuff 41 | sudo yunohost app setting $app installdir -v $installdir 42 | sudo git clone https://github.com/letsencrypt/letsencrypt $installdir 43 | sudo $installdir/letsencrypt-auto 44 | 45 | if [ ! -d /etc/letsencrypt ]; then 46 | cat << EOF 47 | Directory /etc/letsencrypt was not found. Something went 48 | wrong during the install/initialization of letsencrypt ? 49 | EOF 50 | exit 2 51 | fi 52 | 53 | # Move the letsencrypt config file to /etc/letsencrypt 54 | sed -i "s|ADMIN_EMAIL|$admin@$mainDomain|g" ../conf/letsencrypt.conf 55 | sed -i "s|WEBROOT_PATH|$webrootdir|g" ../conf/letsencrypt.conf 56 | sudo cp ../conf/letsencrypt.conf /etc/letsencrypt/conf.ini 57 | 58 | # Create the rootdir directory 59 | sudo mkdir -p $webrootdir 60 | 61 | #################################################### 62 | # Get domain list and start loop # 63 | #################################################### 64 | 65 | domainsList=$mainDomain 66 | 67 | if [[ $installForAllDomains == "Yes" ]] 68 | then 69 | domainsList=$(sudo yunohost domain list \ 70 | | sed 's/domains://g' \ 71 | | sed 's/ - //g' \ 72 | | grep "." \ 73 | | sed 's/ //g' \ 74 | | tr '\n' ' ') 75 | fi 76 | 77 | sudo yunohost app setting $app installDomains -v "$domainsList" 78 | 79 | AT_LEAST_ONE_SUCCESS="False" 80 | AT_LEAST_ONE_FAILED="False" 81 | for domain in $domainsList 82 | do 83 | 84 | #################################################### 85 | # Nginx and SSOwat configuration # 86 | #################################################### 87 | 88 | ## Let's encrypt check the domain/server by adding files that can be 89 | ## accessed at domain.tld/.well-known/acme-challenge, which the Lets 90 | ## encrypt CA server tries to access 91 | 92 | # Nginx location block 93 | 94 | # 000- prefix is to be sure to get parsed before other files rules 95 | sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/000-$app.conf 96 | sudo service nginx restart 97 | 98 | # SSOwat unprotected regex 99 | 100 | regex="$domain/%.well%-known/acme%-challenge/.*$" 101 | regexList=$(sudo yunohost app setting $app unprotected_regex) 102 | if [[ $regexList == "" ]] 103 | then 104 | regexList="$regex" 105 | else 106 | regexList="$regexList,$regex" 107 | fi 108 | sudo yunohost app setting $app unprotected_regex -v "$regexList" 109 | sudo yunohost app ssowatconf 110 | 111 | #################################################### 112 | # Fetch certificate # 113 | #################################################### 114 | 115 | logfile=install-${domain}.log 116 | # Actually try to get the certificates 117 | sudo $installdir/letsencrypt-auto \ 118 | certonly \ 119 | --config /etc/letsencrypt/conf.ini \ 120 | --domains $domain \ 121 | 2>&1 | tee $logfile 122 | 123 | sudo cp ${logfile} /var/log/letsencrypt/${logfile} 124 | 125 | # Check it worked 126 | congrat=`sudo cat $logfile | grep Congratulations!` 127 | if [[ $congrat == "" ]]; then 128 | cat << EOF 129 | There was a problem fetching certificate for $domain. 130 | This shouldn't break your current certificates install, 131 | but you'll probably need to investigate what happened 132 | using /var/log/letsencrypt/$logfile and nginx logs 133 | before reattempting to install letsencrypt. 134 | EOF 135 | AT_LEAST_ONE_FAILED="True" 136 | continue 137 | else 138 | AT_LEAST_ONE_SUCCESS="True" 139 | fi 140 | 141 | #################################################### 142 | # Link certificates # 143 | #################################################### 144 | 145 | # Backup certs 146 | certPath=/etc/yunohost/certs/ 147 | sudo mv $certPath/$domain $certPath/$domain.beforeLetsEncrypt 148 | 149 | # Link letsencrypt certs in the yunohost cert folder 150 | sudo mkdir $certPath/$domain 151 | LE_LIVE_FOLDER=/etc/letsencrypt/live 152 | sudo ln -s $LE_LIVE_FOLDER/$domain/fullchain.pem $certPath/$domain/crt.pem 153 | sudo ln -s $LE_LIVE_FOLDER/$domain/privkey.pem $certPath/$domain/key.pem 154 | 155 | #################################################### 156 | # End of loop on domains # 157 | #################################################### 158 | 159 | done 160 | 161 | # ################################################# # 162 | # If no fetch succeeded, abort install # 163 | # ################################################# # 164 | 165 | if [[ ${AT_LEAST_ONE_SUCCESS} == "False" ]] 166 | then 167 | echo "All certificates fetch failed. Aborting install." 168 | exit 3 169 | fi 170 | 171 | #################################################### 172 | # Cron job for automatic renewal # 173 | #################################################### 174 | 175 | sed -i "s|ADMIN_EMAIL|$admin@$mainDomain|g" ../sources/certificateRenewer 176 | sed -i "s|DOMAIN_NAME|$mainDomain|g" ../sources/certificateRenewer 177 | chmod +x ../sources/certificateRenewer 178 | sudo cp ../sources/certificateRenewer /etc/cron.weekly/ 179 | 180 | # ################################################# # 181 | # Restart services # 182 | # ################################################# # 183 | 184 | # Add metronome permissions to the letsencrypt certs 185 | sudo chown root:metronome /etc/letsencrypt/archive/ 186 | sudo chown root:metronome /etc/letsencrypt/live/ 187 | sudo chmod g+rx /etc/letsencrypt/archive/ 188 | sudo chmod g+rx /etc/letsencrypt/live/ 189 | 190 | sudo service nginx restart 191 | sudo service postfix restart 192 | sudo service dovecot restart 193 | sudo service metronome restart 194 | 195 | # ################################################# # 196 | # Show warning if at least one cert install failed # 197 | # ################################################# # 198 | 199 | if [[ ${AT_LEAST_ONE_FAILED} == "True" ]] 200 | then 201 | echo "At least one certificate fetch failed. !" 202 | echo "Please check logs in /var/log/letsencrypt/install-*.log" 203 | fi 204 | -------------------------------------------------------------------------------- /scripts/remove: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # 5 | # Warning ! Not tested ! 6 | # 7 | # 8 | 9 | app=letsencrypt 10 | domains=$(sudo yunohost app setting $app installDomains) 11 | installdir=/opt/yunohost/letsencrypt/ 12 | backupFolder=/root/.letsencrypt-backupDuringUninstall 13 | 14 | sudo mkdir $backupFolder 15 | 16 | 17 | # Remove letsencrypt git repo clone 18 | sudo rm -rf $installdir 19 | 20 | # Remove cron job 21 | sudo rm /etc/cron.weekly/certificateRenewer 22 | 23 | # Backup /etc/letsencrypt 24 | sudo mv /etc/letsencrypt $backupFolder/etc_letsencrypt 25 | 26 | #################################################### 27 | # Restore backuped certificates # 28 | #################################################### 29 | 30 | certPath=/etc/yunohost/certs/ 31 | 32 | for domain in $domains 33 | do 34 | # Backup nginx conf file 35 | sudo mv /etc/nginx/conf.d/$domain.d/000-$app.conf $backupFolder/$domain.nginx 36 | 37 | # If a backup of self-signed cert exist 38 | if [ -d "$certPath/$domain.beforeLetsEncrypt" ]; then 39 | # Backup letsencrypt certs 40 | sudo mv $certPath/$domain $backupFolder 41 | # Link certs that were used before install 42 | sudo mv $certPath/$domain.beforeLetsEncrypt $certPath/$domain 43 | fi 44 | done 45 | 46 | # ################################################# # 47 | # Restart services # 48 | # ################################################# # 49 | 50 | sudo service nginx restart 51 | sudo service metronome restart 52 | 53 | -------------------------------------------------------------------------------- /scripts/upgrade: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | app=letsencrypt 4 | domains=$(sudo yunohost app setting $app installDomains) 5 | 6 | # Add 000- as prefix to nginx conf 7 | for domain in $domains 8 | do 9 | # If using old nginx conf path (without prefix), migrate it 10 | if [ -e "/etc/nginx/conf.d/$domain.d/$app.conf" ]; then 11 | sudo mv /etc/nginx/conf.d/$domain.d/$app.conf \ 12 | /etc/nginx/conf.d/$domain.d/000-$app.conf 13 | fi 14 | done 15 | 16 | # Move letsencrypt client to /opt/yunohost/letsencrypt 17 | if sudo test -f "/root/.letsencrypt/letsencrypt-auto"; 18 | then 19 | sudo mkdir -p /opt/yunohost/ 20 | sudo mv /root/.letsencrypt/ /opt/yunohost/letsencrypt/ 21 | sudo sed -i "s|\/root\/\.letsencrypt/|/opt/yunohost/letsencrypt/|g" /etc/cron.weekly/certificateRenewer 22 | sudo yunohost app setting $app installdir -v /opt/yunohost/letsencrypt/ 23 | fi 24 | 25 | # Update certificateRenewer 26 | admin=$(sudo yunohost app setting $app admin) 27 | mainDomain=$(sudo cat /etc/yunohost/current_host) 28 | 29 | sed -i "s|ADMIN_EMAIL|$admin@$mainDomain|g" ../sources/certificateRenewer 30 | sed -i "s|DOMAIN_NAME|$mainDomain|g" ../sources/certificateRenewer 31 | chmod +x ../sources/certificateRenewer 32 | sudo cp ../sources/certificateRenewer /etc/cron.weekly/ 33 | 34 | sudo service nginx reload 35 | -------------------------------------------------------------------------------- /sources/certificateRenewer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################### 4 | # Inspired from # 5 | # https://community.letsencrypt.org/t/ # 6 | # how-to-completely-automating-certificate-renewals-on-debian/5615 # 7 | ############################################################################### 8 | 9 | ################### 10 | # Configuration # 11 | ################### 12 | 13 | # This line MUST be present in all scripts executed by cron! 14 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 15 | 16 | # Certs that will expire in less than this value will be renewed 17 | REMAINING_DAYS_TO_RENEW=30 18 | 19 | # Command to execute if certs have been renewed 20 | SERVICES_TO_RESTART="nginx postfix dovecot metronome" 21 | 22 | # Parameters for email alert 23 | EMAIL_ALERT_FROM="cron-certrenewer@DOMAIN_NAME (Cron certificate renewer)" 24 | EMAIL_ALERT_TO="ADMIN_EMAIL" 25 | EMAIL_ALERT_SUBJ="WARNING: SSL certificate renewal for CERT_NAME failed!" 26 | 27 | # Letsencrypt stuff 28 | 29 | # The executable 30 | LEBIN="/opt/yunohost/letsencrypt/letsencrypt-auto" 31 | # The config file 32 | LECFG="/etc/letsencrypt/conf.ini" 33 | # The directory where current .pem certificates are stored 34 | LELIVE="/etc/letsencrypt/live" 35 | # Renewal directory, which contains renewal configs for all certificates. 36 | LERENEWAL="/etc/letsencrypt/renewal" 37 | 38 | ################ 39 | # Misc tools # 40 | ################ 41 | 42 | # ----------------------------------- 43 | # Given a certificate file, return the number of days before it expires 44 | # ----------------------------------- 45 | function daysBeforeCertificateExpire() 46 | { 47 | local CERT_FILE=$1 48 | local DATE_EXPIRE=$(openssl x509 -in $CERT_FILE -text -noout \ 49 | | grep "Not After" \ 50 | | cut -c 25-) 51 | local D1=$(date -d "$DATE_EXPIRE" +%s) 52 | local D2=$(date -d "now" +%s) 53 | local DAYS_EXP=$(( ( D1 - D2) / 86400 )) 54 | echo $DAYS_EXP 55 | } 56 | 57 | # ----------------------------------- 58 | # Send an alert email stating that the renewing of a cert failed, and paste the 59 | # logs into the mail body 60 | # ----------------------------------- 61 | function sendAlert() 62 | { 63 | local CERT_NAME=$1 64 | local LOG_FILE=$2 65 | local SUBJ=$(echo $EMAIL_ALERT_SUBJ | sed "s/CERT_NAME/${CERT_NAME}/g") 66 | 67 | echo -e " Here is the log of what happened\n" \ 68 | "Consider also checking /var/log/letsencrypt/\n" \ 69 | "--------------------------------------------\n" \ 70 | | cat - ${LOG_FILE} \ 71 | | mail -s "${SUBJ}" \ 72 | -r "${EMAIL_ALERT_FROM}" \ 73 | ${EMAIL_ALERT_TO} 74 | } 75 | 76 | # ----------------------------------- 77 | # ----------------------------------- 78 | function restartServices() 79 | { 80 | eval "/bin/sync" 81 | 82 | local SERVICE 83 | for SERVICE in ${SERVICES_TO_RESTART} 84 | do 85 | eval "service ${SERVICE} restart" 86 | done 87 | } 88 | 89 | ############################### 90 | # Actual lets encrypt stuff # 91 | ############################### 92 | 93 | # ----------------------------------- 94 | # Given a certificate name, echo True or False if it will soon expire 95 | # (see REMAINING_DAYS_TO_RENEW) 96 | # ----------------------------------- 97 | function certificateNeedsToBeRenewed() 98 | { 99 | local CERT_NAME=$1 100 | local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem" 101 | local DAYS_BEFORE_EXPIRE=`daysBeforeCertificateExpire $CERT_FILE` 102 | 103 | if [[ ${DAYS_BEFORE_EXPIRE} -lt ${REMAINING_DAYS_TO_RENEW} ]] 104 | then 105 | echo "True" 106 | else 107 | echo "False" 108 | fi 109 | } 110 | 111 | # ----------------------------------- 112 | # Given a certificate name, attempt to renew it 113 | # Stuff is logged in a file 114 | # ----------------------------------- 115 | function renewCertificate() 116 | { 117 | local CERT_NAME=$1 118 | local LOG_FILE=$2 119 | local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem" 120 | local CERT_CONF="${LERENEWAL}/${CERT_NAME}.conf" 121 | local DOMAINS=$(openssl x509 -in ${CERT_FILE} -text | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:") 122 | local LAST_CHAR=$(echo ${DOMAINS} | awk '{print substr($0,length,1)}') 123 | if [ "${LAST_CHAR}" = "," ] 124 | then 125 | local DOMAINS=$(echo ${DOMAINS} |awk '{print substr($0, 1, length-1)}') 126 | fi 127 | 128 | # Recreate the webroot folder (expected to be in /tmp/) 129 | WEBROOT_PATH=$(cat $CERT_CONF \ 130 | | grep webroot_path \ 131 | | tr ',' ' ' \ 132 | | awk '{print $3}') 133 | mkdir -p ${WEBROOT_PATH} 134 | 135 | rm ${LOG_FILE} 136 | touch ${LOG_FILE} 137 | ${LEBIN} certonly \ 138 | --renew-by-default \ 139 | --config "${LECFG}" \ 140 | --domains "${DOMAINS}" \ 141 | > ${LOG_FILE} 2>&1 142 | } 143 | 144 | # ----------------------------------- 145 | # Attempt to renew all certificates in LELIVE directory 146 | # ----------------------------------- 147 | function renewAllCertificates() 148 | { 149 | local AT_LEAST_ONE_CERT_RENEWED="False" 150 | 151 | # Loop on certificates in live directory 152 | local CERT 153 | for CERT in $(ls -1 "${LELIVE}") 154 | do 155 | #echo "Checking $CERT certificate ..." 156 | # Check if current certificate needs to be renewed 157 | if [[ `certificateNeedsToBeRenewed ${CERT}` == "True" ]] 158 | then 159 | echo " > $CERT certificate needs to be renewed. Attempting to ..." 160 | 161 | # If yes, attempt to renew it 162 | local LOG_FILE="/tmp/cron-cert-renewer.log" 163 | renewCertificate ${CERT} ${LOG_FILE} 164 | 165 | # Check it worked 166 | if [[ `certificateNeedsToBeRenewed $CERT` == "False" ]] 167 | then 168 | echo " > Cert was succesfully renewed." 169 | local AT_LEAST_ONE_CERT_RENEWED="True" 170 | else 171 | echo " > An error occured, an email was sent." 172 | sendAlert ${CERT} ${LOG_FILE} 173 | fi 174 | #else 175 | #echo " > No need to renew it." 176 | fi 177 | done 178 | 179 | if [[ ${AT_LEAST_ONE_CERT_RENEWED} == "True" ]] 180 | then 181 | return 1 182 | else 183 | return 0 184 | fi 185 | } 186 | 187 | ################### 188 | # Main function # 189 | ################### 190 | 191 | function main() 192 | { 193 | renewAllCertificates 194 | 195 | if [[ $? -eq 1 ]] 196 | then 197 | restartServices 198 | fi 199 | 200 | } 201 | main 202 | 203 | 204 | --------------------------------------------------------------------------------