From a808c674e4cf7f5e572b711f73f07102444152db Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch>
Date: Wed, 8 Jan 2025 13:52:01 +0100
Subject: [PATCH] optimize _wait_for_free_slot

---
 cm.sh      |   9 +-
 cm.sh__bak | 963 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 970 insertions(+), 2 deletions(-)
 create mode 100755 cm.sh__bak

diff --git a/cm.sh b/cm.sh
index 074b6a7..d1d4597 100755
--- a/cm.sh
+++ b/cm.sh
@@ -354,6 +354,10 @@ function _update(){
 # "neverending" loop that waits until the current process is
 # the one with lowest PID
 function _wait_for_free_slot(){
+    if [ "$FREESLOT" = "1" ]; then
+        _debuglog "skip _wait_for_free_slot"
+        return 0
+    fi
     local _bWait=true
     _debuglog "start in _wait_for_free_slot"
     typeset -i local _iFirstPID=0
@@ -363,10 +367,10 @@ function _wait_for_free_slot(){
 	_wd "--- Need to wait until own process PID $$ is on top ... "
     while [ $_bWait = true ];
     do
-		_sProcesses=$( ps -ef | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n )
+		_sProcesses=$( ps aux | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n )
 		_iPos=$( echo "$_sProcesses" | grep -n " $$ " | head -1 | cut -f 1 -d ':' )
 
-		_wd "instances: $_iProcesses"
+        _wd "process $$ is on position $_iPos"
 		test ${CM_showdebug} -ne 0 && echo "$_sProcesses"
 
         # if [ $_iFirstPID -eq $$ ]; then
@@ -381,6 +385,7 @@ function _wait_for_free_slot(){
             sleep $((3 + RANDOM % 3));
         fi
     done
+    FREESLOT=1
     _debuglog "end _wait_for_free_slot"
 }
 
diff --git a/cm.sh__bak b/cm.sh__bak
new file mode 100755
index 0000000..f44368a
--- /dev/null
+++ b/cm.sh__bak
@@ -0,0 +1,963 @@
+#!/usr/bin/env bash
+# ======================================================================
+#
+# WRAPPER FOR ACME.SH 
+# Let's Encrypt client
+#
+# requires
+# - bash
+# - openssl
+# - curl
+# - dig (opional)
+# - acme.sh client
+#
+# ----------------------------------------------------------------------
+# 2021-02-02  <axel.hahn@iml.unibe.ch>  first lines
+# 2021-02-10  <axel.hahn@iml.unibe.ch>  compare hashes, logging
+# 2021-02-12  <axel.hahn@iml.unibe.ch>  added self test
+# 2021-02-17  <axel.hahn@iml.unibe.ch>  ensure checks list of aliases; new: optional host filter before adding a cert
+# 2021-03-12  <axel.hahn@iml.unibe.ch>  create file for haproxy
+# 2021-03-22  <axel.hahn@iml.unibe.ch>  for haproxy: use chained cert instead of server cert
+# 2021-04-12  <axel.hahn@iml.unibe.ch>  reject multiple usage of fqdn in cli params
+# 2021-04-12  <axel.hahn@iml.unibe.ch>  optional: force excecution with a given user only
+# 2021-06-24  <axel.hahn@iml.unibe.ch>  added transfer command; delete files if acme.sh --install-cert ... failes
+# 2021-07-14  <axel.hahn@iml.unibe.ch>  added _wait_for_free_slot in cert actions to execute multiple processes sequentially
+# 2021-09-27  <axel.hahn@iml.unibe.ch>  softer behaviour: do not revoke changed certs (add does not stop; ensure does not delete)
+# 2021-12-23  <axel.hahn@iml.unibe.ch>  added param --trace as 1st param to generate a trace log 
+# 2022-01-10  <axel.hahn@iml.unibe.ch>  _wait_for_free_slot: exclude ssh calls 
+# 2022-03-30  <axel.hahn@iml.unibe.ch>  remove usage of csr and generation of key file
+# 2022-03-31  <axel.hahn@iml.unibe.ch>  dns authentication with alias domain
+# 2022-04-04  <axel.hahn@iml.unibe.ch>  Bugfix: copy key from csr folder to ~/.acme/
+# 2022-04-04  <axel.hahn@iml.unibe.ch>  added param "list-old"
+# 2022-04-07  <axel.hahn@iml.unibe.ch>  fix missing key in public_ensure before calling public_add too.
+# 2022-04-20  <axel.hahn@iml.unibe.ch>  fix multiple domains using domain alias
+# 2022-04-21  <axel.hahn@iml.unibe.ch>  mix multiple domains using domain alias or not
+# 2022-05-19  <axel.hahn@iml.unibe.ch>  add timer and debug.log
+# 2022-05-20  <axel.hahn@iml.unibe.ch>  update _wait_4_free_slot and data in debug.log
+# 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
+# 2024-07-09  <axel.hahn@unibe.ch>      remove grep: warning: stray \ before white space; add --force for renewal
+# ======================================================================
+
+
+# ----------------------------------------------------------------------
+#
+# CONFIG
+#
+# ----------------------------------------------------------------------
+
+_version="2024-07-09"
+logdir="./log"
+touchfile="$logdir/lastchange.txt"
+logfile="$logdir/certmanager.log"
+debuglogfile="$logdir/debug.log"
+
+# CSR USAGE WAS REMOVED
+# csrfile="./templates/csr.txt"
+
+line="_______________________________________________________________________________"
+
+# flag: show debug infos on console (STDOUT)
+CM_showdebug=0
+
+# flag: write a log for created/ renewd/ deleted certs
+CM_writelog=1
+
+# flag: write a log for executed functions with timer and process count
+CM_writedebuglog=0
+
+CM_timer_start=$( date +%s.%N )
+
+# ----------------------------------------------------------------------
+#
+# INTERNAL FUNCTIONS
+#
+# ----------------------------------------------------------------------
+
+# 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
+}
+
+# internal function; list certificates incl. creation date and renew date
+function _listCerts(){
+	$ACME --list	
+}
+
+# internal function; get a list of fqdn of all existing certs
+function _listCertdomains(){
+	_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
+}
+
+# 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
+}
+
+# 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
+}
+
+# internal function: transfer generated/ updated cert data to a
+# known directory (based on CM_diracme - see inc_config.sh)
+# 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 "--- 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}"
+    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}
+
+	_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}"
+    then
+        echo "ERROR missing a file (or no access?)"
+        rm -f "${CM_dircerts}/*"
+        exit 2
+    fi
+}
+
+# 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
+}
+
+# internal function: dig for given fqdn.
+# Function stops if fqdn was not found in DNS.
+# If dig is not found the function skips the DNS check.
+# This function is used in _dnsCheck
+# param  string  fqdn to check
+# param  string  type of dns entry; one of a|cname
+# param  string  optional filter on output of dig (regex)
+function _checkDig(){
+    local myfqdn=$1
+	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."
+            exit 2
+        fi
+        _wd "OK: [$myfqdn] exists in DNS."
+    else
+        _wd "SKIP: dig was not found"
+    fi
+    echo
+
+}
+
+# internal function: check DNS entries
+# - the hostname to be added in the certificate must exist
+# - if a hostname does not match and CM_challenge_alias was set:
+#   - _acme-challenge.FQDN must be a cname to _acme-challenge.${CM_challenge_alias}
+# 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
+
+		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
+# internal function; generate a csr file before creating a new certifcate
+# this function is used in public_add
+# function _UNUSED_gencsr(){
+
+# 	local altdns=
+
+# 	for myalt in $*
+# 	do
+# 		altdns="${altdns}DNS:$myalt,"
+# 	done
+# 	altdns=$( echo $altdns | sed "s#,\$##" )
+#     _wd "--- $CM_fqdn"
+# 	_wd "DNS alternative names: $altdns"
+
+# 	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
+
+# 	# 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
+# }
+
+# 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"
+	)
+}
+
+# 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
+}
+
+# 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
+}
+# 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
+
+}
+
+# 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
+}
+
+
+# set update message in a file
+# param  string(s)  message
+function _update(){
+	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"
+    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
+		_sProcesses=$( ps -ef | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n )
+		_iPos=$( echo "$_sProcesses" | grep -n " $$ " | head -1 | cut -f 1 -d ':' )
+
+		_wd "instances: $_iProcesses"
+		test ${CM_showdebug} -ne 0 && echo "$_sProcesses"
+
+        # if [ $_iFirstPID -eq $$ ]; then
+        if [ $_iPos -eq 1 ]; then
+            _bWait=false
+			_debuglog "GO from _wait_for_free_slot"
+            _wd "OK. Go!"
+        else
+			_iProcesses=$( echo "$_sProcesses" | wc -l )
+			_iFirstPID=$( echo "$_sProcesses" | head -1 | awk '{ print $2}' )
+			_debuglog "zzz ... waiting in _wait_for_free_slot ... $_iFirstPID is first ... my pos is $_iPos of $_iProcesses"
+            sleep $((3 + RANDOM % 3));
+        fi
+    done
+	_debuglog "end _wait_for_free_slot"
+}
+
+# write debug output if CM_showdebug is set to 1
+function _wd(){
+	test ${CM_showdebug} -ne 0 && echo "DEBUG: $*"
+}
+
+# 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
+
+}
+# internal function; helper: sort words in alphabetic order
+function _sortWords(){
+	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
+}
+
+# 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 sec_time=$( echo $totaltime | cut -f 1 -d "." )
+         test -z "$sec_time" && sec_time=0
+
+         local ms_time=$( echo $totaltime | cut -f 2 -d "." | cut -c 1-3 )
+
+         echo "$sec_time.$ms_time sec"
+}
+
+# ----------------------------------------------------------------------
+#
+# PUBLIC FUNCTIONS
+#
+# ----------------------------------------------------------------------
+
+#
+# pulic function ADD certificate
+# 
+function public_add(){
+	local _params=""
+
+	_debuglog "start public_add"
+	_wait_for_free_slot
+	_requiresFqdn
+    _certMustNotExist
+
+	_dnsCheck $CM_fqdn $*
+
+	for _mydomain in $CM_fqdn $*
+	do
+		_params+="-d $_mydomain --challenge-alias "
+
+		if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null
+		then
+			_params+="${CM_challenge_alias} "
+		else
+			_params+="no "
+		fi
+	done
+
+	# 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 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
+
+	_certTransfer
+	_certMatching
+
+	_update "added $CM_fqdn $*"
+	_debuglog "end public_add"
+}
+
+# CSR USAGE WAS REMOVED
+# function OLD__public_add(){
+# 	_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 $*"
+# }
+
+#
+# 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
+}
+
+#
+# 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"
+}
+
+
+#
+# public function; list certificates incl. creation date and renew date
+# 
+function public_list(){
+	_listCerts
+
+}
+#
+# public function; list old / outdated certificates
+# 
+function public_list-old(){
+
+	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
+}
+
+
+#
+# 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 $CM_force --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"
+}
+
+#
+# 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
+
+}
+
+
+# internal function; helper for selftest to handle a single selftest
+# if a given command is successful it shows "OK" or "ERROR" followed
+# by the label inparam 2.
+# The value _iErrors will be incremented by 1 if an error occured.
+# 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
+
+	echo "$_status $_label"
+}
+
+#
+# list existing certs
+# no params
+function public_selftest(){
+
+	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
+}
+
+#
+# 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
+}
+
+# Transfer cert from acme.sh internal cache to our output dir again
+function public_transfer(){
+	_wait_for_free_slot
+	_requiresFqdn
+	_certExists
+
+	_certTransfer
+}
+
+# ----------------------------------------------------------------------
+#
+# main
+#
+# ----------------------------------------------------------------------
+
+cd $( dirname $0 )
+
+CM_force=
+while [[ "$#" -gt 0 ]]; do case $1 in
+	--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
+		;;
+
+	--force)
+		echo "INFO: enable --force (for renewal)"
+		CM_force="--force"
+		shift 1
+		;;
+	*) if grep "^-" <<< "$1" >/dev/null ; then
+		echo; echo "ERROR: Unknown parameter: $1"; echo; _showHelp; exit 2
+		fi
+		break;
+		;;
+esac; done
+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 - v$_version <<<===--- - - -
+
+$line
+
+ENDOFHEADER
+
+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
+fi
+
+_testUser
+_testStaging
+
+test -z "${CM_diracme}" && CM_diracme=./certs
+test -z "${CM_dircsr}"  && CM_dircsr=./csr
+
+grep "function public_$1" $( basename $0 ) >/dev/null 
+if [ $# -gt 0 -a $? -eq 0 ]; then
+	# _wd $*
+	action=$1
+	CM_fqdn=$2
+	shift 2
+
+	_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
+
+	_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 $*"
+
+else
+	self=$( basename $0 )
+	cat <<EOF
+
+HELP
+
+  The basic syntax is
+  $self [OPTIONS] ACTION [FQDN] [ALIAS_1 [.. ALIAS_N]]
+
+OPTIONS
+  --trace
+    the output additionally will be written into a tracelog file 
+    below $logdir.
+  --force
+    Use "--force" prameter for renewal of certificates
+
+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
+fi
+
+echo
+_testStaging
-- 
GitLab