diff --git a/cm.sh b/cm.sh index 28b9ab77d6c56199b7fc2d73ccb74917d12a1ff2..7ea28f983b3d0da660374655743b169b34d66380 100755 --- a/cm.sh +++ b/cm.sh @@ -37,6 +37,7 @@ # 2023-02-01 <axel.hahn@unibe.ch> skip function _fixKeyfile with former workaround # 2023-05-08 <axel.hahn@unibe.ch> fix: "key and cert do not match" # 2024-03-21 <axel.hahn@unibe.ch> shorter sleep times +# 2025-01-08 <axel.hahn@unibe.ch> support for http challenge on port 80; more params supported for force renew, http challenge # ====================================================================== @@ -46,6 +47,8 @@ # # ---------------------------------------------------------------------- +_version="2025-01-08" + logdir="./log" touchfile="$logdir/lastchange.txt" logfile="$logdir/certmanager.log" @@ -67,6 +70,12 @@ CM_writedebuglog=0 CM_timer_start=$( date +%s.%N ) +# webroot to write challenge files into - +CM_webroot="" + +# force renew (default: disabled - remember quotas on LE) +CM_force=0 + # ---------------------------------------------------------------------- # # INTERNAL FUNCTIONS @@ -76,58 +85,58 @@ CM_timer_start=$( date +%s.%N ) # BUGFIX: acme.sh does not create a new key file on renew. # After switching from csr method to param -d we got a 0 byte Keyfile function _fixKeyfile(){ - echo "SKIP: _fixKeyfile won't be executed anymore." - # local _acme_keyfile=~/.acme.sh/${CM_fqdn}/${CM_fqdn}.key - # if test ! -f "$_acme_keyfile" - # then - # echo "FIX: copy key from csr folder $CM_filekey to $_acme_keyfile" - # if ! cp "$CM_filekey" "$_acme_keyfile" - # then - # exit 1 - # fi - # fi + echo "SKIP: _fixKeyfile won't be executed anymore." + # local _acme_keyfile=~/.acme.sh/${CM_fqdn}/${CM_fqdn}.key + # if test ! -f "$_acme_keyfile" + # then + # echo "FIX: copy key from csr folder $CM_filekey to $_acme_keyfile" + # if ! cp "$CM_filekey" "$_acme_keyfile" + # then + # exit 1 + # fi + # fi } # internal function; list certificates incl. creation date and renew date function _listCerts(){ - $ACME --list + $ACME --list } # internal function; get a list of fqdn of all existing certs function _listCertdomains(){ - _listCerts | sed -n '2,$p' | awk '{ print $1 }' + _listCerts | sed -n '2,$p' | awk '{ print $1 }' } # internal function; checks if a certificate for a given FQDN already exists # used in _certMustExist, _certMustNotExist # param string FQDN function _certExists(){ - # _listCertdomains | grep "^${CM_fqdn}$" >/dev/null - $ACME --info -d "${CM_fqdn}" 2>/dev/null | grep "letsencrypt.org" >/dev/null + # _listCertdomains | grep "^${CM_fqdn}$" >/dev/null + $ACME --info -d "${CM_fqdn}" 2>/dev/null | grep "letsencrypt.org" >/dev/null } # internal function; a certificate of a given FQDN must exist - otherwise # the script will be aborted # param string FQDN function _certMustExist(){ - _certExists - if [ $? -ne 0 ]; then - echo "ERROR: cert ${CM_fqdn} was not added yet." - exit 1 - fi + _certExists + if [ $? -ne 0 ]; then + _we "Cert ${CM_fqdn} was not added yet." + exit 1 + fi } # internal function; a certificate of a given FQDN must not exist - otherwise # the script will be aborted # param string FQDN function _certMustNotExist(){ - if _certExists - then - echo "ERROR: cert ${CM_fqdn} was added already." - # exit 1 - echo "Press Ctrl+C to abort within the next 3 sec..." - sleep 3 - fi + if _certExists + then + _we "Cert ${CM_fqdn} was added already." + # exit 1 + echo "Press Ctrl+C to abort within the next 3 sec..." + sleep 3 + fi } # internal function: transfer generated/ updated cert data to a @@ -135,35 +144,35 @@ function _certMustNotExist(){ # used in public_add and public_renew # used in ADD and RENEW action function _certTransfer(){ - _wd "--- acme internal data - ~/.acme.sh/${CM_fqdn}" - ls -l ~/.acme.sh/${CM_fqdn} + _wd "--- acme internal data - ~/.acme.sh/${CM_fqdn}" + ls -l ~/.acme.sh/${CM_fqdn} - _wd "--- delete current files in ${CM_dircerts}/ if they already exist." + _wd "--- delete current files in ${CM_dircerts}/ if they already exist." test -d ${CM_dircerts} && rm -f "${CM_dircerts}/*" 2>/dev/null - _wd "--- transfer acme.sh files to ${CM_dircerts}/" - if ! $ACME \ - --install-cert \ - -d "${CM_fqdn}" \ - --key-file "${CM_outfile_key}" \ - --cert-file "${CM_outfile_cert}" \ - --fullchain-file "${CM_outfile_chain}" \ - --ca-file "${CM_outfile_ca}" + _wd "--- transfer acme.sh files to ${CM_dircerts}/" + if ! $ACME \ + --install-cert \ + -d "${CM_fqdn}" \ + --key-file "${CM_outfile_key}" \ + --cert-file "${CM_outfile_cert}" \ + --fullchain-file "${CM_outfile_chain}" \ + --ca-file "${CM_outfile_ca}" then echo "ERROR occured during acme transfer. Removing files in ${CM_dircerts} to prevent strange effects..." rm -f "${CM_dircerts}/*" exit 2 fi - echo "OK." - - # _wd "--- copy key to ${CM_dircerts}" - # cp ${CM_filekey} ${CM_outfile_key} + echo "OK." + + # _wd "--- copy key to ${CM_dircerts}" + # cp ${CM_filekey} ${CM_outfile_key} - _wd "--- create chained file for haproxy" - cat "${CM_outfile_chain}" "${CM_outfile_key}" > "${CM_outfile_haproxy}" + _wd "--- create chained file for haproxy" + cat "${CM_outfile_chain}" "${CM_outfile_key}" > "${CM_outfile_haproxy}" - _wd "--- content of output dir $CM_dircerts:" - if ! ls -l "${CM_outfile_cert}" "${CM_outfile_chain}" "${CM_outfile_key}" "${CM_outfile_haproxy}" + _wd "--- content of output dir $CM_dircerts:" + if ! ls -l "${CM_outfile_cert}" "${CM_outfile_chain}" "${CM_outfile_key}" "${CM_outfile_haproxy}" then echo "ERROR missing a file (or no access?)" rm -f "${CM_dircerts}/*" @@ -174,23 +183,23 @@ function _certTransfer(){ # internal function; show md5 hashsums for certificate and key # for visual comparison if the match function _certMatching(){ - # CSR USAGE WAS REMOVED - # local md5_csr=$( test -f ${CM_filecsr} && openssl req -noout -modulus -in ${CM_filecsr} | openssl md5 | cut -f 2 -d " " ) - local md5_key=$( test -f ${CM_outfile_key} && openssl rsa -noout -modulus -in ${CM_outfile_key} | openssl md5 | cut -f 2 -d " " ) - local md5_cert=$( test -f ${CM_outfile_cert} && openssl x509 -noout -modulus -in ${CM_outfile_cert} | openssl md5 | cut -f 2 -d " " ) - - echo - echo "--- compare hashes" - # CSR USAGE WAS REMOVED - # echo "csr : $md5_csr (used for creation of cert)" - echo "key : $md5_key" - echo "cert : $md5_cert" - if [ "$md5_key" = "$md5_cert" ]; then - echo "OK, key and cert match :-)" - else - echo "ERROR: key and cert do NOT MATCH!" - fi - echo + # CSR USAGE WAS REMOVED + # local md5_csr=$( test -f ${CM_filecsr} && openssl req -noout -modulus -in ${CM_filecsr} | openssl md5 | cut -f 2 -d " " ) + local md5_key; md5_key=$( test -f ${CM_outfile_key} && openssl rsa -noout -modulus -in ${CM_outfile_key} | openssl md5 | cut -f 2 -d " " ) + local md5_cert; md5_cert=$( test -f ${CM_outfile_cert} && openssl x509 -noout -modulus -in ${CM_outfile_cert} | openssl md5 | cut -f 2 -d " " ) + + echo + echo "--- compare hashes" + # CSR USAGE WAS REMOVED + # echo "csr : $md5_csr (used for creation of cert)" + echo "key : $md5_key" + echo "cert : $md5_cert" + if [ "$md5_key" = "$md5_cert" ]; then + echo "OK, key and cert match :-)" + else + _we "Key and cert do NOT MATCH!" + fi + echo } # internal function: dig for given fqdn. @@ -202,15 +211,15 @@ function _certMatching(){ # param string optional filter on output of dig (regex) function _checkDig(){ local myfqdn=$1 - local _type=${2:-"a"} - local _verify=${3:-"."} + local _type=${2:-"a"} + local _verify=${3:-"."} if which dig >/dev/null then # _wd "[$myfqdn] exists as type [$_type] in DNS?" if ! dig "${myfqdn}" "${_type}" | grep "^${myfqdn}" | grep -E "${_verify}" then - echo "ERROR: [$myfqdn] was not found. Maybe there is a typo in the hostname or it does not exist in DNS." + _we "[$myfqdn] was not found. Maybe there is a typo in the hostname or it does not exist in DNS." exit 2 fi _wd "OK: [$myfqdn] exists in DNS." @@ -228,24 +237,23 @@ function _checkDig(){ # Function stops if a fqdn was not found in DNS. # param string fqdn(s) that are part of the certificate function _dnsCheck(){ - local altdns= - local _mydomain= - local _subdomain='_acme-challenge' - - for _mydomain in $* - do - _wd "dig check - domain for cert" - _checkDig "$_mydomain" "a" "IN.*(A|CNAME)" # matches A and CNAME records + local altdns= + local _mydomain= + local _subdomain='_acme-challenge' - if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null - then - _wd "dig check - cname ${_subdomain}.${_mydomain} must exist" - _checkDig "${_subdomain}.${_mydomain}" "cname" - _wd "dig check - cname ${_subdomain}.${_mydomain} must point to ${_subdomain}.${CM_challenge_alias}" - _checkDig "${_subdomain}.${_mydomain}" "cname" "${_subdomain}.${CM_challenge_alias}" - fi - done + for _mydomain in $* + do + _wd "dig check - domain for cert" + _checkDig "$_mydomain" "a" "IN.*(A|CNAME)" # matches A and CNAME records + if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null + then + _wd "dig check - cname ${_subdomain}.${_mydomain} must exist" + _checkDig "${_subdomain}.${_mydomain}" "cname" + _wd "dig check - cname ${_subdomain}.${_mydomain} must point to ${_subdomain}.${CM_challenge_alias}" + _checkDig "${_subdomain}.${_mydomain}" "cname" "${_subdomain}.${CM_challenge_alias}" + fi + done } # CSR USAGE WAS REMOVED @@ -253,106 +261,105 @@ function _dnsCheck(){ # this function is used in public_add # function _UNUSED_gencsr(){ -# local altdns= +# local altdns= -# for myalt in $* -# do -# altdns="${altdns}DNS:$myalt," -# done -# altdns=$( echo $altdns | sed "s#,\$##" ) +# for myalt in $* +# do +# altdns="${altdns}DNS:$myalt," +# done +# altdns=$( echo $altdns | sed "s#,\$##" ) # _wd "--- $CM_fqdn" -# _wd "DNS alternative names: $altdns" +# _wd "DNS alternative names: $altdns" -# rm -f $CM_filecnf $CM_filekey $CM_filecsr -# mkdir -p "${CM_dircsr}" 2>/dev/null +# rm -f $CM_filecnf $CM_filekey $CM_filecsr +# mkdir -p "${CM_dircsr}" 2>/dev/null -# cat $csrfile \ -# | sed "s#__FQDN__#$CM_fqdn#g" \ -# | sed "s#__ALTNAMES__#$altdns#g" \ -# > $CM_filecnf || exit 1 +# cat $csrfile \ +# | sed "s#__FQDN__#$CM_fqdn#g" \ +# | sed "s#__ALTNAMES__#$altdns#g" \ +# > $CM_filecnf || exit 1 -# # generate csr -# _wd "creating key and csr" -# openssl req -new -config $CM_filecnf -keyout $CM_filekey -out $CM_filecsr || exit 1 +# # generate csr +# _wd "creating key and csr" +# openssl req -new -config $CM_filecnf -keyout $CM_filekey -out $CM_filecsr || exit 1 -# # view csr -# # openssl req -noout -text -in $CM_filecsr -# ls -ltr $CM_filecnf $CM_filekey $CM_filecsr +# # view csr +# # openssl req -noout -text -in $CM_filecsr +# ls -ltr $CM_filecnf $CM_filekey $CM_filecsr # } # internal function; get a sorted list of DNS aliases in the current cert function _getAliases(){ - _sortWords $( - openssl x509 -noout -text -in ${CM_outfile_cert} \ - | grep -E "(DNS:)" \ - | sed "s#^ *##g" \ - | sed "s#DNS:##g" \ - | sed "s#,##g" - ) + _sortWords "$( + openssl x509 -noout -text -in ${CM_outfile_cert} \ + | grep -E "(DNS:)" \ + | sed "s#^ *##g" \ + | sed "s#DNS:##g" \ + | sed "s#,##g" + )" } # internal function; check if a required 2nd CLI parameter was given # if not the script will abort function _requiresFqdn(){ - if [ -z "$CM_fqdn" ]; then - echo "ERROR: 2nd parameter must be a FQDN for Main_Domain." - exit 1 - fi + if [ -z "$CM_fqdn" ]; then + _we "2nd parameter must be a FQDN for Main_Domain." + exit 1 + fi } # internal function; it shows a message if the current instance uses a stage # server. It shows a message that it is allowed to test arround ... or to be # careful with LE requests on a production system function _testStaging(){ - echo $ACME_Params | grep "\-\-staging" >/dev/null - if [ $? -eq 0 ]; then - _wd "Using Let's Encrypt STAGE environment ..." - _wd "You can test and mess around. Do not use certs in production." - else - _wd "Using Let's Encrypt LIVE environment for production." - _wd "Be careful with count of connects to Let's Encrypt servers." - fi - echo + if echo "${ACME_Params}" | grep -- "--staging" >/dev/null; then + _wd "Using Let's Encrypt STAGE environment ..." + _wd "You can test and mess around. Do not use certs in production." + else + _wd "Using Let's Encrypt LIVE environment for production." + _wd "Be careful with count of connects to Let's Encrypt servers." + fi + echo } # internal function; if a user was set as CM_user then verify it with # current user function _testUser(){ - if [ ! -z "$CM_user" ]; then - local _sUser=`id | cut -f 2 -d "(" | cut -f 1 -d ")"` - if [[ $_sUser != "$CM_user" ]]; then - echo "ERROR: Run this script under user [$CM_user] - not as $_sUser." - exit 1 - fi - fi - + if [ ! -z "$CM_user" ]; then + local _sUser + _sUser="$( id | cut -f 2 -d "(" | cut -f 1 -d ")")" + if [[ $_sUser != "$CM_user" ]]; then + _we "Run this script under user [$CM_user] - not as $_sUser." + exit 1 + fi + fi } # set update message into access log file # global bool CM_writedebuglog flag to write access log. # param string(s) message function _debuglog(){ - if [ ${CM_writedebuglog} -eq 1 ]; then - echo "$( date ) $CM_fqdn [$$] | $(show_timer) | $*" >> ${debuglogfile} - fi + if [ ${CM_writedebuglog} -eq 1 ]; then + echo "$( date ) $CM_fqdn [$$] | $(show_timer) | $*" >> ${debuglogfile} + fi } # set update message in a file # param string(s) message function _update(){ - echo "[$( date )] $*" > ${touchfile} - test ${CM_writelog} -ne 0 && echo "[$( date )] $*" >> ${logfile} + echo "[$( date )] $*" > ${touchfile} + test ${CM_writelog} -ne 0 && echo "[$( date )] $*" >> ${logfile} } # "neverending" loop that waits until the current process is # the one with lowest PID function _wait_for_free_slot(){ local _bWait=true - _debuglog "start in _wait_for_free_slot" + _debuglog "start in _wait_for_free_slot" typeset -i local _iFirstPID=0 typeset -i local _iPos=0 local _sProcesses - + _wd "--- Need to wait until own process PID $$ is on top ... " while [ $_bWait = true ]; do @@ -374,69 +381,180 @@ function _wait_for_free_slot(){ sleep $((3 + RANDOM % 3)); fi done - _debuglog "end _wait_for_free_slot" + _debuglog "end _wait_for_free_slot" } # write debug output if CM_showdebug is set to 1 +# param string message (prefix "DEBUG" will be added in front) function _wd(){ - test ${CM_showdebug} -ne 0 && echo "DEBUG: $*" + test ${CM_showdebug} -ne 0 && echo -e "\e[1;30mDEBUG: $* \e[0m" +} + + +# show error message; +# param string message (prefix "ERROR" will be added in front) +function _we(){ + echo -e "\e[1;37;41mERROR: $*\e[0m" } # set environment for a single certificate based on FQDN # param string FQDN function _setenv(){ - CM_fqdn=$1 - # CSR USAGE WAS REMOVED - # keeping vars to delete files of existing certs that used a csr - CM_filecsr="${CM_dircsr}/${CM_fqdn}.csr" - CM_filecnf="${CM_dircsr}/${CM_fqdn}.cnf" - CM_filekey="${CM_dircsr}/${CM_fqdn}.key" - - CM_dircerts="${CM_diracme}/${CM_fqdn}" - CM_outfile_cert=${CM_dircerts}/${CM_fqdn}.cert.cer - CM_outfile_chain=${CM_dircerts}/${CM_fqdn}.fullchain.cer - CM_outfile_key=${CM_dircerts}/${CM_fqdn}.key.pem - CM_outfile_haproxy=${CM_dircerts}/${CM_fqdn}.haproxy.pem - CM_outfile_ca=${CM_dircerts}/${CM_fqdn}.ca.cer - - # echo $CM_fqdn; set | grep "^CM_"; echo - + CM_fqdn=$1 + # CSR USAGE WAS REMOVED + # keeping vars to delete files of existing certs that used a csr + CM_filecsr="${CM_dircsr}/${CM_fqdn}.csr" + CM_filecnf="${CM_dircsr}/${CM_fqdn}.cnf" + CM_filekey="${CM_dircsr}/${CM_fqdn}.key" + + CM_dircerts="${CM_diracme}/${CM_fqdn}" + CM_outfile_cert=${CM_dircerts}/${CM_fqdn}.cert.cer + CM_outfile_chain=${CM_dircerts}/${CM_fqdn}.fullchain.cer + CM_outfile_key=${CM_dircerts}/${CM_fqdn}.key.pem + CM_outfile_haproxy=${CM_dircerts}/${CM_fqdn}.haproxy.pem + CM_outfile_ca=${CM_dircerts}/${CM_fqdn}.ca.cer + + # echo $CM_fqdn; set | grep "^CM_"; echo } + # internal function; helper: sort words in alphabetic order function _sortWords(){ - echo $* | tr " " "\n" | sort | tr "\n" " " + echo "$*" | tr " " "\n" | sort | tr "\n" " " } # internal function; verify fqdn in cli params - each fqdn is allowed only once. # on error it shows the count of usage of each fqdn function _testFqdncount(){ - typeset -i local iHostsInParam=$( echo $* | wc -w ) - typeset -i iHostsUniq=$( echo $* | tr " " "\n" | sort -u | wc -w ) - if [ $iHostsInParam -ne $iHostsUniq ]; then - echo "ERROR: each given FQDN is allowed only once. You need to remove double entries." - - for myhost in $( echo $* | tr " " "\n" | sort -u ) - do - typeset -i iHostcount=$( echo $* | tr " " "\n" | grep "^$myhost$" | wc -l ) - test $iHostcount -gt 1 && echo " $iHostcount x $myhost" - done - echo - exit 1 - fi + typeset -i local iHostsInParam=$( echo $* | wc -w ) + typeset -i iHostsUniq=$( echo $* | tr " " "\n" | sort -u | wc -w ) + if [ $iHostsInParam -ne $iHostsUniq ]; then + _we "Each given FQDN is allowed only once. You need to remove double entries." + + for myhost in $( echo $* | tr " " "\n" | sort -u ) + do + typeset -i iHostcount=$( echo $* | tr " " "\n" | grep "^$myhost$" | wc -l ) + test $iHostcount -gt 1 && echo " $iHostcount x $myhost" + done + echo + exit 1 + fi } # get time in sec and milliseconds since start # no parameter is required function show_timer(){ - local timer_end=$( date +%s.%N ) - local totaltime=$( awk "BEGIN {print $timer_end - $CM_timer_start }" ) + local timer_end; timer_end=$( date +%s.%N ) + local totaltime; totaltime=$( awk "BEGIN {print $timer_end - $CM_timer_start }" ) + + local sec_time; sec_time=$( echo "$totaltime" | cut -f 1 -d "." ) + test -z "$sec_time" && sec_time=0 + + local ms_time; ms_time=$( echo "$totaltime" | cut -f 2 -d "." | cut -c 1-3 ) + + echo "$sec_time.$ms_time sec" +} + +# show help text +function show_help(){ + self="$( basename "$0" )" + cat <<EOF - local sec_time=$( echo $totaltime | cut -f 1 -d "." ) - test -z "$sec_time" && sec_time=0 +HELP + +Wrapper script for acme.sh to handle certificates. +For automation you should use the "ensure" action that detects if a +certificate must be created, renewed or re-created. + +📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/iml-certman> +📜 License: GNU GPL 3.0 +📗 Docs: <https://os-docs.iml.unibe.ch/iml-certman/> + +SYNTAX: + + $self [OPTIONS] ACTION <FQDN> [<ALIASES>] + +OPTIONS: + + -a|--alias + Use http challenge with existing http server on port 80 + Challenge file will be written into ../alias-dir/ + + -f|--force + Force renew of certificate even if it is not due yet. + Use it carefully - remember the execution limits on Let's Encrypt. + + -t|--trace + the output additionally will be written into a tracelog file + below $logdir. + + -v|--verbose + show debug infos on console. + Remark: for permanent usage set CM_showdebug=1 in inc_config.sh - local ms_time=$( echo $totaltime | cut -f 2 -d "." | cut -c 1-3 ) + -w|--webroot <DIR> + Use http challenge with existing http server on port 80 + Challenge file will be written into given directory - echo "$sec_time.$ms_time sec" +The ACTIONs for SINGLE certificate handlings are: + + add <FQDN> [.. <FQDN-N>] + create new certificate + The first FQDN is a hostname to generate the certificate for. + Following multiple hostnames will be used as DNS aliases in the + same certificate. + It updates files in ${CM_diracme} + + ensure <FQDN> [.. <FQDN-N>] + It ensures that a certificate with given aliases exists and is up to date. + This param is for simple usage in automation tools like Ansible or Puppet. + It is required to add all aliases as parameters what is unhandy for + direct usage on cli. + + If the cert does not exist it will be created (see "add"). + If fqdn and aliases are the same like in the certificate it performs a renew. + If fqdn and aliases differ: + - the current certificate will be rejected + deleted (see "delete") + - a new certificate will be added () + + delete <FQDN> + delete all files of a given certificate + + renew <FQDN> + renew (an already added) certificate + and update files in ${CM_diracme} + + show <FQDN> + show place of certificate data and show basic certificate data + (issuer, subject, aliases, ending date) + + transfer <FQDN> + Transfer cert from acme.sh internal cache to our output dir again. + It is done during add or renew. With transfer command you can repeat it. + +ACTIONs for ALL certs + + list + list all certificates including creation and renew date + + list-old + list all certificates older 65 and older 90 days and exit. + Exitcodes: + 0 - all certs are up to date. + 1 - certificates to renew were found + 2 - outdatedt certificates were found + + renew-all + renew all certificates (fast mode - without --force) + and update files in ${CM_diracme} + It is useful for a cronjob. + +other ACTIONs + + selftest + check of health with current setup and requirements. + This command is helpful for initial setups. + +EOF } # ---------------------------------------------------------------------- @@ -449,230 +567,247 @@ function show_timer(){ # pulic function ADD certificate # function public_add(){ - local _params="" + local _params="" - _debuglog "start public_add" - _wait_for_free_slot - _requiresFqdn + _debuglog "start public_add" + _wait_for_free_slot + _requiresFqdn _certMustNotExist - _dnsCheck $CM_fqdn $* + _dnsCheck $CM_fqdn $* - for _mydomain in $CM_fqdn $* - do - _params+="-d $_mydomain --challenge-alias " + local iCounter + typeset -i iCounter=0 + for _mydomain in $CM_fqdn $* + do + iCounter+=1 + _params+="-d $_mydomain " - if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null - then - _params+="${CM_challenge_alias} " - else - _params+="no " - fi - done + if [ -n "$CM_webroot" ] + then + echo "WEBROOT found" + if [ $iCounter -eq 1 ] + then + _params+="-w $CM_webroot " + fi + else + if [ -n "${CM_challenge_alias}" ] + then + _params+=" --challenge-alias " + if ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null + then + _params+="${CM_challenge_alias} " + else + _params+="no " + fi + fi + fi + done - # 2023-05-08: Specifies the domain key length - _params+="--keylength 2048 " + # 2023-05-08: Specifies the domain key length + _params+="--keylength 2048 " - _wd "--- create output dir $dircerts" - mkdir -p "${CM_dircerts}" 2>/dev/null + _wd "--- create output dir $dircerts" + mkdir -p "${CM_dircerts}" 2>/dev/null - _wd "--- create certificate" - echo "$ACME --issue $_params $ACME_Params" - if ! $ACME --issue $_params $ACME_Params - then - echo "ERROR: adding cert failed. Trying to delete internal data ..." - public_delete $CM_fqdn - exit 1 - fi - # $ACME --issue -d $CM_fqdn $ACME_Params || exit 1 + _wd "--- create certificate" + echo "$ACME --issue $_params $ACME_Params" + if ! $ACME --issue $_params $ACME_Params + then + _we "Adding cert failed. Trying to delete internal data ..." + public_delete "$CM_fqdn" + exit 1 + fi + # $ACME --issue -d $CM_fqdn $ACME_Params || exit 1 - _certTransfer - _certMatching + _certTransfer + _certMatching - _update "added $CM_fqdn $*" - _debuglog "end public_add" + _update "added $CM_fqdn $*" + _debuglog "end public_add" } # CSR USAGE WAS REMOVED # function OLD__public_add(){ -# _wait_for_free_slot -# _requiresFqdn +# _wait_for_free_slot +# _requiresFqdn # _certMustNotExist -# for myhost in $( echo $CM_fqdn $*) -# do -# echo $myhost | grep "$CM_certmatch" >/dev/null -# if [ $? -ne 0 ]; then -# echo "ERROR: host $myhost does not match [$CM_certmatch]." -# exit 1 -# fi -# done -# _gencsr $CM_fqdn $* - -# _wd "--- create output dir $dircerts" -# mkdir -p "${CM_dircerts}" 2>/dev/null - -# _wd "--- csr data" -# $ACME --showcsr --csr $CM_filecsr || exit 1 - -# _wd "--- create certificate" -# echo $ACME --signcsr --csr $CM_filecsr $ACME_Params -# $ACME --signcsr --csr $CM_filecsr $ACME_Params -# if [ $? -ne 0 ]; then -# echo "ERROR: adding cert failed. Trying to delete internal data ..." -# public_delete $CM_fqdn -# exit 1 -# fi -# # $ACME --issue -d $CM_fqdn $ACME_Params || exit 1 - -# _certTransfer -# _certMatching - -# _update "added $CM_fqdn $*" +# for myhost in $( echo $CM_fqdn $*) +# do +# echo $myhost | grep "$CM_certmatch" >/dev/null +# if [ $? -ne 0 ]; then +# echo "ERROR: host $myhost does not match [$CM_certmatch]." +# exit 1 +# fi +# done +# _gencsr $CM_fqdn $* + +# _wd "--- create output dir $dircerts" +# mkdir -p "${CM_dircerts}" 2>/dev/null + +# _wd "--- csr data" +# $ACME --showcsr --csr $CM_filecsr || exit 1 + +# _wd "--- create certificate" +# echo $ACME --signcsr --csr $CM_filecsr $ACME_Params +# $ACME --signcsr --csr $CM_filecsr $ACME_Params +# if [ $? -ne 0 ]; then +# echo "ERROR: adding cert failed. Trying to delete internal data ..." +# public_delete $CM_fqdn +# exit 1 +# fi +# # $ACME --issue -d $CM_fqdn $ACME_Params || exit 1 + +# _certTransfer +# _certMatching + +# _update "added $CM_fqdn $*" # } # # pulic function ADD OR RENEW certificate # function public_ensure(){ - _wait_for_free_slot - _requiresFqdn - _certExists - if [ $? -eq 0 ]; then - _wd "--- cert $CM_fqdn was found ... compare aliases" - local _newAliases=$( _sortWords $CM_fqdn $* ) - local _certAliases=$( _getAliases ) - - _wd "from params: $_newAliases" - _wd "inside cert: $_certAliases" - if [ "$_newAliases" = "$_certAliases" ]; then - _wd "--- DNS aliases match ... renew it (ignore --force - it comes from acme.sh)" - public_renew $* - else - # _wd "--- DNS aliases do NOT match ... deleting cert and create a new one" - # public_delete $* - _wd "--- DNS aliases do NOT match ... creating a new one" - _fixKeyfile - public_add $* - fi - else - _wd "--- cert does mot exist ... add it" - public_add $* - fi + _wait_for_free_slot + _requiresFqdn + _certExists + if [ $? -eq 0 ]; then + _wd "--- cert $CM_fqdn was found ... compare aliases" + local _newAliases; _newAliases=$( _sortWords $CM_fqdn $* ) + local _certAliases; _certAliases=$( _getAliases ) + + _wd "from params: $_newAliases" + _wd "inside cert: $_certAliases" + if [ "$_newAliases" = "$_certAliases" ]; then + _wd "--- DNS aliases match ... renew it (ignore --force - it comes from acme.sh)" + public_renew $* + else + # _wd "--- DNS aliases do NOT match ... deleting cert and create a new one" + # public_delete $* + _wd "--- DNS aliases do NOT match ... creating a new one" + _fixKeyfile + public_add $* + fi + else + _wd "--- cert does mot exist ... add it" + public_add $* + fi } # # public function to delete a cert # function public_delete(){ - _debuglog "start public_delete" - _wait_for_free_slot - _requiresFqdn - _certMustExist - - # TODO: revoke it too?? - _wd "--- revoke cert" - $ACME --revoke -d ${CM_fqdn} $ACME_Params - _wd "--- delete ACME.SH data" - $ACME --remove -d ${CM_fqdn} $ACME_Params - _wd "--- delete local data" - - # CSR USAGE WAS REMOVED - rm -rf ${CM_dircerts} ${CM_filecnf} ${CM_filekey} ${CM_filecsr} ~/.acme.sh/${CM_fqdn} && echo OK - _update "deleted ${CM_fqdn}" - _debuglog "end public_delete" + _debuglog "start public_delete" + _wait_for_free_slot + _requiresFqdn + _certMustExist + + # TODO: revoke it too?? + _wd "--- revoke cert" + $ACME --revoke -d ${CM_fqdn} $ACME_Params + _wd "--- delete ACME.SH data" + $ACME --remove -d ${CM_fqdn} $ACME_Params + _wd "--- delete local data" + + # CSR USAGE WAS REMOVED + rm -rf ${CM_dircerts} ${CM_filecnf} ${CM_filekey} ${CM_filecsr} ~/.acme.sh/${CM_fqdn} && echo OK + _update "deleted ${CM_fqdn}" + _debuglog "end public_delete" } - # # public function; list certificates incl. creation date and renew date # function public_list(){ - _listCerts - + _listCerts } + # # public function; list old / outdated certificates # function public_list-old(){ + local _iRuntime=90 + local _iWarn=65 + local _rc; typeset -i _rc=0 - local _iRuntime=90 - local _iWarn=65 - typeset -i local _rc=0 - - cd "${CM_dircerts}" || exit 1 - - echo - echo "Cert dir is $( pwd )" - echo - echo "---- Certificates expiring soon - with age $_iWarn ... $_iRuntime days:" - if find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime | grep . >/dev/null - then - find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime -exec ls -ld {} \; | nl - _rc=1 - else - echo " NONE." - fi - echo - echo "---- Certificate list ... older $_iRuntime days:" - if find -type f -name "*.cert.cer" -mtime +$_iRuntime | grep . >/dev/null - then - find -type f -name "*.cert.cer" -mtime +$_iRuntime -exec ls -ld {} \; | nl - _rc=2 - else - echo " NONE." - fi - - echo - cd - >/dev/null - - _wd "Exiting with rc=$_rc" - exit $_rc -} + cd "${CM_dircerts}" || exit 1 + + echo + echo "Cert dir is $( pwd )" + echo + echo "---- Certificates expiring soon - with age $_iWarn ... $_iRuntime days:" + if find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime | grep . >/dev/null + then + find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime -exec ls -ld {} \; | nl + _rc=1 + else + echo " NONE." + fi + echo + echo "---- Certificate list ... older $_iRuntime days:" + if find -type f -name "*.cert.cer" -mtime +$_iRuntime | grep . >/dev/null + then + find -type f -name "*.cert.cer" -mtime +$_iRuntime -exec ls -ld {} \; | nl + _rc=2 + else + echo " NONE." + fi + echo + cd - >/dev/null + + _wd "Exiting with rc=$_rc" + exit $_rc +} # # public function - renew a certificate # param string fqdn of domain to renew function public_renew(){ - _debuglog "start public_renew" - _wait_for_free_slot - _requiresFqdn - _certMustExist - - _fixKeyfile - - $ACME --renew -d ${CM_fqdn} $ACME_Params - local _rc=$? - - case $_rc in - 0) - _certTransfer - _certMatching - _update "renewed ${CM_fqdn}" - ;; - 2) - _wd "renew was skipped ... we need to wait a while." - ;; - *) - _wd "Error ocured." - exit $_rc - esac - _debuglog "end public_renew" + _debuglog "start public_renew" + _wait_for_free_slot + _requiresFqdn + _certMustExist + + _fixKeyfile + + if [ $CM_force -ne 0 ]; then + _wd "renew was forced" + $ACME --force --renew -d "${CM_fqdn}" $ACME_Params + else + _wd "soft renew" + $ACME --renew -d "${CM_fqdn}" $ACME_Params + fi + local _rc=$? + + case $_rc in + 0) + _certTransfer + _certMatching + _update "renewed ${CM_fqdn}" + ;; + 2) + _wd "renew was skipped ... we need to wait a while." + ;; + *) + _wd "Error ocured." + exit $_rc + esac + _debuglog "end public_renew" } # # public function - renew all certificates (to be used in a cronjob) # no params function public_renew-all(){ - - _listCertdomains | while read mydomain - do - _wd "--- renew $mydomain" - _setenv ${mydomain} - public_renew - done - + _listCertdomains | while read mydomain + do + _wd "--- renew $mydomain" + _setenv ${mydomain} + public_renew + done } @@ -683,105 +818,103 @@ function public_renew-all(){ # param string command to verify # param string output label function _selftestItem(){ - local _check=$1 - local _label=$2 - local _status="OK:" - - eval "$_check" - if [ $? -ne 0 ]; then - _status="ERROR: the check failed for the test of -" - _iErrors=$_iErrors+1 - fi + local _check=$1 + local _label=$2 + local _status="OK:" + + eval "$_check" + if [ $? -ne 0 ]; then + _status="ERROR: the check failed for the test of -" + _iErrors=$_iErrors+1 + fi - echo "$_status $_label" + echo "$_status $_label" } # # list existing certs # no params function public_selftest(){ + typeset -i _iErrors=0 - typeset -i _iErrors=0 - - echo - echo --- dependencies - _selftestItem "which openssl" "openssl was found" - _selftestItem "which curl" "curl was found" - echo - - echo --- acme.sh client - _selftestItem "ls -ld ${ACME}" "${ACME} exits" - _selftestItem "test -x ${ACME}" "${ACME} is executable" - echo - - echo --- acme.sh installation \(may fail in future releases of acme.sh\) - _selftestItem "ls -ld ~/.acme.sh" "internal acme data were found = [acme.sh --install] was done" - _selftestItem "test -w ~/.acme.sh/" "it is writable" - echo - - # CSR USAGE WAS REMOVED - # echo --- csr template - # _selftestItem "ls -ld ${csrfile}" "csr base template exists" - # _selftestItem "test -r ${csrfile}" "it is readable" - # echo - # - # echo --- output directory for csr and key - # _selftestItem "ls -ld ${CM_dircsr}" "data dir for csr exists" - # _selftestItem "test -w ${CM_dircsr}" "it is writable" - # echo - - echo --- output dir for centralized place of certificates - _selftestItem "ls -ld ${CM_diracme}" "central output dir for certificate data exists" - _selftestItem "test -w ${CM_diracme}" "it is writable" - echo - - - echo --- logs - _selftestItem "ls -ld ./log/" "Logdir exists" - _selftestItem "test -w" "Logdir is writable" - test -f $logfile && _selftestItem "test -w $logfile" "Logfile $logfile is writable" - test -f $touchfile && _selftestItem "test -w $touchfile" "Logfile $touchfile is writable" - echo - - echo --- Errors: $_iErrors - test $_iErrors -eq 0 && echo "OK, this looks fine." - echo - exit $_iErrors + echo + echo --- dependencies + _selftestItem "which openssl" "openssl was found" + _selftestItem "which curl" "curl was found" + echo + + echo --- acme.sh client + _selftestItem "ls -ld ${ACME}" "${ACME} exits" + _selftestItem "test -x ${ACME}" "${ACME} is executable" + echo + + echo --- acme.sh installation \(may fail in future releases of acme.sh\) + _selftestItem "ls -ld ~/.acme.sh" "internal acme data were found = [acme.sh --install] was done" + _selftestItem "test -w ~/.acme.sh/" "it is writable" + echo + + # CSR USAGE WAS REMOVED + # echo --- csr template + # _selftestItem "ls -ld ${csrfile}" "csr base template exists" + # _selftestItem "test -r ${csrfile}" "it is readable" + # echo +# + # echo --- output directory for csr and key + # _selftestItem "ls -ld ${CM_dircsr}" "data dir for csr exists" + # _selftestItem "test -w ${CM_dircsr}" "it is writable" + # echo + + echo --- output dir for centralized place of certificates + _selftestItem "ls -ld ${CM_diracme}" "central output dir for certificate data exists" + _selftestItem "test -w ${CM_diracme}" "it is writable" + echo + + echo --- logs + _selftestItem "ls -ld ./log/" "Logdir exists" + _selftestItem "test -w" "Logdir is writable" + test -f $logfile && _selftestItem "test -w $logfile" "Logfile $logfile is writable" + test -f $touchfile && _selftestItem "test -w $touchfile" "Logfile $touchfile is writable" + echo + + echo --- Errors: $_iErrors + test $_iErrors -eq 0 && echo "OK, this looks fine." + echo + exit $_iErrors } # # list existing certs # no params function public_show(){ - _requiresFqdn - _certMustExist - - # CSR USAGE WAS REMOVED - # ls -l ${CM_filecsr} ${CM_dircerts}/* - ls -l ${CM_dircerts}/* - _certMatching - - # CSR USAGE WAS REMOVED - # echo $line - # echo CSR $CM_filecsr - # openssl req -noout -text -in $CM_filecsr | grep -E "(Subject:|DNS:)" | sed "s#^\ *##g" - - for myfile in ${CM_outfile_cert} ${CM_outfile_haproxy} - do - echo $line - echo Cert ${myfile} - # openssl x509 -noout -text -in ${CM_outfile_cert} - openssl x509 -noout -text -in ${myfile} | grep -E "(Issuer:|Subject:|Not\ |DNS:)"| sed "s#^\ *##g" - done + _requiresFqdn + _certMustExist + + # CSR USAGE WAS REMOVED + # ls -l ${CM_filecsr} ${CM_dircerts}/* + ls -l ${CM_dircerts}/* + _certMatching + + # CSR USAGE WAS REMOVED + # echo $line + # echo CSR $CM_filecsr + # openssl req -noout -text -in $CM_filecsr | grep -E "(Subject:|DNS:)" | sed "s#^\ *##g" + + for myfile in ${CM_outfile_cert} ${CM_outfile_haproxy} + do + echo $line + echo Cert ${myfile} + # openssl x509 -noout -text -in ${CM_outfile_cert} + openssl x509 -noout -text -in ${myfile} | grep -E "(Issuer:|Subject:|Not\ |DNS:)"| sed "s#^\ *##g" +done } # Transfer cert from acme.sh internal cache to our output dir again function public_transfer(){ - _wait_for_free_slot - _requiresFqdn - _certExists + _wait_for_free_slot + _requiresFqdn + _certExists - _certTransfer + _certTransfer } # ---------------------------------------------------------------------- @@ -792,37 +925,78 @@ function public_transfer(){ cd $( dirname $0 ) -if [ "$1" = "--trace" ]; then - tracelog="$logdir/trace__$2__$3__`date +%Y-%m-%d__%H-%M-%S`.log" - exec > >(tee -a ${tracelog} ) - exec 2> >(tee -a ${tracelog} >&2) - echo "TRACELOG was triggered." - echo "TIME : $(date)" - echo "COMMAND: $0 $*" - echo "LOG : $tracelog" - - # set -vx - shift 1 -fi - cat <<ENDOFHEADER $line - - - - ---===>>> CERT MANAGER <<<===--- - - - + - - - ---===>>> CERT MANAGER - v$_version <<<===--- - - - $line ENDOFHEADER + +while [[ "$#" -gt 0 ]]; do case $1 in + + -a|--alias) + cd .. + CM_webroot="$( pwd )/alias-dir" + cd - + echo "INFO: directory for challenges is [$CM_webroot]" + shift + ;; + + -f|--force) + echo "INFO: enable --force (for renewal)" + CM_force=1 + exit 0 + ;; + + -h|--help) + show_help + exit 0 + ;; + + -t|--trace) + tracelog="$logdir/trace__$2__$3__$(date +%Y-%m-%d__%H-%M-%S).log" + exec > >(tee -a "${tracelog}" ) + exec 2> >(tee -a "${tracelog}" >&2) + echo "TRACELOG was triggered." + echo "TIME : $(date)" + echo "COMMAND: $0 $*" + echo "LOG : $tracelog" + # set -vx + shift 1 + ;; + + -v|--verbose) + CM_showdebug=1 + shift 1 + ;; + + -w|--webroot) + CM_webroot="$2" + echo "INFO: directory for challenges is [$CM_webroot]" + # ACME="sudo $ACME" + # shift + shift + ;; + + *) if grep "^-" <<< "$1" >/dev/null ; then + echo; _we "Unknown parameter: $1"; show_help; exit 2 + fi + break; + ;; +esac; done + which openssl >/dev/null || exit 1 . ./inc_config.sh if [ $? -ne 0 ]; then - echo "ERROR: loading the config failed." - echo "Copy the inc_config.sh.dist to inc_config.sh and make your settings in it." - echo - exit 1 + _we "Loading the config failed." + echo "Copy the inc_config.sh.dist to inc_config.sh and make your settings in it." + echo + exit 1 fi _testUser @@ -831,103 +1005,34 @@ _testStaging test -z "${CM_diracme}" && CM_diracme=./certs test -z "${CM_dircsr}" && CM_dircsr=./csr -grep "function\ public_$1" $( basename $0 ) >/dev/null +grep "function public_$1" "$( basename "$0" )" >/dev/null if [ $# -gt 0 -a $? -eq 0 ]; then - # _wd $* - action=$1 - CM_fqdn=$2 - shift 2 + # _wd $* + action=$1 + CM_fqdn=$2 + shift 2 - _testFqdncount $CM_fqdn $* + _testFqdncount "$CM_fqdn" $* - test -z "${ACME}" && ACME=$( which acme.sh ) - if [ ! -x "${ACME}" ]; then - echo "ERROR: acme.sh not found. You need to install acme.sh client and configure it in inc_config.sh." - exit 1 - fi + test -z "${ACME}" && ACME=$( which acme.sh ) + if [ ! -x "${ACME}" ]; then + _we "acme.sh not found. You need to install acme.sh client and configure it in inc_config.sh." + exit 1 + fi - _setenv $CM_fqdn + _setenv "$CM_fqdn" - _wd "A C T I O N -->> $action <<--" - _debuglog ">>> START public_$action $CM_fqdn $*" - eval "public_$action $*" - _debuglog ">>> DONE public_$action $CM_fqdn $*" + _wd "A C T I O N -->> $action <<--" + _debuglog ">>> START public_$action $CM_fqdn $*" + eval "public_$action $*" + _debuglog ">>> DONE public_$action $CM_fqdn $*" else - self=$( basename $0 ) - cat <<EOF - -HELP - -The basic syntax is -$self [--trace] ACTION [FQDN] [ALIAS_1 [.. ALIAS_N]] - -The ACTIONs for SINGLE certificate handlings are: - - add FQDN [.. FQDN-N] - create new certificate - The first FQDN is a hostname to generate the certificate for. - Following multiple hostnames will be used as DNS aliases in the - same certificate. - It updates files in ${CM_diracme} - - ensure FQDN [.. FQDN-N] - It ensures that a certificate with given aliases exists and is up to date. - This param is for simple usage in automation tools like Ansible or Puppet. - It is required to add all aliases as parameters what is unhandy for - direct usage on cli. - - If the cert does not exist it will be created (see "add"). - If fqdn and aliases are the same like in the certificate it performs a renew. - If fqdn and aliases differ: - - the current certificate will be rejected + deleted (see "delete") - - a new certificate will be added () - - delete FQDN - delete all files of a given certificate - - renew FQDN - renew (an already added) certificate - and update files in ${CM_diracme} - - show FQDN - show place of certificate data and show basic certificate data - (issuer, subject, aliases, ending date) - - transfer FQDN - Transfer cert from acme.sh internal cache to our output dir again. - It is done during add or renew. With transfer command you can repeat it. - -ACTIONs for ALL certs - - list - list all certificates including creation and renew date - - list-old - list all certificates older 65 and older 90 days and exit. - Exitcodes: - 0 - all certs are up to date. - 1 - certificates to renew were found - 2 - outdatedt certificates were found - - renew-all - renew all certificates (fast mode - without --force) - and update files in ${CM_diracme} - It is useful for a cronjob. - -other ACTIONs - - selftest - check of health with current setup and requirements. - This command is helpful for initial setups. - -OPTIONS - --trace (it must be the 1st parameter) - the output additionally will be written into a tracelog file - below $logdir. - -EOF + _we "No valid action was specified" + show_help fi echo _testStaging + +# ---------------------------------------------------------------------- diff --git a/docs/30_Usage.md b/docs/30_Usage.md index e998df73f32eb3eb9d25c9a85a96c14588dc955d..f172c21e0309c5a7c192cf62abe7865442695c6d 100644 --- a/docs/30_Usage.md +++ b/docs/30_Usage.md @@ -6,94 +6,113 @@ Verify a new setup (or changes in the config) with `./cm.sh selftest`. ## Show help -Without any parameter it shows a help. +Without -h or --help it shows a help. ```text -./cm.sh +./cm.sh -h _______________________________________________________________________________ - - - - ---===>>> CERT MANAGER <<<===--- - - - + - - - ---===>>> CERT MANAGER - v2025-01-08 <<<===--- - - - _______________________________________________________________________________ -DEBUG: Using Let's Encrypt STAGE environment ... -DEBUG: You can test and mess around. Do not use certs in production. - HELP -The basic syntax is -cm.sh [--trace] ACTION [FQDN] [ALIAS_1 [.. ALIAS_N]] +Wrapper script for acme.sh to handle certificates. +For automation you should use the "ensure" action that detects if a +certificate must be created, renewed or re-created. -The ACTIONs for SINGLE certificate handlings are: +📄 Source: <https://git-repo.iml.unibe.ch/iml-open-source/iml-certman> +📜 License: GNU GPL 3.0 +📗 Docs: <https://os-docs.iml.unibe.ch/iml-certman/> - add FQDN [.. FQDN-N] - create new certificate - The first FQDN is a hostname to generate the certificate for. - Following multiple hostnames will be used as DNS aliases in the - same certificate. - It updates files in ./certs +SYNTAX: - ensure FQDN [.. FQDN-N] - It ensures that a certificate with given aliases exists and is up to date. - This param is for simple usage in automation tools like Ansible or Puppet. - It is required to add all aliases as parameters what is unhandy for - direct usage on cli. + dev_cm.sh [OPTIONS] ACTION <FQDN> [<ALIASES>] - If the cert does not exist it will be created (see "add"). - If fqdn and aliases are the same like in the certificate it performs a renew. - If fqdn and aliases differ: - - the current certificate will be rejected + deleted (see "delete") - - a new certificate will be added () +OPTIONS: - delete FQDN - delete all files of a given certificate + -a|--alias + Use http challenge with existing http server on port 80 + Challenge file will be written into ../alias-dir/ - renew FQDN - renew (an already added) certificate - and update files in ./certs + -f|--force + Force renew of certificate even if it is not due yet. + Use it carefully - remember the execution limits on Let's Encrypt. - show FQDN - show place of certificate data and show basic certificate data - (issuer, subject, aliases, ending date) + -t|--trace + the output additionally will be written into a tracelog file + below ./log. - transfer FQDN - Transfer cert from acme.sh internal cache to our output dir again. - It is done during add or renew. With transfer command you can repeat it. + -v|--verbose + show debug infos on console. + Remark: for permanent usage set CM_showdebug=1 in inc_config.sh -ACTIONs for ALL certs + -w|--webroot <DIR> + Use http challenge with existing http server on port 80 + Challenge file will be written into given directory - list - list all certificates including creation and renew date +The ACTIONs for SINGLE certificate handlings are: - list-old - list all certificates older 65 and older 90 days and exit. - Exitcodes: - 0 - all certs are up to date. - 1 - certificates to renew were found - 2 - outdatedt certificates were found + add <FQDN> [.. <FQDN-N>] + create new certificate + The first FQDN is a hostname to generate the certificate for. + Following multiple hostnames will be used as DNS aliases in the + same certificate. + It updates files in - renew-all - renew all certificates (fast mode - without --force) - and update files in ./certs - It is useful for a cronjob. + ensure <FQDN> [.. <FQDN-N>] + It ensures that a certificate with given aliases exists and is up to date. + This param is for simple usage in automation tools like Ansible or Puppet. + It is required to add all aliases as parameters what is unhandy for + direct usage on cli. -other ACTIONs + If the cert does not exist it will be created (see "add"). + If fqdn and aliases are the same like in the certificate it performs a renew. + If fqdn and aliases differ: + - the current certificate will be rejected + deleted (see "delete") + - a new certificate will be added () + + delete <FQDN> + delete all files of a given certificate + + renew <FQDN> + renew (an already added) certificate + and update files in - selftest - check of health with current setup and requirements. - This command is helpful for initial setups. + show <FQDN> + show place of certificate data and show basic certificate data + (issuer, subject, aliases, ending date) -OPTIONS - --trace (it must be the 1st parameter) - the output additionally will be written into a tracelog file - below ./log. + transfer <FQDN> + Transfer cert from acme.sh internal cache to our output dir again. + It is done during add or renew. With transfer command you can repeat it. +ACTIONs for ALL certs + + list + list all certificates including creation and renew date + + list-old + list all certificates older 65 and older 90 days and exit. + Exitcodes: + 0 - all certs are up to date. + 1 - certificates to renew were found + 2 - outdatedt certificates were found + + renew-all + renew all certificates (fast mode - without --force) + and update files in + It is useful for a cronjob. + +other ACTIONs -DEBUG: Using Let's Encrypt STAGE environment ... -DEBUG: You can test and mess around. Do not use certs in production. + selftest + check of health with current setup and requirements. + This command is helpful for initial setups. ``` diff --git a/docs/style.css b/docs/style.css index 18463c799d9cd101d8f9b27a282128cde4494d99..90a0bc938f147608a1d4fd11952e4825f076862c 100644 --- a/docs/style.css +++ b/docs/style.css @@ -1,52 +1,59 @@ /* override css elements of daux.io blue theme - version 2022-11-30 + version 2024-10-31 */ :root { /* Axels Overrides */ - --color-text: #222; - --link-color: #822; - --brand-color: var(--color-secondary); + --color-text: #234; + --link-color: #228; + --brand-color: var(--color-text); --brand-background: var(--body-background); + --code-tag-background-color: #f0f3f3; + --code-tag-border-color: #dee; + --code-tag-box-shadow: none; --hr-color: none; + --pager-background-color: #f8fafa; + --pager-border-color: none; --search-field-background: none; --search-field-border-color: none; --sidebar-background: var(--body-background); --sidebar-border-color: none; - --sidebar-link-active-background: #e8f4f6; - --sidebar-link-active-background: #eee; + --sidebar-link-active-background: #f0f4f6; + --toc--inner-border-color: none; /* Axels custom values */ - --axel_bg-toc: var(--body-background); + --axel_bg-toc: #f8fafa; --axel_bg-toc-head: #f8f8f8; --axel_brand-background: none; --axel_brand-pre-background: rgb(255, 0, 51); - ; --axel_brand-pre-background-hover: rgb(255, 0, 51); - ; --axel_h1_header: none; - --axel_h1: #345; + --axel_h1: #111; --axel_h1-bg: none; --axel_h1-bottom: 3px solid none; - --axel_h2: #156; - --axel_h2-bg: #f8fafb; - --axel_h2-bottom: 2px solid #467; + --axel_h2: #222; + --axel_h2-bg: none; + --axel_h2-bottom: 0px solid #467; --axel_h2-hero-bottom: 2px solid #912; - --axel_h3: #278; - --axel_h3-bottom: 1px solid #ddd; - --axel_hero_bg: #f8f8f8; + --axel_h3: #333; + --axel_h3-bottom: 0px solid #ddd; + --axel_h4: #666; + --axel_h5: #888; + --axel_hero_bg: #faf8f6; + --axel_img-border: 2px dashed #ccc; --axel_nav-bg: #fcfcfc; --axel_nav-buttomborder: #ddd; - --axel_pre-background: #f8f8f8; - --axel-th-background: #d0e0e8; + --axel_pre-background: #faf8f6; + --axel-th-background: #e0e4e8; --axel-article-nav-border-top: 0px dotted #ddd; } .dark { /* Axels Overrides */ --color-text: #c0c0c0; - --link-color: #b44; + --link-color: #88e; --brand-color: var(--color-text); --brand-background: var(--body-background); + --body-background: #101418; --hr-color: none; --code-tag-background-color_: #bcc; --search-field-background: none; @@ -54,6 +61,7 @@ --sidebar-background: var(--body-background); --sidebar-border-color: none; --sidebar-link-active-background: #333; + --sidebar-link-color: var(--link-color); /* Axels custom values */ --axel_bg-toc: var(--body-background); --axel_bg-toc-head: #333; @@ -63,16 +71,19 @@ --axel_brand-pre-background-hover: rgb(255, 0, 51); ; --axel_h1_header: none; - --axel_h1: #777; + --axel_h1: #578; --axel_h1-bg: none; --axel_h1-bottom: none; --axel_h2: #467; - --axel_h2-bg: #202020; - --axel_h2-bottom: 2px solid #256; + --axel_h2-bg: none; + --axel_h2-bottom: 0px solid #256; --axel_h2-hero-bottom: 2px solid #712; --axel_h3: #589; - --axel_h3-bottom: 1px solid #333; + --axel_h3-bottom: 0px solid #333; + --axel_h4: #478; + --axel_h5: #278; --axel_hero_bg: #242424; + --axel_img-border: 2px dashed #555; --axel_nav-bg: #242424; --axel_nav-buttomborder: #555; --axel_pre-background: #bcc; @@ -117,7 +128,25 @@ a.Brand { /* ---------- page content ---------- */ .s-content { - padding-top: 1em; + padding-top: 6em; +} + +/** +h1::before{color: #aaa;content: 'h1: ';} +h2::before{color: #aaa;content: 'h2: ';} +h3::before{color: #aaa;content: 'h3: ';} +h4::before{color: #aaa;content: 'h4: ';} +h5::before{color: #aaa;content: 'h5: ';} +h6::before{color: #aaa;content: 'h6: ';} +*/ +h2::before{color: #888;content: ': : ';} +h3::before{color: #ccc;content: '> ';} +h4::before{color: #ccc;content: '_ ';} + +.s-content h1::before{ + color: #f00; + content: 'FEHLER: Keine Überschrift 1 in einer Markdown-Datei für Daux verwenden! Mit H2 beginnen!'; + content: '!! h1 !! '; } .s-content h1 { @@ -133,20 +162,32 @@ a.Brand { .s-content h2 { background: var(--axel_h2-bg); color: var(--axel_h2); - font-size: 180%; + font-size: 190%; font-weight: bold; margin-top: 4em; border-bottom: var(--axel_h2-bottom); } -h1:first-of-type { +.Page__header > h1:first-of-type { margin-top: 0em; + margin-left: -1em; + padding-left: 1em; + position: fixed; + min-width: 100%; + background: var(--body-background); + box-shadow: 0 2em 1em var(--body-background); } h2:first-of-type { margin-top: 0em; } +img{ + border: var(--axel_img-border); + border-radius: 1.5em; + padding: 0.7em; +} + .s-content h3 { background: var(--axel_h3-bg); color: var(--axel_h3); @@ -156,16 +197,37 @@ h2:first-of-type { border-bottom: var(--axel_h3-bottom); } -.s-content h4 { - margin: 0; - font-size: 100%; + +.s-content > h4 { + color: var(--axel_h4); + font-size: 140%; + font-weight: bold; + margin: 2em 0; +} + +.s-content > h5 { + color: var(--axel_h5); + font-size: 135%; + font-weight: bold; + margin: 2em 0; +} + +.s-content .TableOfContentsContainer h4 { + margin: 1em 0; + font-size: 110%; text-align: center; - background-color: rgba(0, 0, 0, 0.05); + background-color: rgba(0, 0, 0, 0.1); padding: 0.3em; + font-weight: bold; + font-family: Arial; +} +ul.TableOfContents a{ + color: var(--color-text); } - .s-content pre { background: var(--axel_pre-background); + border-radius: 0.5em; + padding: 1rem; } /* FIX smaller fnt size in tables */ @@ -217,13 +279,6 @@ div.hero h2 { } /* ---------- TOC ---------- */ -@media(min-width:1700px) { - .TableOfContentsContainer { - position: fixed; - right: 2em; - top: 1em; - } -} .TableOfContentsContainer { background-color: var(--axel_bg-toc); @@ -233,14 +288,18 @@ div.hero h2 { .s-content .TableOfContentsContainer h4 { background-color: var(--axel_bg-toc-head); border-top-left-radius: 1em; + border-bottom: 2px solid var(--axel_bg-toc-bottom-border); font-size: 1.1em; margin: 0; - padding: 0; + padding: 0.3em; + display: none; } .TableOfContentsContainer__content { - border-width: 1px; + border-width: 0px; font-size: 0.5em; + height: inherit; + overflow: auto; } ul.TableOfContents ul { @@ -248,20 +307,30 @@ ul.TableOfContents ul { padding-left: 1em; } +.TableOfContents a:hover{ + text-decoration: underline; +} + +@media(min-width:1700px) { + .TableOfContentsContainer { + background: none; + position: fixed; + right: 2em; + top: 4em; + height: 90%; + } +} + /* ----- Icons on links --- */ .EditOn a::before{ content: 'âœï¸ '; } -.Links a[href^="https://github.com/"]::before { - content: '🌠'; -} - -.Links a[href^="https://git-repo.iml.unibe.ch/"]::before { +.Links a::before { content: '🌠'; } .Links a[href^="https://os-docs.iml.unibe.ch"]::before { content: '📗 '; -} \ No newline at end of file +}