diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 6d1712bef045520d58521847256f34d64c8a24ad..0000000000000000000000000000000000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Changelog
-
-## πŸ—“οΈ 2022-05-10
-
-🐞 Bugfix: nocache
-When using restic_nocache it was applied on backup only. Now the restic parameter --no-cache is used in all restic commands.
-
-## πŸ“’ Info
-
-The changelog was started on 2022-05-10.
-
-Legend:
-
-βœ… Added Feature
-✴️ Update
-🐞 Bugfix
-πŸ›‘ Security feature
diff --git a/README.md b/README.md
index 6465169914d10cd6419d486e37d63b147ea01ff8..5c9f18be4d216cf9d5a77747c7a346f0c771dfc2 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Runs on Linux: CentOS, Debian, Manjaro, Ubuntu.
 
 πŸ“„ Source: <https://git-repo.iml.unibe.ch/iml-open-source/iml-backup/> \
 πŸ“œ License: GNU GPL 3.0 \
-πŸ“– Docs: <https://os-docs.iml.unibe.ch/iml-backup/>
+πŸ“— Docs: <https://os-docs.iml.unibe.ch/iml-backup/>
 
 Supported backup clients:
 
diff --git a/backup.sh b/backup.sh
index c9ddb85ed1dfdfbec8b7b0527994d86ec65af599..2b3294b3a2b89ead2310aac3372f8cd2a1688c09 100755
--- a/backup.sh
+++ b/backup.sh
@@ -17,8 +17,8 @@
 # 2022-11-04  ah  v1.2  rename hooks
 # ================================================================================
 
-. $( dirname "$0" )/jobhelper.sh
-. `dirname $0`/inc_bash.sh
+. $( dirname "$0" )/includes/jobhelper.sh || exit 1
+. $(dirname $0)/includes/inc_bash.sh      || exit 1
 
   typeset -i rcBackup=0
   typeset -i rcTransfer=0
@@ -191,9 +191,9 @@ EOFbackupinfo
     cat "$JOBFILE" >>"$JOB_LOGFILE"
 
 
-    echo "INFO: $(date) - Making local backups ... ${DIR_SELF}/localdump.sh ALL" | tee -a "$JOB_LOGFILE"
+    echo "INFO: $(date) - Making local backups ... ${DIR_SELF}/localdump.sh backup ALL" | tee -a "$JOB_LOGFILE"
 
-    "${DIR_SELF}"/localdump.sh ALL | tee -a "$JOB_LOGFILE"
+    "${DIR_SELF}"/localdump.sh backup ALL | tee -a "$JOB_LOGFILE"
     rcBackup=$?
     test $rcBackup -gt 0 && j_notify "db dumps" "rc=$rcBackup" $rcBackup
 
diff --git a/check_clientbackup.sh b/check_clientbackup.sh
index 1daec2aa320e52b7f096deaa44e7803a18a8c3d0..73d568840c87f49bed208a605bae4509e220b42e 100755
--- a/check_clientbackup.sh
+++ b/check_clientbackup.sh
@@ -20,7 +20,7 @@
 # 2023-09-04  ah     v1.6  fix perfdata dirs-del=
 # ==============================================================================
 
-. $(dirname $0)/jobhelper.sh
+. $(dirname $0)/includes/jobhelper.sh
 
 # ------------------------------------------------------------------------------
 # CONFIG
diff --git a/detector.sh b/detector.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a5e028b324ab869f1a40c8c724a001189ca91c5a
--- /dev/null
+++ b/detector.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+# ====================================================================
+#
+#   Detect a database type
+#
+# --------------------------------------------------------------------
+# ah: <www.axel-hahn.de>
+# 2024-02-xx  v0.1  ah   Initial version
+# --------------------------------------------------------------------
+
+cd $( dirname $0 ) || exit 1
+
+. vendor/ini.class.sh || exit 1
+. vendor/color.class.sh || exit 1
+
+. $(dirname $0)/includes/dbdetect.class.sh || exit 1
+
+DBD_DEBUG=0
+showInfos=0
+USAGE="Detect profiles for databases which are located in 
+$DBD_BASEDIR.
+
+For detected profiles it shows its used parameters.
+This script helps you to define / verify custom profiles.
+
+SYNTAX: $( basename $0) [OPTIONS] [FILTER]
+
+OPTIONS:
+    -h|--help     Show this help
+    -i|--infos    Show infos about detected profiles
+    -v|--verbose  Show debug information during detection. This helps to find
+                  out why a profile was skipped.
+
+PARAMETERS:
+    FILTER   a string / regex to filter profilenames.
+
+SXYNTAX:
+    $( basename $0) mysql   Test profiles matching 'mysql' only
+    $( basename $0) -v      Test all profiles with debug output
+"
+# --------------------------------------------------------------------
+
+# ----------------------------------------------------------------------
+# MAIN
+# ----------------------------------------------------------------------
+
+echo ';' >&2
+echo '; --==##|  DATABASE PROFILE DETECTOR  |##==-- '
+echo ';' >&2
+
+while [ "$#" -gt 0 ]; do case $1 in
+    -h|--help)      echo "$USAGE"; exit 0;;
+    -i|--infos)     showInfos=1; shift 1;;
+    -v|--verbose)   DBD_DEBUG=1; shift 1;;
+
+    *) if grep "^-" <<< "$1" >/dev/null ; then
+        echo; echo "ERROR: Unknown parameter: $1"; echo; echo "$USAGE"; exit 2
+       fi
+       break;
+       ;;
+esac; done
+
+sFilter="${1:-.}"
+test ! "${sFilter}" = "." && ( echo -n "Filter enabled: "; color.echo "blue" "${sFilter}"; echo ';' >&2)
+
+dbdetect._wd "------"
+for config in $(dbdetect.getConfigs | grep "${sFilter}"); do
+    dbdetect._wd "----- $config"
+    dbdetect.validate $config
+
+    if dbdetect.exists $config; then
+        color.print "green" "FOUND"; echo ": $config"
+        if [ "$showInfos" -gt "0" ]; then
+            echo "  Type       : $( dbdetect.getType    $config )"
+            echo "  Target dir : $( dbdetect.getProfile $config )"
+            echo "  runas      : $( dbdetect.runas )"
+            echo "  Params     : $( dbdetect.getParams )"
+            echo "  Env        : $( dbdetect.setenv )"
+            echo "  Files      : $( dbdetect.getFiles | grep -c '.' )"
+            echo ';'
+        fi
+    else
+        echo "SKIP : $config" 
+    fi
+    dbdetect._wd
+done
+
+# --------------------------------------------------------------------
diff --git a/docs/30_Configuration/20_Database.md b/docs/30_Configuration/20_Database.md
index 971f0168d32cffa3b6db93e81ef5f2bb103034fb..aa37f253167185540e30eb65550fded4354d5db7 100644
--- a/docs/30_Configuration/20_Database.md
+++ b/docs/30_Configuration/20_Database.md
@@ -1,9 +1,11 @@
-# Basic settings for database backups #
+# Database backups #
+
+## Shared settings for all database types ##
 
 There are 2 required values in the jobs/backup.job
 
 ```text
-dir-localdumps = /var/iml-backup
+dir-localdumps = /var/iml-backup/backup
 keep-days = 7
 ```
 
@@ -11,21 +13,139 @@ This defines the backup target for sql dumps and how long they will be kept loca
 
 There is an optional value to define the target directory for archived dumps. This value is used for couchdb2 only.
 
-`dir-dbarchive = /var/localdumps/archive`
+`dir-dbarchive = /var/iml-backup/archive`
 
 see [backup.job](50_File_backup.job.md)
 
+## Profiles
+
+In the folder plugins/localdump/profiles/ are several ini files.
+
+They describe a database environment by the [detect] section. 
+
+If a profile matches then a database will be dumped or restored.
+For the detection you can verify
+
+* existing files
+* running processes
+* open tcp port
+  * localhost or a remote system
+  * on a local machine (if no hostname is given) you can check the process using this port
+
+After installation there are some ini files that contain a common 
+
+### File names
+
+File convention: `<DBTYPE>_<PROFILE>.ini`
+
+* <DBTYPE> must match a database dumper in plugins/localdump/ without extension ".sh"
+* <DBTYPE>_<PROFILE> is the target folder to store backups. 
+
+  * <PROFILE> is a custom name to 
+  * _<PROFILE> can be empty to mark a local database of the given type.
+
+### Detect
+
+To detect the existance of you can use the following keys. If all found detect entries match it is counted as detected.
+
+| Key          | Type    | Description |
+|---           |---      |---          |
+| binary       | string  | Binary that must be found in path. Use comma to separate multiple binaries.
+| file[]       | string  | full path of a file. It must match "type". It can be given multiple times.
+| process      | regex   | A binary to be matched in the process list
+| tcp          | integer | A tcp port number that must be in use
+| tcp-process  | regex   | A process name offering the local tcp port (regex for last column in `netstat -tulpen`). If process names can differ depending on an os use a pipe to set alternatives eg `postgres|postmaster`
+| tcp-target   | string  | A hostname for tcp port check, eg localhost
+| type         | regex   | a matching regex for `file -b FILE | grep -i REGEX; see "file[]"`
+
+Example:
+
+Mysql detection in a docker container:
+
+```ini
+[detect]
+process = 'mysqld|mariadb'
+tcp = 13306
+tcp-target = localhost
+tcp-process = 'rootlesskit'
+```
+
+Multiple Sqlite files
+
+```ini
+[detect]
+file[] = "/home/axel/data/docker/ciserver/data/imldeployment/data/database/logs.db"
+file[] = "/home/axel/data/docker/ciserver/public_html/valuestore/data/versioncache.db"
+type = "sqlite"
+```
+
+### Setter
+
+If a profile detection was successful then values in the [set] section will be applied.
+
+| Key          | Type    | Description |
+|---           |---      |---          |
+| su           | string  | User for su command; used for postgres
+| dbuser       | string  | Database user; for replacement
+| dbpassword   | string  | Password of database user; for replacement
+| params       | string  | cli paramaters fΓΌr dump / restore tools.
+| env          | string  | extend environment with some variables, eg. export var1="something here". After backup/ restore this variables will be unset
+
+Replacements that can be used for values `params` and `env`:
+
+| Key          | Description |
+|---           |---          |
+| {dbpassword} | value of [set] -> dbpassword
+| {dbuser}     | value of [set] -> dbuser
+| {tcp-port}   | value of [detect] -> tcp-port
+| {tcp-target} | value of [detect] -> tcp-target
+
+```ini
+[set]
+
+su = ''
+dbuser = 'root'
+dbpassword = '12345678'
+params = '--port={tcp-port} --password={dbpassword} --user={dbuser} --host={tcp-target}'
+
+env = 'export var1="happy meal"; export var2="new"; export var3="year!"'
+
+```
+
 ## Backup sqlite ##
 
 Sqlite files can be located anywhere in the filesystem. That's why the
-cannot be located with an auto detection. You need to define them in
-the file jobs/backup-dbfiles.job first.
+cannot be located with an auto detection. 
 
-Per database file set a line with the `sqlite = ` prefix
+* In the detect section set `type = "sqlite"`
+* Per database file set a line with the `file[] = ` prefix
 
-```text
-sqlite = /var/lib/whatever/sqlite-database_01.db
-sqlite = /var/lib/somewhere/else/db.sqlite
-```
+This is the plugins/localdump/profile/sqlite.ini.example:
+
+```ini
+# ======================================================================
+#
+# LOCAL SQLITE DATABASES
+#
+# ======================================================================
+
+[detect]
+
+# the filetype to detect using file command
+type = "sqlite"
+
+# list of files to backup
+# file[] = "/var/www/database/logs.db"
 
-see [backup-dbfiles.job](50_File_backup-dbfiles.job.md)
+
+[set]
+
+su = ''
+dbuser = ''
+dbpassword = ''
+
+params = ''
+env = ''
+
+# ----------------------------------------------------------------------
+```
diff --git a/docs/40_Usage/20_Database.md b/docs/40_Usage/20_Database.md
index 9b931f2bb5bb9a565f6a236524310b8b8b3ef112..74f89365a467fa90b816f024dbe764d51ba3d274 100644
--- a/docs/40_Usage/20_Database.md
+++ b/docs/40_Usage/20_Database.md
@@ -1,35 +1,53 @@
 ## Description
 
 To create backup database dumps without transfer of local directory to a backup target use `sudo ./localdump.sh`.
-Backup dumps will be stored as gzip files into `/var/iml-backup/[service]`.
+Backup dumps will be stored as gzip files into `/var/iml-backup/[profile]`.
 
 ## Help
 
 ```text
+LOCALDUMP detects existing local databases and dumps them locally.
+It is included in the backup.sh to dump all before a file backup will store 
+them. It can be started seperately for manual database backups or for restore.
+
 SYNTAX:
-localdump.sh [[operation]] [Name_of_service] [[more services]]
-localdump.sh restore  [Name_of_service] [file-to-restore]
-
-  operation       - one of backup|restore; optional parameter; default is backup
-  Name_of_service - name of database service
-                    You get a list of all available services without parameter
-                    Use ALL for bulk command
-  file            - filename of db dump to restore
-
-Known services (see ./plugins/localdump):
-couchdb
-couchdb2
-ldap
-mysql
-pgsql
-sqlite
+    localdump.sh [OPTIONS] <operation> <profile [more_profiles]>
+
+OPTIONS:
+    -h|--help   show this help
+
+PARAMETERS:"
+    operation   - one of check|backup|restore; optional parameter
+                      backup   dump all databases/ schemes of a given service
+                      check    show info only if the service is available
+                      restore  import a dump into same or new database
+                               Without a filename it starts an interactive mode
+    profile     - name of database profiles
+                  You get a list of all available services without parameter
+                  Use ALL for bulk command
+    file        - filename of db dump to restore to origin database scheme
+
+EXAMPLES:
+    localdump.sh backup
+    localdump.sh backup ALL
+                 Backup all databases of all found services
+    localdump.sh backup mysql
+                 Backup all Mysql databases.
+
+    localdump.sh restore
+                 Start interactive restore of a database of any service.
+    localdump.sh restore sqlite
+                 Start interactive restore of an sqlite database.
+    localdump.sh restore <file-to-restore> [<database-name>]
+                 Restore a given dump file to the origin database scheme or
+                 to a new/ other database with the given name.
 ```
 
 If you have local Mysql daemon or Pgsql you can test it by starting
 
 ```text
 # dump all databases
-sudo ./localdump.sh ALL
+sudo ./localdump.sh backup ALL
 ```
 
 ```text
@@ -43,12 +61,12 @@ To dump schemes of a specific database type add the name of a known service.
 
 ```text
 # dump all Mysql databases
-sudo ./localdump.sh mysql
+sudo ./localdump.sh backup mysql
 ```
 
 ## Structure in the backup folder
 
-In the database dump folder is a subdir per service `/var/iml-backup/[service]`.
+In the database dump folder is a subdir per service `/var/iml-backup/[profile]`.
 
 Below the service folder are files named like the database scheme + `__` + timestamp.
 
@@ -59,15 +77,14 @@ will be deleted from `/var/iml-backup/[service]`.
 
 ### Backup sqlite
 
-Keep in mind that you need to define sqlite databases in jobs/backup-dbfiles.job first.
+Keep in mind that you need to create an ini file to enable sqlite backups.
+See plogins/localdump/profiles/sqlite.ini.example.
 
 ```text
 # dump all Sqlite databases
 sudo ./localdump.sh sqlite
 ```
 
-This greps "^sqlite = " in jobs/backup-dbfiles.job and squentially dumps each sqlite file.
-
 In the folder /var/iml-backup/sqlite/ it creates 2 files per database 
 
 * the gzip compressed dump (filename is full path with replacing `/` by `_`)
diff --git a/docs/_index.md b/docs/_index.md
index 13f10a4d5f65844f3a709cf0470dc22c1376fdd8..18ddd14589cd539aef1832cac1f0f1ef64df2afa 100644
--- a/docs/_index.md
+++ b/docs/_index.md
@@ -8,7 +8,7 @@ Runs on
 
 πŸ“„ Source: <https://git-repo.iml.unibe.ch/iml-open-source/iml-backup/> \
 πŸ“œ License: GNU GPL 3.0 \
-πŸ“– Docs: see docs folder or online <https://os-docs.iml.unibe.ch/iml-backup/>
+πŸ“— Docs: see docs folder or online <https://os-docs.iml.unibe.ch/iml-backup/>
 
 Supported backup clients:
 
diff --git a/helper/couchdb2_restore_deleted_db.sh b/helper/couchdb2_restore_deleted_db.sh
index 2f2eb5f70cfaa9ee0cdde645bd11a17ad2b8dd50..36d915f0d130505f8ee17d3dd77979bb204d965c 100755
--- a/helper/couchdb2_restore_deleted_db.sh
+++ b/helper/couchdb2_restore_deleted_db.sh
@@ -20,8 +20,8 @@
 
 cd "$( dirname $0 )/.." || exit 1
 
-  . $(dirname $0)/jobhelper.sh
-  . $(dirname $0)/inc_bash.sh
+  . $(dirname $0)/includes/jobhelper.sh
+  . $(dirname $0)/includes/inc_bash.sh
 
 BACKUP_BASEDIR=$( _j_getvar "${JOBFILE}" "dir-localdumps" )/couchdb2
 ARCHIVE_DIR=$(    _j_getvar "${JOBFILE}" "dir-dbarchive"  )/couchdb2
diff --git a/includes/dbdetect.class.sh b/includes/dbdetect.class.sh
new file mode 100644
index 0000000000000000000000000000000000000000..a7327f1744af651c4c5f0ee1ff95d64618c12352
--- /dev/null
+++ b/includes/dbdetect.class.sh
@@ -0,0 +1,238 @@
+# ======================================================================
+#
+# Database detector functions
+# used in localdump.se and detector.sh
+#
+# it analyzes all ini files with database profiles and sets
+# variables / environment to perform database backup/ restore
+#
+# Remark:
+# The script that sources this file must source vendor/ini.class.sh
+# before using these functions.
+#
+# ----------------------------------------------------------------------
+# ah - Axel Hahn <axel.hahn@unibe.ch>
+#
+# 2024-03-15  ah     v1.0  first public version
+# ======================================================================
+
+# ----------------------------------------------------------------------
+# CONFIG
+# ----------------------------------------------------------------------
+
+DBD_BASEDIR=plugins/localdump/profiles
+
+declare -A DBD_PARAMS
+DBD_INIFILE=
+
+
+# ----------------------------------------------------------------------
+# FUNCTIONS
+# ----------------------------------------------------------------------
+
+# write debug output if DBD_DEBUG is enabled
+# param  string  text to show
+function dbdetect._wd(){
+    test "$DBD_DEBUG" -ne "0" && color.echo "darkgray" "DEBUG: $*" >&2
+}
+
+# check if a process name exisats
+# param  string  regex to find in ps -ef output
+# param  bool    flag to skip; default: none = abort on miss
+function dbdetect._requireProcess(){
+    # echo "CHECK process $1"
+    # ps -ef | grep -v grep | grep -E "$1" >/dev/null
+    pgrep -l "$1" >/dev/null
+    rcself=$?
+    if [ $rcself -ne 0 ]; then
+        rc=$rc+$rcself
+        echo "INFO: missing process $1"
+        if [ -z "$2" ]; then
+        exit 4
+        fi
+        return 1
+    fi
+    return 0
+}
+
+# get a list of all config files
+# param  string  full path of ini file
+function dbdetect.getConfigs(){
+    find ${DBD_BASEDIR} -type f -name "*.ini" | sort
+}
+
+# get type from given ini file
+# param  string  full path of ini file
+function dbdetect.getType(){
+    basename "$1" | cut -d "_" -f 1 | sed "s,\.ini$,,"
+}
+
+# get profile from given ini file
+# param  string  full path of ini file
+function dbdetect.getProfile(){
+    basename "$1" | cut -d "_" -f 1- | sed "s,\.ini$,,"
+}
+
+
+# check if the requirements for a database match
+# param  string  full path of ini file to check
+function dbdetect.validate(){
+    local _config="${1:-$DBD_INIFILE}"
+
+    # show errors in profile ini files
+    ini.validate "$_config" "$( dirname "$0")/includes/dbdetect_validate_profile.ini" 1 && dbdetect._wd "OK: Validation of '$_config' was successful"
+}
+
+# check if the requirements for a database match
+# param  string  full path of ini file to check
+function dbdetect.exists(){
+    local _config="$1"
+
+    DBD_PARAMS=()
+
+    local _found=0
+
+    if [ ! -f "$_config" ]; then
+        dbdetect._wd "ERROR: ini file '$_config' does not exist."
+        return 1
+    fi
+
+
+    # set file and inisection we read values from
+    ini.set "$_config" "detect"
+
+    # --- check tcp
+    local tcpport; tcpport=$( ini.value "tcp-port" )
+    if [ -n "$tcpport" ]; then
+    local tcptarget; tcptarget=$( ini.value "tcp-target" )
+    tcptarget=${tcptarget:-localhost}
+    if { ! >/dev/tcp/$tcptarget/$tcpport; } > /dev/null 2>&1; then
+        # echo "No port tcp $tcpport available"
+        dbdetect._wd "... No port tcp $tcpport available on $tcptarget"
+        return 1
+    fi
+    dbdetect._wd "... Found tcp $tcpport on $tcptarget."
+    fi
+
+    # --- check tcp process
+    local tcpprocess; tcpprocess=$( ini.value "tcp-process" )
+    if [ -n "$tcpprocess" ]; then
+        if ! netstat -tulpen 2>/dev/null | grep -E "^tcp.*:${tcpport} .*/(${tcpprocess})" >/dev/null; then
+            # echo "No port tcp $tcpport available"
+            dbdetect._wd "... $tcpprocess not found for tcp ${tcpport}"
+            return 1
+        fi
+        dbdetect._wd "... tcp $tcpport is used by $tcpprocess."
+    fi
+
+    # --- check process
+    local process; process=$( ini.value "process" )
+    if [ -n "${process}" ]; then
+        if ! dbdetect._requireProcess "${process}" 1 >/dev/null; then
+            dbdetect._wd "... Missing process: ${process}"
+            return 1
+        fi
+        # if ! ps -eo command | grep -E "${process}" >/dev/null 2>&1; then
+        #     dbdetect._wd "... Process ${process} was not found"
+        #     return 1
+        # fi
+        dbdetect._wd "... Process ${process} was found"
+    fi
+
+    # --- check db files
+    local filetype; filetype=$( ini.value "type" )
+
+    if [ -n "${filetype}" ]; then
+      local myfiles
+      for myfile in $( ini.value "file[]" )
+      do
+        if ! file -b "${myfile}" | grep -i "$filetype" >/dev/null; then
+          dbdetect._wd "... File ${myfile} is not of type $filetype"
+          return 1
+        fi
+        dbdetect._wd "... File ${myfile} is type $filetype"
+        myfiles+="${myfile}|"
+      done
+      if [ -z "${myfiles}" ]; then
+        dbdetect._wd "... No files for type [${filetype}] were added"
+        return 1
+      fi
+    fi
+
+    # --- check binaries
+    local binary; binary=$( ini.value "binary" )
+    if [ -n "${binary}" ]; then
+        for mybinary in $( echo "${binary}" | tr "," " " ); do
+            if ! which "$mybinary" >/dev/null 2>&1; then
+                dbdetect._wd "... Missing binary: ${mybinary}"
+                return 1
+            fi
+            dbdetect._wd "... Binary: ${mybinary} was found"
+        done
+    fi
+
+    # --- OK, everything was found ... we initialize it
+    dbdetect._wd "OK, match: $_config"
+
+    ini.set "$_config" "set"
+    local value
+    local dbuser;     dbuser=$( ini.value "dbuser" )
+    local dbpassword; dbpassword=$( ini.value "dbpassword" )
+
+    for mykey in env params
+    do
+        value="$( ini.value "$mykey" )"
+        value="${value//\{tcp-port\}/$tcpport}"
+        value="${value//\{tcp-target\}/$tcptarget}"
+        value="${value//\{dbuser\}/$dbuser}"
+        value="${value//\{dbpassword\}/$dbpassword}"
+        value="${value//\{file\[\]\}/$myfiles}"
+        
+        DBD_PARAMS[$mykey]="$value"
+        dbdetect._wd ">>> $mykey = $value"
+        grep -q "{[a-z-]*" <<< "$value" && echo "WARNING: Maybe unresolved variables in [set] > $mykey '$_config'"
+
+    done
+    dbdetect._wd ">>> files = $myfiles"
+    DBD_PARAMS[files]="$myfiles"
+
+    return 0
+}
+
+# set a profile name
+# param  string  profile name
+function dbdetect.setProfile(){
+    DBD_INIFILE="${DBD_BASEDIR}/${1}.ini"
+    dbdetect.exists "${DBD_BASEDIR}/${1}.ini"
+}
+
+function dbdetect.getParams(){
+    echo "${DBD_PARAMS['params']}"
+}
+
+
+# for backup scripts: get checked files from [detect] -> file[]
+# 
+function dbdetect.getFiles(){
+    echo "${DBD_PARAMS['files']}" | tr "|" "\n"
+}
+
+# set variables in [set] -> env = ...
+# USAGE:
+# eval $( dbdetect.setenv )
+function dbdetect.setenv(){
+    echo "${DBD_PARAMS['env']}"
+}
+
+# unset variables from [set] -> env = ...
+# USAGE:
+# eval $( dbdetect.unsetenv )
+function dbdetect.unssetenv(){
+    echo "${DBD_PARAMS['env']}" | grep -o '[a-z0-9]*=' | tr -d '=' | sed "s,^,unset ,"
+}
+
+function dbdetect.runas(){
+    echo "${DBD_PARAMS['su']}"
+}
+
+# ----------------------------------------------------------------------
diff --git a/includes/dbdetect_validate_profile.ini b/includes/dbdetect_validate_profile.ini
new file mode 100644
index 0000000000000000000000000000000000000000..b438680e36adbe096969bca1e6f0528525dc67d8
--- /dev/null
+++ b/includes/dbdetect_validate_profile.ini
@@ -0,0 +1,52 @@
+# ----------------------------------------------------------------------
+#
+# VALIDATE PROFILE INI
+#
+# ----------------------------------------------------------------------
+
+# IDEA
+[style]
+section  = "^[a-zA-Z0-9_]*$"
+key      = "^[a-zA-Z0-9_\-]*$"
+
+# ----------------------------------------------------------------------
+# section names
+#
+# SYNTAX:
+# comma separated list of sections
+# ----------------------------------------------------------------------
+
+[sections]
+must = "detect,set"
+can = ""
+
+# ----------------------------------------------------------------------
+# validate keys in sections
+#
+# SYNTAX:
+# <section> .<key> = "<VALIDATION_TYPE>[:<VALIDATION_VALUE>]"
+# VALIDATION_TYPE:
+# - INTEGER
+# - REGEX:<REGEX_TO_VALIDATE>
+# - ONEOF:<VALUE1[,<VALUE_N>]>
+# ----------------------------------------------------------------------
+
+[varsMust]
+; detect.tcp-port = "INTEGER"
+
+[varsCan]
+detect.binary      = "REGEX:.*"
+detect.process     =
+detect.tcp-port    = "INTEGER"
+detect.tcp-target  = "REGEX:.*"
+detect.tcp-process =
+detect.type        = "ONEOF:sqlite"
+detect.file[]      =
+
+set.su =
+set.dbuser =
+set.dbpassword =
+set.env =
+set.params =
+
+# ----------------------------------------------------------------------
diff --git a/inc_bash.sh b/includes/inc_bash.sh
similarity index 89%
rename from inc_bash.sh
rename to includes/inc_bash.sh
index f299e4377b15ceb84279229854caa83f5d2db358..636986f2eae815fa48057f14cb64022ac541a503 100755
--- a/inc_bash.sh
+++ b/includes/inc_bash.sh
@@ -103,3 +103,13 @@ function fetchrc(){
   }
 
 # ----------------------------------------------------------------------
+
+# define color presets
+# see https://www.axel-hahn.de/docs/bash_colorfunctions/Color_presets.html
+COLOR_PRESET_error=("white" "red")
+COLOR_PRESET_ok=("green")
+COLOR_PRESET_warning=("yellow")
+
+COLOR_PRESET_cmd=("lightblue")
+COLOR_PRESET_head=("yellow")
+COLOR_PRESET_input=("green")
diff --git a/jobhelper.sh b/includes/jobhelper.sh
similarity index 99%
rename from jobhelper.sh
rename to includes/jobhelper.sh
index 8eb304e8e52f0d61b061bf1ab9304829b65efdbe..353e74139a06e9ae2fa1e958b5060f4c98e4c195 100755
--- a/jobhelper.sh
+++ b/includes/jobhelper.sh
@@ -228,7 +228,6 @@ function _j_runHooks(){
     _j_runHooks "$_hookbase"
   fi
 
-  echo
 }
 
 # ------------------------------------------------------------
diff --git a/jobs/backup-dbfiles.job.dist b/jobs/backup-dbfiles.job.dist
deleted file mode 100644
index e1679cdbd4c7f5edf334aa7f3625317ab39434f2..0000000000000000000000000000000000000000
--- a/jobs/backup-dbfiles.job.dist
+++ /dev/null
@@ -1,22 +0,0 @@
-# ----------------------------------------------------------------------
-#
-# jobfile for backup of filebased databases
-# list of files with servicename as key ... and its files with full path
-#
-# ----------------------------------------------------------------------
-#
-# SYNTAX: 
-# [variable] = [value]
-#
-# - variable must start in first column
-# - char "=" must be surrounded by space
-# - value - any string; no " needed
-#
-# EXAMPLE:
-# sqlite = /var/lib/whatever/mysqlite-database.db
-#
-# ----------------------------------------------------------------------
-
-
-
-# ----------------------------------------------------------------------
diff --git a/localdump.sh b/localdump.sh
index f9d1b11f2ae569a3a7116e8df613c3337d79bb40..ac35250e12b2e6b32c2e9a5caf551cdbe2355034 100755
--- a/localdump.sh
+++ b/localdump.sh
@@ -16,9 +16,11 @@
 # 2022-02-18  .....  WIP: use class like functions
 # 2022-03-17  .....  WIP: add lines with prefix __DB__
 # 2022-11-04  ah     rename hooks
+# 2024-03-14  ah     v2.0: use profiles for local and remote databases
 # ======================================================================
 
 # --- variables:
+# ARCHIVE_BASEDIR     {string}  base directory for db archive (couchdb2 only)
 # BACKUP_BASEDIR      {string}  base directory for db dumps
 # BACKUP_DATE         {string}  string with current timestamp; will be part of filename for backups
 # BACKUP_KEEP_DAYS    {int}     count of days how long to keep db dumps below $BACKUP_BASEDIR
@@ -31,439 +33,634 @@
 # CONFIG VARS
 # ----------------------------------------------------------------------
 
+    . $(dirname $0)/vendor/ini.class.sh    || exit 1
+    . $(dirname $0)/vendor/color.class.sh  || exit 1
 
-  . `dirname $0`/jobhelper.sh
-  . `dirname $0`/inc_bash.sh
-  # if [ -r ~/.backup.conf ]; then
-  #   . ~/.backup.conf
-  # fi
+    . $(dirname $0)/includes/jobhelper.sh  || exit 1
+    . $(dirname $0)/includes/inc_bash.sh   || exit 1
 
-  if [ ! -r "${JOBFILE}" ]; then
-    color error
-    echo ERROR: missing config file ${JOBFILE}.
-    color reset
-    exit 1
-  fi
+    . $(dirname $0)/includes/dbdetect.class.sh || exit 1
 
-  LOCALDUMP_LOADED=1
-
-  BACKUP_BASEDIR=
-  BACKUP_PLUGINDIR=
-
-  # Cleanup local dumps older N days
-  typeset -i BACKUP_KEEP_DAYS=0
-
-  BACKUP_DATE=
-
-# ----------------------------------------------------------------------
-# FUNCTIONS 4 DB-WRAPPER
-# ----------------------------------------------------------------------
-
-  function db.init(){
-    
-    BACKUP_BASEDIR=`_j_getvar ${JOBFILE} "dir-localdumps"`
-
-    # check
-    if [ -z "$BACKUP_BASEDIR" ]; then
-      color error
-      echo ERROR: missing config for backup target.
-      echo There must be an entry dir-localdumps in ${JOBFILE}
-      color reset
-      exit 1
-    fi
-    BACKUP_PLUGINDIR=`dirname $0`/plugins/localdump
-    BACKUP_KEEP_DAYS=`_j_getvar ${JOBFILE} "keep-days"`
-
-    if [ $BACKUP_KEEP_DAYS -eq 0 ]; then
-      BACKUP_KEEP_DAYS=7
-    fi
-    BACKUP_DATE=$(/bin/date +%Y%m%d-%H%M)
-  }
-
-  # helpfer function for SERVICENAME.backup
-  # it is called after the service specific dump was done.
-  # param  {string}  filename of created dump file
-  function db._compressDumpfile(){
-    local _outfile=$1
-
-    # $myrc is last returncode - set in fetchrc
-    if [ $myrc -eq 0 ]; then
-      echo -n "gzip $1 ... "
-      gzip -9 -f "${1}"
-      fetchrc
-    else
-      cecho error "ERROR occured while dumping - no gzip of $_outfile"
+    if [ ! -r "${JOBFILE}" ]; then
+        color.echo error "ERROR: missing config file ${JOBFILE}."
+        exit 1
     fi
-    # echo -n "__DB__$SERVICENAME INFO: backup to " 
-    # ls -l "$_outfile"* 2>&1
-    # echo
-  }
 
-# ----------------------------------------------------------------------
-# FUNCTIONS 4 BACKUP
-# ----------------------------------------------------------------------
+    LOCALDUMP_LOADED=1
 
+    ARCHIVE_BASEDIR=
+    BACKUP_BASEDIR=
+    BACKUP_PLUGINDIR=
 
-  # ------------------------------------------------------------
-  # cleanup a backup dir: remove old files and delete empty dirs
-  function cleanup_backup_target(){
-    if [ -d "${BACKUP_TARGETDIR}" ]; then
-      h3 "CLEANUP ${BACKUP_TARGETDIR} older $BACKUP_KEEP_DAYS days ..."
+    DBD_DEBUG=0
 
-      echo find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print
-      color cmd
-      find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print
-      color reset
+    # Cleanup local dumps older N days
+    typeset -i BACKUP_KEEP_DAYS=0
 
-      if [ `find "${BACKUP_TARGETDIR}" -type f | wc -l` -eq 0 ]; then
-        echo "INFO: the directory is empty - deleting it"
-        rm -rf "${BACKUP_TARGETDIR}"
-      fi
-    fi
-  }
-
-  # ------------------------------------------------------------
-  # compress a file
-  # shared function in localdump_*
-  # param  string  filename of uncompressed output file
-  function compress_file(){
-    echo compressing $1 ...
-    gzip -9 -f "${1}"
-    fetchrc
-  }
-
-  # ------------------------------------------------------------
-  # create a backup directory with name of service
-  # shared function in localdump_*
-  function create_targetdir(){
-    mkdir -p "${BACKUP_TARGETDIR}" 2>/dev/null
-    if [ ! -d "${BACKUP_TARGETDIR}" ]; then
-      color error
-      echo FATAL ERROR: directory ${BACKUP_TARGETDIR} was not created
-      color reset
-      exit 1
-    fi
-  }
-
-
-  # ------------------------------------------------------------
-  # generate a base filename for backup dump based on on db name
-  # ... and added timestamp
-  # param  string  name of database schema
-  # --> see listBackupedDBs() and guessDB() - these function must be able to split this
-  function get_outfile(){
-    echo $*__${BACKUP_DATE}
-  }
-
-  # ------------------------------------------------------------
-  # get name of a service script
-  # param  string  name of a service 
-  function get_service_script(){
-    local _service=$1
-    ls -1 ${BACKUP_PLUGINDIR}/${_service}.sh 2>/dev/null
-  }
-
-  # ------------------------------------------------------------
-  # get a list of existing dumper scripts
-  function get_services(){
-    #ls -1 `dirname $0`/localdump_* | sed "s#`dirname $0`/localdump_##" | sed "s#\.sh##"
-    ls -1 ${BACKUP_PLUGINDIR}/*.sh | sed "s#${BACKUP_PLUGINDIR}/##" | sed "s#\.sh##" | sort
-  }
-
-  # ------------------------------------------------------------
-  # show directory infos with count of files and used space
-  # show used space and count of files and dirs
-  function show_info_backup_target(){
-
-    if [ -d "${BACKUP_TARGETDIR}" ]; then
-      h3 "INFO about backup target ${BACKUP_TARGETDIR}"
-
-      echo -n "used space: "
-      du -hs "${BACKUP_TARGETDIR}"
-
-      echo -n "subdirs   : "
-      find "${BACKUP_TARGETDIR}" -type d | wc -l
-
-      echo -n "files     : "
-      find "${BACKUP_TARGETDIR}" -type f | wc -l
-
-      echo -n "free space: "
-      df -h "${BACKUP_TARGETDIR}" | tail -1 | awk '{ print $4 }'
-      echo
-    fi
-  }
+    BACKUP_DATE=
+    LASTINPUT=
 
 # ----------------------------------------------------------------------
-# FUNCTIONS 4 RESTORE
+# FUNCTIONS 4 DB-WRAPPER
 # ----------------------------------------------------------------------
 
 
-  # ------------------------------------------------------------
-  # restore: show databases that can be restored
-  # param  string  file filter optional;
-  function listBackupedDBs(){
-    if [ -d "${BACKUP_TARGETDIR}" ]; then
-      cd "${BACKUP_TARGETDIR}"
-      if [ -z $1 ]; then
-        find -type f | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g" | grep -v "\.meta" | sort -ud| sed "s#^\./##g"
-      else
-        ls -ltr "$*__"* | sed "s#^\./##g"
-      fi
-      cd - >/dev/null
-    else
-      color error
-      echo ERROR: ${BACKUP_TARGETDIR} does not exist - here are no backups to restore.
-      color reset
-      echo
-      echo You can try to restore dumps:
-      echo "1) Restore dump files from a backup set"
-      echo "     `dirname $0`/restore.sh $BACKUP_BASEDIR"
-      echo "2) Copy restored dumps into $BACKUP_TARGETDIR"
-      echo "3) Start database restore again"
-      echo "     `dirname $0`/localdump.sh restore [service]"
-      echo
- 
-      exit 1
-    fi
-  }
-
-
-  # ------------------------------------------------------------
-  # guess name of the database file
-  # param  string  filename of db dump; can be full path or not
-  function guessDB(){
-    dumpfile=$1
-
-    # the metafile is written in sqlite backup to store full path
-    metafile=${BACKUP_TARGETDIR}/${dumpfile}.meta
-    if [ -f $metafile ]; then
-      cat $metafile
-    else
-      sBasename=`basename $1`
-      sDb=`echo ${sBasename} | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g"`
-      if [ -z $sDb ]; then
-        color error
-        echo ERROR: db name was not detected from file $1
-        color reset
-        exit 1
-      fi
-      echo $sDb
-    fi
-  }
-
-
-  # ------------------------------------------------------------
-  # show help
-  # ------------------------------------------------------------
-  function showhelp(){
-    local _self
-    _self=$( basename "$0" )
-    echo "SYNTAX: "
-    echo "$_self [[operation]] [Name_of_ervice] [[more services]]"
-    echo
-    echo "$_self backup  [Name_of_service]"
-    echo "$_self restore [Name_of_service] [[file-to-restore]]"
-    echo
-    echo "  operation       - one of check|backup|restore; optional parameter; default is backup"
-    echo "                      check    WIP: show info only if the service is available"
-    echo "                      backup   dump all databases/ schemes of a given service"
-    echo "                      restore  import a dump into same or new database"
-    echo "                               Without a filename it starts an interactive mode"
-    echo "  Name_of_service - name of database service"
-    echo "                    You get a list of all available services without parameter"
-    echo "                    Use ALL for bulk command"
-    echo "  file            - filename of db dump to restore to origin database scheme"
-  }
+    # helpfer function for SERVICENAME.backup
+    # it is called after the service specific dump was done.
+    # param  {string}  filename of created dump file
+    function db._compressDumpfile(){
+        local _outfile=$1
 
+        # $myrc is last returncode - set in fetchrc
+        if [ $myrc -eq 0 ]; then
+            echo -n "gzip $_outfile ... "
+            gzip -9 -f "${_outfile}"
+            fetchrc
+        else
+            color.echo error "ERROR occured while dumping - no gzip of $_outfile"
+        fi
+        # echo -n "__DB__$SERVICENAME INFO: backup to " 
+        # ls -l "$_outfile"* 2>&1
+        # echo
+    }
 
 # ----------------------------------------------------------------------
-# INIT
+# FUNCTIONS 4 BACKUP
 # ----------------------------------------------------------------------
 
-  db.init
 
-  # ----- checks
+    # ------------------------------------------------------------
+    # cleanup a backup dir: remove old files and delete empty dirs
+    function cleanup_backup_target(){
+        if [ -d "${BACKUP_TARGETDIR}" ]; then
+            h3 "CLEANUP ${BACKUP_TARGETDIR} older $BACKUP_KEEP_DAYS days ..."
+
+            echo find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print
+            color.preset cmd
+            find "${BACKUP_TARGETDIR}" -mtime +$BACKUP_KEEP_DAYS -delete -print
+            color.reset
+
+            if [ $(find "${BACKUP_TARGETDIR}" -type f | wc -l) -eq 0 ]; then
+                echo "INFO: the directory is empty - deleting it"
+                rm -rf "${BACKUP_TARGETDIR}"
+            fi
+        fi
+    }
 
-  # . /usr/local/bin/inc_cronfunctions.sh
-  j_requireUser "root"
+    # ------------------------------------------------------------
+    # compress a file
+    # shared function in localdump_*
+    # param  string  filename of uncompressed output file
+    function compress_file(){
+        echo -n compressing $1 ...
+        gzip -9 -f "${1}"
+        fetchrc
+    }
 
-  h1 `date` IML BACKUP :: LOCALDUMP :: $*
+    # ------------------------------------------------------------
+    # create a backup directory with name of service
+    # shared function in localdump_*
+    function create_targetdir(){
+        mkdir -p "${BACKUP_TARGETDIR}" 2>/dev/null
+        if [ ! -d "${BACKUP_TARGETDIR}" ]; then
+            color.echo "error" "FATAL ERROR: directory ${BACKUP_TARGETDIR} was not created"
+            exit 1
+        fi
+    }
 
-  if [ $# -eq 0 ]; then
-    color error
-    echo "ERROR: missing parameter."
-    color reset
-    echo
-    showhelp
-    echo
-    echo "Known services (see ${BACKUP_PLUGINDIR}):"
-    get_services
-    exit 1
-  fi
 
-  mode="backup"
-  case "$1" in
-    backup|check|restore|shell)
-      mode=$1
-      shift 1
-      ;;
-  esac
+    # ------------------------------------------------------------
+    # generate a base filename for backup dump based on on db name
+    # ... and added timestamp
+    # param  string  name of database schema
+    # --> see listBackupedDBs() and guessDB() - these function must be able to split this
+    function get_outfile(){
+        echo $*__${BACKUP_DATE}
+    }
 
-  export SERVICENAME=$1
-  BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME}
-  BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
+    # ------------------------------------------------------------
+    # get name of a service script
+    # param  string  name of a service 
+    function get_service_script(){
+        local _service=$1
+        local _type; _type=$( dbdetect.getType "$_service" )
+        ls -1 ${BACKUP_PLUGINDIR}/${_type}.sh 2>/dev/null
+    }
 
-  case "$mode" in
     # ------------------------------------------------------------
-    check)
-      . $BACKUP_SCRIPT $mode
-      ;;
+    # get a list of existing database profiles
+    function get_database_profiles(){
+        for config in $(dbdetect.getConfigs); do
+            if dbdetect.exists "$config"; then
+                echo "$( dbdetect.getProfile $config )"
+            fi
+        done
+    }
+
     # ------------------------------------------------------------
-    backup)
-      if [ "$SERVICENAME" = "ALL" ]; then
-        services=$(get_services)
-        echo AUTO: calling local backup scripts for all known services
-        echo $services
+    # show directory infos with count of files and used space
+    # show used space and count of files and dirs
+    function show_info_backup_target(){
+      if [ -d "${BACKUP_TARGETDIR}" ]; then
+        h3 "INFO about backup target ${BACKUP_TARGETDIR}"
+  
+        echo -n "used space: "
+        du -hs "${BACKUP_TARGETDIR}"
+  
+        echo -n "subdirs   : "
+        find "${BACKUP_TARGETDIR}" -type d | wc -l
+  
+        echo -n "files     : "
+        find "${BACKUP_TARGETDIR}" -type f | wc -l
+  
+        echo -n "free space: "
+        df -h "${BACKUP_TARGETDIR}" | tail -1 | awk '{ print $4 }'
         echo
-      else
-        services=$*
       fi
+    }
 
-      # ----- check all params
-      for SERVICENAME in $services
-      do
-        BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
-        if [ ! -f $BACKUP_SCRIPT ]; then
-          color error
-          echo ERROR: parameter $SERVICENAME seems to be wrong.
-          echo The backup script does not exist: $BACKUP_SCRIPT
-          color reset
-          echo
-          echo services in this folder are:
-          get_services
-          exit 2
-        fi
-      done
+# ----------------------------------------------------------------------
+# FUNCTIONS 4 RESTORE
+# ----------------------------------------------------------------------
 
-      # ----- GO
-      for SERVICENAME in $services
-      do
+    # ------------------------------------------------------------
+    # restore: show profiles from that exist backups
+    # global  string  BACKUP_BASEDIR  base directory of all backups
+    function listBackupedServices(){
+        (
+            test -n "${BACKUP_BASEDIR}" && test -d "${BACKUP_BASEDIR}" \
+                && find "${BACKUP_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;
+            test -n "${ARCHIVE_BASEDIR}" && test -d "${ARCHIVE_BASEDIR}" \
+                && find "${ARCHIVE_BASEDIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;
+        ) | sort -u
+    }
 
-        BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME}
-        BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
+    # ------------------------------------------------------------
+    # restore: show databases or dumps of a given database that can be restored
+    # global  string  BACKUP_BASEDIR  base directory of all backups of selected dbprofile
+    # param  string  optional: DB-Name for file filter to select from existing dumps;
+    function listBackupedDBs(){
+        if [ -d "${BACKUP_TARGETDIR}" ]; then
+            if [ -z $1 ]; then
+                # list all databases
+                find "${BACKUP_TARGETDIR}" -mindepth 1 -maxdepth 1 -type f -exec basename {} \; \
+                    | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g" \
+                    | sed "s#\..*##g" \
+                    | sort -ud| sed "s#^\./##g"
+            else
+                # list dumps of a database
+                ls -ltr ${BACKUP_TARGETDIR}/${1}*gz | sed "s,${BACKUP_TARGETDIR}/,,g"
+            fi
+        else
+            color.echo error "ERROR: ${BACKUP_TARGETDIR} does not exist - here are no backups to restore."
+            echo
+            echo "You can try to restore dumps:"
+            echo "1) Restore dump files from a backup set"
+            echo "     $(dirname $0)/restore.sh $BACKUP_BASEDIR"
+            echo "2) Copy restored dumps into $BACKUP_TARGETDIR"
+            echo "3) Start database restore again"
+            echo "     $(dirname $0)/localdump.sh restore [profile]"
+            echo
+            exit 1
+        fi
+    }
 
-        # ----- start service specific script
-        h2 "START SCRIPT FOR ${SERVICENAME} - $BACKUP_SCRIPT"
 
-        _j_runHooks "200-before-db-service"
-        . $BACKUP_SCRIPT $mode
-        test $rc -gt 0 && j_notify "db ${SERVICENAME}" "$BACKUP_SCRIPT $mode was finished with rc=$rc" $rc
-        _j_runHooks "230-after-db-service" "$rc"
+    # ------------------------------------------------------------
+    # guess name of the database file
+    # param  string  filename of db dump; can be full path or not
+    function guessDB(){
+        dumpfile=$1
+  
+        # the metafile is written in sqlite backup to store full path
+        metafile=${BACKUP_TARGETDIR}/${dumpfile}.meta
+        if [ -f $metafile ]; then
+            grep "^/" "$metafile" || grep "^  File: " "$metafile" | cut -c 9-
+        else
+            sBasename=$(basename $1)
+            sDb=$(echo ${sBasename} | sed "s#__[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9].*##g" | sed "s#\.couchdbdump\.gz##g" )
+            #                                   ^                                                                   ^
+            #        timestamp in backup file __/                               for couchdb2 restore from archive __/
+            if [ -z $sDb ]; then
+                color.echo error "ERROR: db name was not detected from file $1"
+                exit 1
+            fi
+            echo $sDb
+        fi
+    }
 
-        # ----- post jobs: cleanup
-        cleanup_backup_target
-        show_info_backup_target
+    # ------------------------------------------------------------
+    # show a selection + a prompt and read the input
+    # - If the selection is just 1 line it will be returned
+    # - If the user presses just return the script will exit
+    # param  string  selection of items to select from
+    # param  string  prompt to show
+    function showSelectAndInput(){
+        local _selection="$1"
+        local _prompt="$2"
+    
+        local _lines
+        typeset -i _lines; _lines=$( grep -c "." <<< "$_selection" )
+    
+        case $_lines in
+            0)
+                color.echo error "ERROR: No data found for a selection. Aborting."
+                echo
+                exit 1
+                ;;
+            1) 
+                echo "INFO: No interaction on a single choice. Using '$_selection'"
+                LASTINPUT="$_selection"
+                return 0
+                ;;
+            *)
+            echo "$_selection"
+            color.print input "${_prompt} >"
+            read -r LASTINPUT
+            if [ -z "$LASTINPUT" ]; then
+                echo "No input given. Aborting."
+                exit 1
+            fi
+            ;;
+        esac  
+    }
 
-      done
-      ;;
+    # ------------------------------------------------------------
+    # read .meta file (that contains output of stats) and restore last owner and file permissions
+    # param  string  filename of db dump
+    # param  string  restored database file
+    function restorePermissions(){
+        local sMyMeta="${1}.meta"
+        local sTargetfile="$2"
+        if [ -f "${sMyMeta}" ]; then
+
+            # Access: (0674/-rw-rwxr--)  Uid: ( 1000/    axel)   Gid: ( 1000/    axel)
+            #          ^                                 ^                       ^
+            #          _sPerm                            _sUser                  _sGroup
+            local _sPerm=$(  grep "^Access: (" "${sMyMeta}" | cut -f 2 -d '(' | cut -f 1 -d '/')
+            if [ -n "$_sPerm" ]; then
+
+                local _sUser=$(  grep "^Access: (" "${sMyMeta}" | cut -f 3 -d '(' | cut -f 1 -d '/' | tr -d ' ')
+                local _sGroup=$( grep "^Access: (" "${sMyMeta}" | cut -f 4 -d '(' | cut -f 1 -d '/' | tr -d ' ')
+
+                echo -n "Restoring file owner $_sUser:$_sGroup and permissions $_sPerm ... "
+                chown "$_sUser:$_sGroup" "${sTargetfile}" && chmod "$_sPerm" "${sTargetfile}"
+                fetchrc
+            fi
+        fi
+    }
 
     # ------------------------------------------------------------
-    restore)
+    # show help
+    # ------------------------------------------------------------
+    function showhelp(){
+        local _self
+        _self=$( basename "$0" )
+        cat <<EOH
+
+LOCALDUMP detects existing local databases and dumps them locally.
+It is included in the backup.sh to dump all before a file backup will store 
+them. It can be started seperately for manual database backups or for restore.
+
+SYNTAX:
+    $_self [OPTIONS] <operation> <profile [more_profiles]>
+
+OPTIONS:
+    -h|--help   show this help
+
+PARAMETERS:"
+    operation   - one of check|backup|restore; optional parameter
+                      backup   dump all databases/ schemes of a given service
+                      check    show info only if the service is available
+                      restore  import a dump into same or new database
+                               Without a filename it starts an interactive mode
+    profile     - name of database profiles
+                  You get a list of all available services without parameter
+                  Use ALL for bulk command
+    file        - filename of db dump to restore to origin database scheme
+
+EXAMPLES:
+    $_self backup
+    $_self backup ALL
+                 Backup all databases of all found services
+    $_self backup mysql
+                 Backup all Mysql databases.
+
+    $_self restore
+                 Start interactive restore of a database of any service.
+    $_self restore sqlite
+                 Start interactive restore of an sqlite database.
+    $_self restore <file-to-restore> [<database-name>]
+                 Restore a given dump file to the origin database scheme or
+                 to a new/ other database with the given name.
+
+EOH
+    }
 
-      h1 "RESTORE DATABASE"
-      if [ -z $2 ]; then
 
-        # ----- file selection
+# ----------------------------------------------------------------------
+# INIT
+# ----------------------------------------------------------------------
 
-        h2 "select database"
-        listBackupedDBs
-        color input
-        echo -n "name of db to restore >"
-        color reset
-        read fileprefix
+    while [[ "$#" -gt 0 ]]; do case $1 in
+        -h|--help)      showhelp; exit 0;;
+        *) if grep "^-" <<< "$1" >/dev/null ; then
+                echo; color.echo error "ERROR: Unknown parameter: $1"; echo; showhelp; exit 2
+            fi
+            break;
+            ;;
+    esac; done
+
+    mode=""
+    case "$1" in
+        backup|check|restore|shell)
+            mode=$1
+            shift 1
+            ;;
+    esac
+
+    if [ -z "$mode" ]; then
+        color.echo error "ERROR: missing parameter for operation."
         echo
-
-        h2 "select a specific dump for that database"
-        listBackupedDBs $fileprefix
-        color input
-        echo -n "backupset to import >"
-        color reset
-        read dbfile
+        showhelp
         echo
-
-        color input
-        sTargetDb=`guessDB ${dbfile}`
-        echo -n "new database name [$sTargetDb] >"
-        color reset
-        read sTargetDb
-        if [ -z $sTargetDb ]; then
-          sTargetDb=`guessDB ${dbfile}`
-        fi
+        echo "Hint: On this machine working profiles:"
+        get_database_profiles | nl
         echo
+        exit 1
+    fi
 
-        sDumpfile="${BACKUP_TARGETDIR}/${dbfile}"
-      else
-        sDumpfile=$2
-        sTargetDb=$3
-      fi
-      shift 2
-
-      # ----- start restore
-
-      if [ ! -f "${sDumpfile}" ]; then
-        color error
-        echo ERROR: ${sDumpfile} is not a file
-        color reset
-        rc=$rc+1
-      else
-
-        . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}"
-        if [ $? -ne 0 -o $rc -ne 0 ]; then
-          color error
-          echo ERROR: $mode failed. See ouput above. :-/
-          color reset
-        else
-          color ok
-          echo OK, $mode was successful.
-          color reset
-        fi
+    # ----- init vars
+    BACKUP_BASEDIR=$(_j_getvar "${JOBFILE}" "dir-localdumps")
 
-      fi
-      ;;
-    # ------------------------------------------------------------
-    shell)
+    # check
+    if [ -z "$BACKUP_BASEDIR" ]; then
+        color.echo error "ERROR: missing config for backup target."
+        echo There must be an entry dir-localdumps in ${JOBFILE}
+        exit 1
+    fi
+    ARCHIVE_BASEDIR=$(_j_getvar "${JOBFILE}" dir-dbarchive)
 
-      export BACKUP_TARGETDIR
-      . $BACKUP_SCRIPT
-      ( 
-        mycmd=
-        echo
-        echo "Starting interactive shell..."
-        echo
-        echo "STATUS: STILL ALPHA as long existing db plugins are not rewritten."
-        echo
-        echo "INFO: Try ${SERVICENAME}.help to see database specific commands."
-        echo "INFO: Type exit and return to leave the shell."
-        echo
-        while [ ! "$mycmd" = "exit" ]; do
-          echo -n "[${SERVICENAME}]"
-          color input
-          echo -n " $( pwd )"
-          color reset
-          echo -n " % "
-          read -r mycmd
-          if [ ! "$mycmd" = "exit" ];then
-            color cmd
-            eval $mycmd
-            color reset
-          fi
-        done
-      )
-      ;;
-  esac
+    BACKUP_PLUGINDIR=$(dirname $0)/plugins/localdump
+    DBD_BASEDIR=$BACKUP_PLUGINDIR/profiles
+
+    BACKUP_KEEP_DAYS=$(_j_getvar ${JOBFILE} "keep-days")
 
-  echo _______________________________________________________________________________
-  echo STATUS $0 exit with final returncode rc=$rc
-  exit $rc
+    if [ $BACKUP_KEEP_DAYS -eq 0 ]; then
+        BACKUP_KEEP_DAYS=7
+    fi
+    BACKUP_DATE=$(/bin/date +%Y%m%d-%H%M)
 
+    # ----- checks
+
+    # . /usr/local/bin/inc_cronfunctions.sh
+    j_requireUser "root"
+
+    h1 $(date) IML BACKUP :: LOCALDUMP
+
+
+    export SERVICENAME=$1
+    # BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${SERVICENAME}
+    # BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
+  
+    case "$mode" in
+        # ------------------------------------------------------------
+        check)
+            DBD_DEBUG=1
+            for PROFILENAME in $(dbdetect.getConfigs)
+            do
+                echo "----- $PROFILENAME"
+                dbdetect.exists "${PROFILENAME}"
+                echo
+            done
+            # . $BACKUP_SCRIPT $mode
+            ;;
+      # ------------------------------------------------------------
+      backup)
+        if [ "$1" = "ALL" ] || [ -z "$1" ]; then
+  
+            profiles2run=$(get_database_profiles)
+            echo "INFO: Calling local backup scripts for all active profiles"
+            echo "$profiles2run" | nl
+        else
+            profiles2run=$*
+            echo "INFO: I try to dump the profiles you gave as parameter: $profiles2run"
+            echo
+        fi
+  
+        iProfilesFound=$( grep -c . <<< "$profiles2run" )
+        test "$iProfilesFound" -eq "0" && echo "INFO: No match - no database dumps needed."
+        typeset -i iProfileCounter=0
+
+        # ----- GO
+        # PROFILENAME    mysql_localhost_13306
+        # SERVICENAME    mysql
+        #
+        for PROFILENAME in $profiles2run
+        do
+  
+            iProfileCounter+=1
+
+            if dbdetect.setProfile "${PROFILENAME}"; then
+                h2 "START PROFILE $iProfileCounter of $iProfilesFound [${PROFILENAME}]"
+  
+                SERVICENAME=$( dbdetect.getType "$PROFILENAME" )
+                BACKUP_PARAMS=$( dbdetect.getParams )
+  
+                BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME}
+                ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME}
+                BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
+    
+  
+                # ------ set env
+                # echo "BACKUP_PARAMS = $BACKUP_PARAMS"
+                # dbdetect.setenv
+                eval $( dbdetect.setenv )
+  
+                _j_runHooks "200-before-db-service"
+  
+                h3 "BACKUP [${PROFILENAME}] -> ${SERVICENAME}"
+                . $BACKUP_SCRIPT $mode
+  
+                test $rc -gt 0 && j_notify "db ${SERVICENAME}" "$BACKUP_SCRIPT $mode was finished with rc=$rc" $rc
+                _j_runHooks "230-after-db-service" "$rc"
+  
+                # ------ unset env
+                eval $( dbdetect.unssetenv )
+  
+                # ----- post jobs: cleanup
+                cleanup_backup_target
+                show_info_backup_target
+    
+            else
+  
+                echo "SKIP: profile $iProfileCounter of $iProfilesFound '$PROFILENAME' "
+  
+                # see why it is not active
+                DBD_DEBUG=1; dbdetect.setProfile "${PROFILENAME}"; echo; DBD_DEBUG=0
+  
+            fi
+  
+            # just to have it in the output
+            dbdetect.validate
+  
+        done
+        ;;
+  
+      # ------------------------------------------------------------
+      restore)
+  
+        h1 "RESTORE DATABASE"
+  
+        if ! listBackupedServices | grep -q . ; then
+            color.echo error "ERROR: No database dump was found in [${BACKUP_BASEDIR}] nor [${ARCHIVE_BASEDIR}]."
+            exit 1
+        fi
+  
+        if [ -z $1 ] || [ ! -f "$1" ]; then
+  
+            parService="$1"
+  
+          # ----- interactive selections
+  
+            h2 "Select profile that has a dump"
+  
+            if [ -z "${parService}" ]; then
+                showSelectAndInput "$( listBackupedServices )" "Restore for profile name"
+                parService="$LASTINPUT"
+            else
+                echo "Taken from command line: $parService"
+            fi
+  
+            # ----- check if profile exists
+            if ! dbdetect.setProfile "${parService}"; then
+                color.echo error "ERROR: profile [${parService}] is not known here (or database service is stopped)."
+                echo
+                exit 1
+            fi
+  
+            # ----- check if dump exists in archive and in backup
+            if [ -d "${BACKUP_BASEDIR}/${parService}" ] && [ -d "${ARCHIVE_BASEDIR}/${parService}" ]; then
+                echo
+                showSelectAndInput "$(echo "${BACKUP_BASEDIR}"; echo "${ARCHIVE_BASEDIR}")" "Select a source directory"
+                BACKUP_BASEDIR="$LASTINPUT"
+            else
+                # just one test needed because BACKUP_BASEDIR is BACKUP_BASEDIR
+                test -d "${ARCHIVE_BASEDIR}/${parService}" && BACKUP_BASEDIR="${ARCHIVE_BASEDIR}"
+            fi
+  
+            # ----- check if target dir with profile exists
+            if [ ! -d "${BACKUP_BASEDIR}/${parService}" ]; then
+                color.echo error "ERROR: Directory does not exist '${BACKUP_BASEDIR}/${parService}'."
+                exit 1
+            fi
+  
+            BACKUP_TARGETDIR="${BACKUP_BASEDIR}/${parService}"
+  
+            h2 "Select a database schema"
+            showSelectAndInput "$(listBackupedDBs)" "Name of database to restore"
+            fileprefix="$LASTINPUT"
+            echo
+  
+            h2 "Select a specific dump for that database"
+            showSelectAndInput "$(listBackupedDBs $fileprefix)" "Backupset to import"
+            dbfile="$LASTINPUT"
+
+            # if there is a single dump in backup folder: 
+            # '-rw-r--r-- 1 root root 481 Mar 13 12:27 ahcrawler__20240313-1227.sql.gz'
+            # --> take the last part behind the last space to get a filename
+            grep "^-[rwxsSt\-]* " <<< "$LASTINPUT" && dbfile="$(rev <<< \'"$LASTINPUT"\' | cut -d ' ' -f 1 | rev)"
+            echo
+  
+            sTargetDb=$(guessDB ${dbfile})
+            color.print input "New database name [$sTargetDb] >"
+            read -r sTargetDb
+            if [ -z $sTargetDb ]; then
+                sTargetDb=$(guessDB ${dbfile})
+            fi
+            echo
+  
+            sDumpfile="${BACKUP_TARGETDIR}/${dbfile}"
+        else
+            sDumpfile=$1
+            sTargetDb=$2
+        fi
+        shift 2
+  
+        # ----- start restore
+  
+        if [ ! -f "${sDumpfile}" ]; then
+            color.echo error "ERROR: [${sDumpfile}] is not a file"
+            rc=$rc+1
+        else
+  
+            PROFILENAME="${sDumpfile//${BACKUP_BASEDIR}/}"
+            PROFILENAME="$( echo $PROFILENAME | sed "s,^/*,," | cut -f 1 -d '/')"
+  
+            if dbdetect.setProfile "${PROFILENAME}"; then
+  
+                SERVICENAME=$( dbdetect.getType "$PROFILENAME" )
+
+                BACKUP_TARGETDIR=${BACKUP_BASEDIR}/${PROFILENAME}
+                BACKUP_SCRIPT=$( get_service_script ${SERVICENAME} )
+                ARCHIVE_DIR=${ARCHIVE_BASEDIR}/${PROFILENAME}
+  
+                BACKUP_PARAMS=$( dbdetect.getParams )
+                eval $( dbdetect.setenv )
+                . $BACKUP_SCRIPT $mode "${sDumpfile}" "${sTargetDb}"
+  
+                if [ $? -ne 0 -o $rc -ne 0 ]; then
+                    color.echo error "ERROR: $mode failed. See ouput above. :-/"
+                else
+                    color.echo ok "OK, $mode was successful."
+                fi
+  
+                # ------ unset env
+                eval $( dbdetect.unssetenv )
+            else
+                color.echo error "ERROR: Profile $PROFILENAME was detected but its database service is not available."
+            fi
+  
+        fi
+        ;;
+        # ------------------------------------------------------------
+        # shell)
+    
+        #   export BACKUP_TARGETDIR
+        #   . $BACKUP_SCRIPT
+        #   ( 
+        #     mycmd=
+        #     echo
+        #     echo "Starting interactive shell..."
+        #     echo
+        #     echo "STATUS: STILL ALPHA as long existing db plugins are not rewritten."
+        #     echo
+        #     echo "INFO: Try ${SERVICENAME}.help to see database specific commands."
+        #     echo "INFO: Type exit and return to leave the shell."
+        #     echo
+        #     while [ ! "$mycmd" = "exit" ]; do
+        #       echo -n "[${SERVICENAME}]"
+        #       color.print input " $( pwd )"
+        #       echo -n " % "
+        #       read -r mycmd
+        #       if [ ! "$mycmd" = "exit" ];then
+        #         color.preset cmd
+        #         eval $mycmd
+        #         color.reset
+        #       fi
+        #     done
+        #   )
+        #   ;;
+        
+        # ----- start restore
+        *)
+            color.echo error "ERROR: unknown command [$mode]"
+            ;;
+    esac
+  
+    echo _______________________________________________________________________________
+    echo STATUS $0 exit with final returncode rc=$rc
+    exit $rc
+    
 # ----------------------------------------------------------------------
+  
\ No newline at end of file
diff --git a/plugins/localdump/couchdb2.sh b/plugins/localdump/couchdb2.sh
index 2976f294693cb6a9237acbd08be8223a3d0512f7..653efcef62f35cf12e663843294d137544bedd91 100755
--- a/plugins/localdump/couchdb2.sh
+++ b/plugins/localdump/couchdb2.sh
@@ -48,7 +48,8 @@ CFGDIR=~/.iml_backup/couchdb2
 # UNUSED
 # dirPythonPackages=/usr/lib/python2.7/site-packages
 
-ARCHIVE_DIR=$(_j_getvar "${JOBFILE}" dir-dbarchive)/couchdb2
+# now set in localdump.sh
+# ARCHIVE_DIR=$(_j_getvar "${JOBFILE}" dir-dbarchive)/couchdb2
 
 # --------------------------------------------------------------------------------
 # FUNCTIONS
@@ -59,49 +60,42 @@ ARCHIVE_DIR=$(_j_getvar "${JOBFILE}" dir-dbarchive)/couchdb2
 # param  string  relative url, i.e. _all_dbs or _stats
 # param  string  optional: data for POST|PUT requests
 function _couchapi(){
-  local method=$1
-  local apiurl=$2
-  # local outfile=$3
-  local data=$3
-
-  sParams=
-  # sParams="$sParams -u ${couchdbuser}:${couchdbpw}"
-  sParams="$sParams -X ${method}"
-  sParams="$sParams ${COUCH_URL}${apiurl}"
-  # if [ ! -z "$outfile" ]; then
-  #   sParams="$sParams -o ${outfile}"
-  # fi
-  if [ -n "$data" ]; then
-    sParams="$sParams -d ${data}"
-  fi
-  curl $sParams 2>/dev/null
+    local method=$1
+    local apiurl=$2
+    # local outfile=$3
+    local data=$3
+
+    sParams=
+    # sParams="$sParams -u ${couchdbuser}:${couchdbpw}"
+    sParams="$sParams -X ${method}"
+    sParams="$sParams ${COUCH_URL}${apiurl}"
+    # if [ ! -z "$outfile" ]; then
+    #   sParams="$sParams -o ${outfile}"
+    # fi
+    if [ -n "$data" ]; then
+        sParams="$sParams -d ${data}"
+    fi
+    curl $sParams 2>/dev/null
 }
 
 function _getDblist(){
-   _couchapi GET _all_dbs | sed 's#\"#\n#g' | grep -Ev "^(\[|\,|\])$" | grep -v _replicator | grep -v _global_changes
+    _couchapi GET _all_dbs | sed 's#\"#\n#g' | grep -Ev "^(\[|\,|\])$" | grep -v _replicator | grep -v _global_changes
 }
 
-# UNUSED
-# get value update_seq of given couchdb name
-# function _getDbSeq(){
-#   # _couchapi GET $1 | sed 's#,\"#\n"#g' | egrep -v "^(\[|\,|\])$" | grep update_seq | cut -f 4 -d '"'
-#   _couchapi GET "$1" | sed 's#,\"#\n"#g' | grep -Ev "^(\[|\,|\])$" | grep update_seq | cut -f 4 -d '"' | cut -f 1 -d '-'
-# }
-
-# active curl prozesses
+# get count of active curl prozesses
 function curlCount(){
-  ps -ef | grep -v grep | grep "curl" | wc -l
+    ps -ef | grep -v grep | grep "curl" | wc -l
 }
 
 # wait until min N curl prozesses exist
 function wait4curlcount(){
-  typeset -i local iContinue
-  typeset -i local iCount
+    typeset -i local iContinue
+    typeset -i local iCount
 
-  iContinue=${1:-0}
-  iCount=$( curlCount )
+    iContinue=${1:-0}
+    iCount=$( curlCount )
 
-  test $iCount -gt $iContinue && wait4curlcount $iContinue
+    test $iCount -gt $iContinue && wait4curlcount $iContinue
 }
 
 # optimized curl requests to get metadata from all databases
@@ -112,70 +106,35 @@ function wait4curlcount(){
 # param  integer  iParallel   count of curl processes
 # param  string   dblistfile  path+file to list of database
 function reqCombined(){
-  typeset -i local iChunksize; iChunksize=$1
-  typeset -i local iParallel;  iParallel=$2
-  local dblistfile;            dblistfile="$3"
-
-  typeset -i iCounter=0
-  cmdline=
-
-  for mydb in $( cat $dblistfile )
-  do 
+    local iChunksize; typeset -i iChunksize; iChunksize=$1
+    local iParallel;  typeset -i iParallel;  iParallel=$2
+    local dblistfile;                        dblistfile="$3"
 
-      iCounter+=1
-      test -n "$cmdline" && cmdline+=" -: "
-      cmdline+="${COUCH_URL}${mydb} "
+    typeset -i iCounter=0
+    cmdline=
 
-      if [ $iCounter -ge $iChunksize ]; then
+    for mydb in $( cat $dblistfile )
+    do 
 
-          curl -s $cmdline &
-
-          # wait untile count of curl proecses is lower maximum
-          wait4curlcount $iParallel
-
-          iCounter=0
-          cmdline=
-      fi
-
-  done
-  test -n "${cmdline}" && curl -s $cmdline &
-
-  wait4curlcount 0
-}
+        iCounter+=1
+        test -n "$cmdline" && cmdline+=" -: "
+        cmdline+="${COUCH_URL}${mydb} "
 
+        if [ $iCounter -ge $iChunksize ]; then
 
+            curl -s $cmdline &
 
-# ---------- CONFIG/ INSTANCES
+            # wait untile count of curl proecses is lower maximum
+            wait4curlcount $iParallel
 
-# get valid configured instances
-function getInstances(){
- for mycfg in $(ls -1 ${CFGDIR}/*${1}*.config)
- do
-   if . "$mycfg"; then
-     echo $(basename "${mycfg}" | cut -f 1 -d ".")
-   fi
- done
-}
+            iCounter=0
+            cmdline=
+        fi
 
+    done
+    test -n "${cmdline}" && curl -s $cmdline &
 
-# load the config of an existing instance
-# see getInstances to get valid names
-# param  string  name of the instance to load
-function loadInstance(){
-  COUCH_URL=
-  if ! . "${CFGDIR}/${1}.config"; then
-    color error
-    echo ERROR: invalid instance: $1 - the config file cannot be sourced
-    color reset
-    exit 1
-  fi
-  if [ -z "${COUCH_URL}" ]; then
-    color error
-    echo "ERROR: invalid instance: $1 - the config file has no COUCH_URL"
-    color reset
-    exit 1
-  fi
-
+    wait4curlcount 0
 }
 
 
@@ -184,208 +143,195 @@ function loadInstance(){
 # backup with loop over instances
 # param 1  string  globbing filter to config files
 function doBackup(){
-  # for mycfg in `ls -1 ~/.iml_backup/couchdb/*.config`
-  for COUCHDB_INSTANCE in $(getInstances $1)
-  do
-    loadInstance "$COUCHDB_INSTANCE"
-
-      echo "--- instance: $COUCHDB_INSTANCE"
-      if curl --head -X GET "$COUCH_URL" 2>/dev/null | grep "^HTTP.* 200 "; then
-        echo OK, connected.
-        sleep 1
-        _doBackupOfSingleInstance
-
-      else
-        rc=$rc+1
-        color error
-        echo "ERROR: couch DB instance is not available or canot be accessed with these credentials in config file"
-        # repeat curl to show the error message
-        curl -X GET "$COUCH_URL"
-        color reset
-      fi
+
+    echo "--- instance: $PROFILENAME"
+    if curl --head -X GET "$COUCH_URL" 2>/dev/null | grep "^HTTP.* 200 "; then
+    echo OK, connected.
+    sleep 1
+    _doBackupOfSingleInstance
+
+    else
+    rc=$rc+1
+    color error
+    echo "ERROR: couch DB instance is not available or canot be accessed with these credentials in config file"
+    # repeat curl to show the error message
+    curl -X GET "$COUCH_URL"
+    color reset
+    fi
 
     echo
     echo "--- $(date) done."
     echo
-  done
 }
 
 # make backup of all databases in a couchdb instance
 # global: COUCH_URL
-# global: COUCHDB_INSTANCE
+# global: PROFILENAME
 function _doBackupOfSingleInstance(){
 
-  create_targetdir
-  local ARCHIVE_DIR2="${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/deleted_databases"
-  for _dir in "${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}" "${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq" "${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/security" \
-              "${ARCHIVE_DIR2}"                         "${ARCHIVE_DIR2}/seq"                    "${ARCHIVE_DIR2}/security"
-  do
+    create_targetdir
+    local ARCHIVE_DIR2="${ARCHIVE_DIR}/deleted_databases"
+    for _dir in "${ARCHIVE_DIR}"      "${ARCHIVE_DIR}/seq"  "${ARCHIVE_DIR}/security" \
+                "${ARCHIVE_DIR2}"     "${ARCHIVE_DIR2}/seq" "${ARCHIVE_DIR2}/security"
+    do
     test -d "$_dir" || (echo "creating $_dir" ; mkdir -p "$_dir" )
-  done
-
-  local iChunksize=100
-  local iParallel=6
-
-  local dblistfile
-  local sSequenceCurrent
-  local sSequenceLast
-  local OUTFILE
-  local ARCHIVFILE
-  local SEQFILE
-  local SECURITYFILE
-  typeset -i local iTsStart
-  typeset -i local iTsTotal
-  typeset -i local iDbPerSec
-
-  dblistfile="/tmp/dblist_${COUCHDB_INSTANCE}"
-
-  # this is just a caching file of the sequence id of the last backup and can be safely deleted.
-  seqfile="${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq/all_seqids_of_last_backups_cache.txt"
-
-  echo "--- $( date ) Get list of all databases"
-  _getDblist >"${dblistfile}"
-
-  typeset -i iDbTotal=$( wc -l < "$dblistfile")
-  typeset -i iDb=0        # counter for number of database in the loop
-  typeset -i iDbCount=0   # counter for backed up databases
-  echo "${COUCHDB_INSTANCE} has $iDbTotal databases"
-
-  # detect deleted databases: 
-  echo
-  echo "--- $( date ) MOVE deleted databases "
-  echo "... into ${ARCHIVE_DIR2}"
-  echo
-  for dumpfile in $( find "${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/" -maxdepth 1 -type f -name "*.couchdbdump.gz" )
-  do
-      # extract database name: get basename and cut extension
-      # dbname=$( basename $dumpfile | sed "s#\.couchdbdump\.gz##g" )
-      dbname=${dumpfile##*/}
-      dbname=${dbname/%.couchdbdump.gz//}
-      dbname=${dbname/\/}
-
-      if ! grep "^${dbname}" "${dblistfile}"  >/dev/null; then
-              SEQFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq/__seq__${dbname}
-              SECURITYFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/security/__security__${dbname}.json
-              echo "DELETED $dbname ... $( ls -l ${dumpfile} | cut -f 5- -d ' ' )"
-              mv "${dumpfile}"     "${ARCHIVE_DIR2}"
-              mv "${SEQFILE}"      "${ARCHIVE_DIR2}/seq/"
-              mv "${SECURITYFILE}" "${ARCHIVE_DIR2}/security/"
-      fi
-  done
-
-  echo
-  echo "--- $( date ) DUMP databases"
-  echo "    of instance ${COUCHDB_INSTANCE}: $iDbTotal databases"
-  echo "    TO BACKUP ${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}"
-  echo "      ARCHIVE ${ARCHIVE_DIR}/${COUCHDB_INSTANCE}"
-  echo
-
-  echo "----- $( date ) - Get database meta infos ... max $iParallel parralel curl requests sending $iChunksize database urls per process"
-  seq=$( reqCombined $iChunksize $iParallel "$dblistfile" | jq -r ' [ .db_name, .update_seq ] | @csv ' | tr -d '"' | tr ',' ' '  | awk '{ sub(/-.*/, "", $2 ); print $1 "," $2  }' )
-  #                                                                                             ^        ^           ^                    ^                              ^
-  #                                                     db_name + update_seq in a single line --+        |           |                    |   and back: space to comma --+
-  #                                                                                      delete quotes --+           |                    +-- remove string after first minus char
-  #                                                                                comma to space (for awk values) --+
-  # the result is ... echo "$seq" | head -3
-  # _users,7688
-  # candidate-00649860284626638ac6fd12bf000df5,40
-  # candidate-04561cddbd0fa305714b48a57929d8b4,3
-
-  echo "----- $( date ) - reading current sequence ids..."
-  declare -A aSeq
-  for line in $( echo "$seq" )
-  do
-    IFS="," read -r db seqid <<< "$line"
-    aSeq+=([$db]=$seqid)
-  done
-
-  echo "----- $( date ) - reading sequence ids of last backup..."
-  declare -A aSeqBackup
-  for line in $( cat "${seqfile}" 2>/dev/null )
-  do
-    IFS="," read -r db seqid <<< "$line"
-    aSeqBackup+=([$db]=$seqid)
-  done
-
-  iTsStart=$( date +%s)
-  for dbname in $( cat "$dblistfile" )
-  do
-    iDb+=1
-    echo -n "----- $(date) ${COUCHDB_INSTANCE} -- $iDb of $iDbTotal - ${dbname} - "
-
-    # set later .. OUTFILE=${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}/$(get_outfile "${dbname}").couchdbdump
-    ARCHIVFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/${dbname}.couchdbdump.gz
-    SEQFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/seq/__seq__${dbname}
-    SECURITYFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/security/__security__${dbname}.json
-
-    # sSequenceCurrent=$(_getDbSeq "${dbname}")
-    sSequenceCurrent="${aSeq[$dbname]}"
-    
-    # sSequenceLast=$(cat "${SEQFILE}" 2>/dev/null | cut -f 1 -d '-')
-    sSequenceLast="${aSeqBackup[$dbname]:-$(cat ${SEQFILE} 2>/dev/null | cut -f 1 -d '-')}"
-
-    aSeqBackup[${dbname}]=$sSequenceLast
-    if [ "${sSequenceCurrent}" = "${sSequenceLast}" ] && [ -f "$ARCHIVFILE" ]; then
-      echo "SKIP: still on sequence ${sSequenceLast}"
-
-      # add security file for already existing databases 
-      test -f  "${SECURITYFILE}" || (
-        echo "INFO: creating missing security file ${SECURITYFILE}"
-        _couchapi GET "${dbname}/_security" > "${SECURITYFILE}"
-      )
-      
-    else
-      OUTFILE=${BACKUP_TARGETDIR}/${COUCHDB_INSTANCE}/$(get_outfile "${dbname}").couchdbdump
-      if [ -z "$sSequenceCurrent" ]; then
-        echo "WARNING: unable to fetch current sequence ID - maybe the database was deleted."
-      else
-        echo
-        echo "update_seq --+-- current [${sSequenceCurrent}]" 
-        echo "             +-- backup  [${sSequenceLast}]"
-        echo -n "Need to backup ... "
+    done
 
-        couchbackup --db "${dbname}" >"${OUTFILE}".progress 2>/dev/null && mv "${OUTFILE}".progress "${OUTFILE}"
-        fetchrc
+    local iChunksize=100
+    local iParallel=6
 
-        # $myrc is last returncode - set in fetchrc
-        if [ $myrc -eq 0 ]; then
-          echo -n "gzip ... "
-          compress_file "$OUTFILE"
-          fetchrc
-          if [ $myrc -eq 0 ]; then
-            iDbCount+=1
-
-            aSeqBackup[${dbname}]=${sSequenceCurrent}
-            # flushing cached information
-            rm -f "${seqfile}" 2>/dev/null
-
-            cp "${OUTFILE}"* "${ARCHIVFILE}"                             \
-              && echo "${sSequenceCurrent}">"${SEQFILE}"                 \
-              && _couchapi GET "${dbname}/_security" > "${SECURITYFILE}"
-            ls -l "${ARCHIVFILE}" "${SEQFILE}" "${SECURITYFILE}"
-          fi
-        else
-          echo "ERROR occured while dumping - abort"
+    local dblistfile
+    local sSequenceCurrent
+    local sSequenceLast
+    local OUTFILE
+    local ARCHIVFILE
+    local SEQFILE
+    local SECURITYFILE
+    local iTsStart; typeset -i iTsStart
+    local iTsTotal; typeset -i iTsTotal
+    local iDbPerSec; typeset -i iDbPerSec
+
+    dblistfile="/tmp/dblist_${PROFILENAME}.txt"
+
+    # this is just a caching file of the sequence id of the last backup and can be safely deleted.
+    seqfile="${ARCHIVE_DIR}/seq/all_seqids_of_last_backups_cache.txt"
+
+    echo "--- $( date +%H:%M:%S ) Get list of all databases"
+    _getDblist >"${dblistfile}"
+
+    typeset -i iDbTotal; iDbTotal=$( wc -l < "$dblistfile")
+    typeset -i iDb=0        # counter for number of database in the loop
+    typeset -i iDbCount=0   # counter for backed up databases
+    echo "${PROFILENAME} has $iDbTotal databases"
+
+    # detect deleted databases: 
+    echo
+    echo "--- $( date +%H:%M:%S ) MOVE deleted databases "
+    echo "... into ${ARCHIVE_DIR2}"
+    echo
+    for dumpfile in $( find "${ARCHIVE_DIR}/" -maxdepth 1 -type f -name "*.couchdbdump.gz" )
+    do
+        # extract database name: get basename and cut extension
+        # dbname=$( basename $dumpfile | sed "s#\.couchdbdump\.gz##g" )
+        dbname=${dumpfile##*/}
+        dbname=${dbname/%.couchdbdump.gz//}
+        dbname=${dbname/\/}
+
+        if ! grep "^${dbname}" "${dblistfile}"  >/dev/null; then
+            SEQFILE=${ARCHIVE_DIR}/seq/__seq__${dbname}
+            SECURITYFILE=${ARCHIVE_DIR}/security/__security__${dbname}.json
+            echo "DELETED $dbname ... $( ls -l ${dumpfile} | cut -f 5- -d ' ' )"
+            mv "${dumpfile}"     "${ARCHIVE_DIR2}"
+            mv "${SEQFILE}"      "${ARCHIVE_DIR2}/seq/"
+            mv "${SECURITYFILE}" "${ARCHIVE_DIR2}/security/"
         fi
-        ls -l "$OUTFILE"*
-        echo
-      fi # if [ -z "$sSequenceCurrent" ]; then
-    fi # if [ "${sSequenceCurrent}" = "${sSequenceLast}" ] ...
-  done
-  iTsTotal=$( date +%s)-$iTsStart
-  iDbPerSec=$iDbTotal/$iTsTotal
+    done
+
+    echo
+    echo "--- $( date +%H:%M:%S ) DUMP databases"
+    echo "    of instance ${PROFILENAME}: $iDbTotal databases"
+    echo "    TO BACKUP ${BACKUP_TARGETDIR}"
+    echo "      ARCHIVE ${ARCHIVE_DIR}"
+    echo
 
-  echo "----- $( date ) - writing sequence ids ..."
-  rm -f "${seqfile}" 2>/dev/null
-  for key in "${!aSeqBackup[@]}"; do
-    echo "$key,${aSeqBackup[$key]}" >> "${seqfile}"
-  done
-  ls -l "${seqfile}"
-  echo
+    echo "----- $( date +%H:%M:%S ) - Get database meta infos ... max $iParallel parralel curl requests sending $iChunksize database urls per process"
+    seq=$( reqCombined $iChunksize $iParallel "$dblistfile" | jq -r ' [ .db_name, .update_seq ] | @csv ' | tr -d '"' | tr ',' ' '  | awk '{ sub(/-.*/, "", $2 ); print $1 "," $2  }' )
+    #                                                                                             ^        ^           ^                    ^                              ^
+    #                                                     db_name + update_seq in a single line --+        |           |                    |   and back: space to comma --+
+    #                                                                                      delete quotes --+           |                    +-- remove string after first minus char
+    #                                                                                comma to space (for awk values) --+
+    # the result is database name + comma + sequence id
+    # echo "$seq" | head -3
+    # _users,7688
+    # candidate-00649860284626638ac6fd12bf000df5,40
+    # candidate-04561cddbd0fa305714b48a57929d8b4,3
+
+    echo "----- $( date +%H:%M:%S ) - reading current sequence ids..."
+    declare -A aSeq
+    for line in $( echo "$seq" )
+    do
+        IFS="," read -r db seqid <<< "$line"
+        aSeq+=([$db]=$seqid)
+    done
+
+    echo "----- $( date +%H:%M:%S ) - reading sequence ids of last backup..."
+    declare -A aSeqBackup
+    for line in $( cat "${seqfile}" 2>/dev/null )
+    do
+        IFS="," read -r db seqid <<< "$line"
+        aSeqBackup+=([$db]=$seqid)
+    done
+
+    iTsStart=$( date +%s)
+    for dbname in $( cat "$dblistfile" )
+    do
+        iDb+=1
+        echo -n "----- $( date +%H:%M:%S ) ${PROFILENAME} -- $iDb of $iDbTotal - ${dbname} - "
+
+        ARCHIVFILE=${ARCHIVE_DIR}/${dbname}.couchdbdump.gz
+        SEQFILE=${ARCHIVE_DIR}/seq/__seq__${dbname}
+        SECURITYFILE=${ARCHIVE_DIR}/security/__security__${dbname}.json
+
+        # compare current sequence id with last backup sequence id
+        sSequenceCurrent="${aSeq[$dbname]}"
+        sSequenceLast="${aSeqBackup[$dbname]:-$(cat ${SEQFILE} 2>/dev/null | cut -f 1 -d '-')}"
+        aSeqBackup[${dbname}]=$sSequenceLast
+        if [ "${sSequenceCurrent}" = "${sSequenceLast}" ] && [ -f "$ARCHIVFILE" ]; then
+        echo "SKIP: still on sequence ${sSequenceLast}"      
+        else
+        OUTFILE=${BACKUP_TARGETDIR}/$(get_outfile "${dbname}").couchdbdump
+        if [ -z "$sSequenceCurrent" ]; then
+            echo "WARNING: unable to fetch current sequence ID - maybe the database was deleted."
+        else
+            echo
+            echo "update_seq --+-- current [${sSequenceCurrent}]" 
+            echo "             +-- backup  [${sSequenceLast}]"
+            echo -n "Need to backup ... "
+
+            # TODO
+            couchbackup --db "${dbname}" >"${OUTFILE}".progress 2>/dev/null && mv "${OUTFILE}".progress "${OUTFILE}"
+            fetchrc
+
+            # $myrc is last returncode - set in fetchrc
+            if [ $myrc -eq 0 ]; then
+            echo -n "gzip ... "
+            compress_file "$OUTFILE"
+            fetchrc
+            if [ $myrc -eq 0 ]; then
+                iDbCount+=1
+
+                aSeqBackup[${dbname}]=${sSequenceCurrent}
+                # flushing cached information
+                rm -f "${seqfile}" 2>/dev/null
+
+                cp "${OUTFILE}"* "${ARCHIVFILE}"                             \
+                && echo "${sSequenceCurrent}">"${SEQFILE}"                 \
+                && _couchapi GET "${dbname}/_security" > "${SECURITYFILE}"
+                ls -l "${ARCHIVFILE}" "${SEQFILE}" "${SECURITYFILE}"
+            fi
+            else
+            echo "ERROR occured while dumping - abort"
+            fi
+            ls -l "$OUTFILE"*
+            echo
+        fi # if [ -z "$sSequenceCurrent" ]; then
+        fi # if [ "${sSequenceCurrent}" = "${sSequenceLast}" ] ...
+    done
+    iTsTotal=$( date +%s)-$iTsStart
+    iDbPerSec=$iDbTotal/$iTsTotal
+
+    # cache sequence ids in a file
+    echo "----- $( date +%H:%M:%S ) - writing sequence ids ..."
+    rm -f "${seqfile}" 2>/dev/null
+    for key in "${!aSeqBackup[@]}"; do
+        echo "$key,${aSeqBackup[$key]}" >> "${seqfile}"
+    done
+    ls -l "${seqfile}"
+    echo
 
-  rm -f "$dblistfile"
+    rm -f "$dblistfile"
 
-  echo "__DB__$SERVICENAME backup INFO: ${COUCHDB_INSTANCE} - backed up $iDbCount dbs of $iDbTotal total ... in $iTsTotal sec ($iDbPerSec databases per sec)"
+    echo "__DB__$SERVICENAME backup INFO: ${PROFILENAME} - backed up $iDbCount dbs of $iDbTotal total ... in $iTsTotal sec ($iDbPerSec databases per sec)"
 
 }
 
@@ -412,89 +358,93 @@ function _doBackupOfSingleInstance(){
 # param  string  filename of db dump (full path or relative to BACKUP_TARGETDIR)
 # param  string  optional: target database; default: detect name from import database 
 function restoreByFile(){
-  sMyfile=$1
-  dbname=$2
-
-  bFastMode=0 # 0 = delete db first and import | 1 = create and import (on empty instance only)
+    sMyfile=$1
+    dbname=$2
 
-  echo
-  h2 "analyze dump $sMyfile"
+    bFastMode=0 # 0 = delete db first and import | 1 = create and import (on empty instance only)
 
-  COUCHDB_INSTANCE=$(echo $sMyfile | sed "s#${BACKUP_TARGETDIR}##g" | sed "s#\./##g" | sed "s#^/##g" | cut -f 1 -d "/")
-  echo "detected COUCHDB_INSTANCE   : [${COUCHDB_INSTANCE}]"
-  if [ -z "$COUCHDB_INSTANCE" ]; then
-    echo "ERROR: Name of the instance was not detected."
-    echo "       For couchdb restore you should cd to the ${BACKUP_TARGETDIR} or ${ARCHIVE_DIR}"
-    exit 1
-  fi
+    echo
+    h2 "analyze dump $sMyfile"
+
+    # COUCHDB_INSTANCE=$(echo $sMyfile | sed "s#${BACKUP_TARGETDIR}##g" | sed "s#\./##g" | sed "s#^/##g" | cut -f 1 -d "/")
+    # echo "detected COUCHDB_INSTANCE   : [${COUCHDB_INSTANCE}]"
+    # if [ -z "$COUCHDB_INSTANCE" ]; then
+    #   echo "ERROR: Name of the instance was not detected."
+    #   echo "       For couchdb restore you should cd to the ${BACKUP_TARGETDIR} or ${ARCHIVE_DIR}"
+    #   exit 1
+    # fi
+
+    local _sourceDB="$( guessDB $sMyfile | sed 's#.couchdbdump.gz$##' )"
+    echo "detected source database    : [${_sourceDB}]"
+
+    if [ -z "$dbname" ]; then
+        dbname="$_sourceDB"
+        echo "using the same as target    : [${dbname}]"
+    else
+        echo "using db schema from param 2: [${dbname}]"
+    fi
 
-  local _sourceDB="$( guessDB $sMyfile | sed 's#.couchdbdump.gz$##' )"
-  echo "detected source database    : [${_sourceDB}]"
+    echo
+  
+    if [ $bFastMode -eq 0 ]; then
+        echo connect $couchdbhost on port $couchdbport with user $couchdbuser
+        curl --head -X GET $COUCH_URL 2>/dev/null | grep "^HTTP.* 200 " >/dev/null
+        if [ $? -ne 0 ]; then
+            color error
+            echo ERROR: couch DB instance is not available
+            curl -X GET $COUCH_URL
+            color reset
+            exit 1
+        fi
+        color ok
+        echo OK
+        color reset
+    fi
 
-  if [ -z "$dbname" ]; then
-    dbname="$_sourceDB"
-    echo "using the same as target    : [${dbname}]"
-  else
-    echo "using db schema from param 2: [${dbname}]"
-  fi
+    echo
 
-  echo
+    # _getDblist | grep "^${dbname}$"
+    # if [ $? -eq 0 ]; then
+    #   echo DB exists ... need to drop it first
+    # fi
 
-  loadInstance $COUCHDB_INSTANCE
-  
-  if [ $bFastMode -eq 0 ]; then
-    echo connect $couchdbhost on port $couchdbport with user $couchdbuser
-    curl --head -X GET $COUCH_URL 2>/dev/null | grep "^HTTP.* 200 " >/dev/null
-    if [ $? -ne 0 ]; then
-        color error
-        echo ERROR: couch DB instance is not available
-        curl -X GET $COUCH_URL
+    if [ $bFastMode -eq 0 ]; then
+        h2 deleting database [$dbname] ...
+        color cmd
+        _couchapi DELETE $dbname
+        fetchrc
         color reset
-        exit 1
     fi
-    color ok
-    echo OK
-    color reset
-  fi
 
-  echo
-
-  # _getDblist | grep "^${dbname}$"
-  # if [ $? -eq 0 ]; then
-  #   echo DB exists ... need to drop it first
-  # fi
+    h2 creating database [$dbname] ...
+    color cmd
+    _couchapi PUT $dbname
+    fetchrc
+    color reset
 
-  if [ $bFastMode -eq 0 ]; then
-    h2 deleting database [$dbname] ...
+    h2 import file ...
     color cmd
-    _couchapi DELETE $dbname
+    zcat ${sMyfile} | couchrestore --db $dbname
     fetchrc
     color reset
-  fi
-
-  h2 creating database [$dbname] ...
-  color cmd
-  _couchapi PUT $dbname
-  fetchrc
-  color reset
-
-  h2 import file ...
-  color cmd
-  zcat ${sMyfile} | couchrestore --db $dbname
-  fetchrc
-  color reset
-
-  h2 add security infos ...
-  # todo: this will fail when restoring from "deleted_databases" folder
-  SECURITYFILE=${ARCHIVE_DIR}/${COUCHDB_INSTANCE}/security/__security__${_sourceDB}.json
-  SECDATA="$( cat $SECURITYFILE )"
-  color cmd
-  echo "add security data: $SECDATA"
-  _couchapi PUT "${dbname}/_security" "$SECDATA"
-  fetchrc
-  color reset
-
-  echo
+
+    h2 add security infos ...
+    # todo: this will fail when restoring from "deleted_databases" folder
+    SECURITYFILE="${ARCHIVE_DIR}/security/__security__${_sourceDB}.json"
+    if [ -f "$SECURITYFILE" ]; then
+        SECDATA="$( cat $SECURITYFILE )"
+        color cmd
+        echo "add security data: $SECDATA"
+        _couchapi PUT "${dbname}/_security" "$SECDATA"
+        fetchrc
+        color reset
+    else
+        color warning 
+        echo "WARNING: no security data file was found: $SECURITYFILE"
+        color reset
+    fi
+
+    echo
 
 }
 
@@ -509,50 +459,37 @@ function restoreByFile(){
 # j_requireProcess "couchdb"   1
 
 # --- very specific :-/ ... check available config files
-ls -1 ${CFGDIR}/* >/dev/null 2>&1
-rc=$rc+$?
 
+j_requireBinary  "curl"         1
+j_requireBinary  "couchbackup"  1
+j_requireBinary  "couchrestore" 1
 
-if [ $rc -eq 0 ]; then
-  echo OK: couchdb2 config was found on this system ... checking requirements for backup ...
-
-  j_requireBinary  "curl"         1
-  j_requireBinary  "couchbackup"  1
-  j_requireBinary  "couchrestore" 1
-
-  #ls ${dirPythonPackages}/couchdb/tools/dump.py ${dirPythonPackages}/couchdb/tools/load.py >/dev/null && echo "OK: python couchdb tools were found"
-  #rc=$rc+$?
+#ls ${dirPythonPackages}/couchdb/tools/dump.py ${dirPythonPackages}/couchdb/tools/load.py >/dev/null && echo "OK: python couchdb tools were found"
+#rc=$rc+$?
 
 
-  if [ $rc -eq 0 ]; then
+if [ $rc -eq 0 ]; then
     echo
 
     if [ "$1" = "restore" ]; then
-      echo
-      shift 1
-      restoreByFile $*
+        echo
+        shift 1
+        restoreByFile $*
 
     else
-      shift 1
+        shift 1
 
-      # remove keyword ALL which is used for localdump.sh to loop over all db types
-      test "$1" = "ALL" && shift 1
+        # remove keyword ALL which is used for localdump.sh to loop over all db types
+        test "$1" = "ALL" && shift 1
 
-      doBackup $*
+        doBackup $*
     fi
 
-  else
-    color error
-    echo ERROR: Couchdb is here but I am missing things for the backup :-/
-    color reset
-  fi
-
 else
-  rc=0
-  echo "__DB__$SERVICENAME SKIP: couchdb2 config does not seem to be here"
+    rc=1
+    color.echo error "ERROR: Your Couchdb data cannot be dumped."
 fi
 
-
 echo "__DB__$SERVICENAME INFO: $0 $* [$SERVICENAME] final returncode rc=$rc"
 
 # --------------------------------------------------------------------------------
diff --git a/plugins/localdump/ldap.sh b/plugins/localdump/ldap.sh
index 83ac400006ba581fa08ac8766ca8d06b6f8edf33..1d7e09a689b81eff43458878ed3b615b69a39f4f 100755
--- a/plugins/localdump/ldap.sh
+++ b/plugins/localdump/ldap.sh
@@ -116,7 +116,7 @@ function restoreByFile(){
 j_requireBinary "ldapsearch" 1
 j_requireBinary "slapcat"    1
 
-j_requireProcess "slapd"     1
+# j_requireProcess "slapd"     1
 
 ls $LDAP_CONF_DIR_PATH >/dev/null 2>&1
 
diff --git a/plugins/localdump/mysql.sh b/plugins/localdump/mysql.sh
index 8b1c866a9449942106ac5cc58caedd373b230d65..0dfaebf8d2d8a4067551c24a45976ccdc1bfbb39 100755
--- a/plugins/localdump/mysql.sh
+++ b/plugins/localdump/mysql.sh
@@ -50,12 +50,12 @@ function mysql._check(){
 
   j_requireBinary "mysql"             1
   j_requireBinary "mysqldump"         1
-  j_requireProcess "mysqld|mariadb"   1
+  # j_requireProcess "mysqld|mariadb"   1
 
-  if [ ! -d $SOURCE_DIR ]; then
-    echo "INFO: directory $SOURCE_DIR doees not exist."
-    rc=$rc+1
-  fi
+  # if [ ! -d $SOURCE_DIR ]; then
+  #   echo "INFO: directory $SOURCE_DIR doees not exist."
+  #   rc=$rc+1
+  # fi
 
   # set flag and reset return code
   test $rc -eq 0 && mysql_FOUND=1
@@ -71,7 +71,7 @@ function mysql._check(){
 # param  string  name of the dabase scheme
 function mysql.db.create(){
   local _dbname=$1
-  echo "CREATE DATABASE IF NOT EXISTS \`${_dbname}\`;" | mysql
+  echo "CREATE DATABASE IF NOT EXISTS \`${_dbname}\`;" | mysql ${BACKUP_PARAMS}
   fetchrc >/dev/null
   test $myrc -eq 0 && mysql_COUNT_CREATE+=1
   test $myrc -eq 0 || mysql_COUNT_ERRORS+=1
@@ -89,14 +89,14 @@ function mysql.db.dump(){
   local _dbname=$1
   local _dumpfile=$2
 
-  mysqldump $LD_MYSQL_DUMP_PARAMS --result-file="$_dumpfile" "$_dbname" 2>&1
+  mysqldump ${BACKUP_PARAMS} $LD_MYSQL_DUMP_PARAMS --result-file="$_dumpfile" "$_dbname" 2>&1
   fetchrc >/dev/null
 
   if [ $myrc -eq 0 ]; then
         if ! zgrep -iE "(CREATE|INSERT)" "$_dumpfile" >/dev/null
         then
           typeset -i local _iTables
-          _iTables=$( mysql --skip-column-names --batch -e "use $_dbname; show tables ;" | wc -l )
+          _iTables=$( mysql ${BACKUP_PARAMS} --skip-column-names --batch -e "use $_dbname; show tables ;" | wc -l )
           if [ $_iTables -eq 0 ];
           then
             echo -n "EMPTY DATABASE ... "
@@ -120,7 +120,7 @@ function mysql.db.dump(){
 function mysql.db.import(){
   local _dumpfile=$1
   local _dbname=$2
-  zcat "$_dumpfile" | mysql "${_dbname}"
+  zcat "$_dumpfile" | mysql $BACKUP_PARAMS "${_dbname}"
   fetchrc >/dev/null
   test $myrc -eq 0 && mysql_COUNT_IMPORT+=1
   test $myrc -eq 0 || mysql_COUNT_ERRORS+=1
@@ -129,7 +129,7 @@ function mysql.db.import(){
 # show a list of existing databases
 function mysql.db.list(){
   # mysql -Ee "show databases ;" | grep "^Database:" | awk '{ print $2 }'
-  local _result=$( mysql -Ee "show databases ;" )
+  local _result=$( mysql ${BACKUP_PARAMS} -Ee "show databases ;" $BACKUP_PARAMS )
   fetchrc >/dev/null
   test $myrc -eq 0 && mysql_COUNT_DB=$( echo "$_result" | grep -c "^Database:" ) 
   test $myrc -eq 0 && echo "$_result" | grep "^Database:" | awk '{ print $2 }'
@@ -144,7 +144,8 @@ function mysql.db.list(){
 # USAGE: to abort a function if not available:
 # mysql.available || return
 function mysql.available(){
-  typeset -i local _rc=(1-$mysql_FOUND)
+  local _rc;
+  typeset -i _rc; _rc=(1-$mysql_FOUND)
   return $_rc
 }
 
diff --git a/plugins/localdump/pgsql.sh b/plugins/localdump/pgsql.sh
index cff5ec1d7fa0d4a3bac0743975dd14dcba51acda..fd2efe697ef21727f7fff229b8e31d321ec5b5f1 100755
--- a/plugins/localdump/pgsql.sh
+++ b/plugins/localdump/pgsql.sh
@@ -55,11 +55,11 @@ function doPgsqlBackup(){
   # prevent could not change directory to "/root": Permission denied
   cd /tmp
   sSqlGetDblist="select datname from pg_database where not datistemplate and datallowconn order by datname;"
-  for DATABASE in $(su ${PGUSER} -c "psql -At -c '$sSqlGetDblist' postgres" 2>/dev/null)
+  for DATABASE in $(su ${PGUSER} -c "psql ${BACKUP_PARAMS} -At -c '$sSqlGetDblist' postgres" 2>/dev/null)
   do
     echo -n "__DB__${SERVICENAME} backup $DATABASE ... "
     OUTFILE="${BACKUP_TARGETDIR}/$(get_outfile ${DATABASE}).sql"
-    su ${PGUSER} -c "pg_dump -Fp ${DATABASE} >$OUTFILE"
+    su ${PGUSER} -c "pg_dump ${BACKUP_PARAMS} -Fp ${DATABASE} >$OUTFILE"
     fetchrc >/dev/null
 
     db._compressDumpfile "$OUTFILE"
@@ -95,7 +95,7 @@ function restoreByFile(){
 
   h2 "ensure that database exists ..."
   color cmd
-  su ${PGUSER} -c "psql -c \"CREATE DATABASE ${sMyDb};\""
+  su ${PGUSER} -c "psql ${BACKUP_PARAMS} -c \"CREATE DATABASE ${sMyDb};\""
   fetchrc
   color reset
 
@@ -103,7 +103,7 @@ function restoreByFile(){
   ls -l "${sMyfile}"
   echo "import to database [${sMyDb}]"
   color cmd
-  zcat "${sMyfile}" | su ${PGUSER} -c "psql -d ${sMyDb}"
+  zcat "${sMyfile}" | su ${PGUSER} -c "psql ${BACKUP_PARAMS} -d ${sMyDb}"
   fetchrc
   color reset
 
diff --git a/plugins/localdump/profiles/couchdb2.ini.example b/plugins/localdump/profiles/couchdb2.ini.example
new file mode 100644
index 0000000000000000000000000000000000000000..6462a8cc9c83ada0d4581ffeec3523331cdaad1b
--- /dev/null
+++ b/plugins/localdump/profiles/couchdb2.ini.example
@@ -0,0 +1,23 @@
+# ======================================================================
+#
+# LOCAL COUCHDB SERVER
+#
+# ======================================================================
+
+[detect]
+
+tcp-port = 5984
+tcp-target = localhost
+tcp-process = 'beam.smp'
+
+
+[set]
+
+su = ''
+dbuser = 'admin'
+dbpassword = 'HereIsMyAdminPassword'
+
+params = ''
+env = 'export COUCH_URL=http://{dbuser}:{dbpassword}@{tcp-target}:{tcp-port}/'
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/ldap.ini b/plugins/localdump/profiles/ldap.ini
new file mode 100644
index 0000000000000000000000000000000000000000..62917ca4d119b0d29f8a37c197d2cc94d48dd033
--- /dev/null
+++ b/plugins/localdump/profiles/ldap.ini
@@ -0,0 +1,24 @@
+# ======================================================================
+#
+# LOCAL MYSQL INSTANCE
+#
+# ======================================================================
+
+[detect]
+
+# process = 'slapd'
+tcp-port = 636
+tcp-target = localhost
+tcp-process = 'slapd'
+
+
+[set]
+
+su = ''
+dbuser = ''
+dbpassword = ''
+
+params = ''
+env = ''
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/mysql.ini b/plugins/localdump/profiles/mysql.ini
new file mode 100644
index 0000000000000000000000000000000000000000..3f760a8386e4bd96e9f99739d0c8b2ba87abce94
--- /dev/null
+++ b/plugins/localdump/profiles/mysql.ini
@@ -0,0 +1,24 @@
+# ======================================================================
+#
+# LOCAL MYSQL INSTANCE
+#
+# ======================================================================
+
+[detect]
+
+process = 'mysqld|mariadb'
+tcp-port = 3306
+tcp-target = localhost
+tcp-process = 'mysqld|mariadbd'
+
+
+[set]
+
+su = ''
+dbuser = ''
+dbpassword = ''
+
+params = ''
+env = ''
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/mysql_localhost_docker_13306.ini.example b/plugins/localdump/profiles/mysql_localhost_docker_13306.ini.example
new file mode 100644
index 0000000000000000000000000000000000000000..3cabdc29f118176270c5e82e3a437ac50e39c485
--- /dev/null
+++ b/plugins/localdump/profiles/mysql_localhost_docker_13306.ini.example
@@ -0,0 +1,40 @@
+# ======================================================================
+#
+# DOCKER MYSQL INSTANCE ON LOCAL EXPOSED PORT
+#
+# ======================================================================
+
+[detect]
+# ----------------------------------------------------------------------
+# what to detect
+# ----------------------------------------------------------------------
+
+# a running process that must be found
+process = 'mysqld|mariadb'
+
+# a port that must be open on a given host
+tcp-port = 13306
+tcp-target = localhost
+
+# process that opens a port (see netstat -tulpen) - works for local services only
+# "slirp4netns" is docker network stack
+# "rootlesskit" is docker too
+tcp-process = 'rootlesskit'
+
+
+[set]
+# ----------------------------------------------------------------------
+# data to apply if it was found
+# ----------------------------------------------------------------------
+
+su = ''
+dbuser = 'root'
+dbpassword = '12345678'
+
+# unschΓΆn - das ist in der Prozessliste
+params = '--port={tcp-port} --password={dbpassword} --user={dbuser} --host={tcp-target}'
+
+# https://dev.mysql.com/doc/refman/8.0/en/environment-variables.html
+env = ''
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/pgsql.ini b/plugins/localdump/profiles/pgsql.ini
new file mode 100644
index 0000000000000000000000000000000000000000..09e79fe41c95917770edd687e6ce29f21f20058f
--- /dev/null
+++ b/plugins/localdump/profiles/pgsql.ini
@@ -0,0 +1,36 @@
+# ======================================================================
+#
+# LOCAL PGSQL INSTANCE
+#
+# ======================================================================
+
+[detect]
+
+process = 'postgres|postmaster'
+tcp-port = 5432
+tcp-target = localhost
+tcp-process = 'postgres|postmaster'
+
+
+[set]
+
+su = 'postgres'
+dbuser = ''
+dbpassword = ''
+
+# --- from pgsql help:
+; Connection options:
+;   -h, --host=HOSTNAME      database server host or socket directory (default: "/var/run/postgresql")
+;   -p, --port=PORT          database server port (default: "5432")
+;   -U, --username=USERNAME  database user name (default: "postgres")
+;   -w, --no-password        never prompt for password
+;   -W, --password           force password prompt (should happen automatically)
+;
+# params = '--host= {tcp-target} --port={tcp-port} --username={dbuser} --no-password'
+# https://www.postgresql.org/docs/current/libpq-envars.html
+
+
+params = ''
+env = ''
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/pgsql_remote.ini.example b/plugins/localdump/profiles/pgsql_remote.ini.example
new file mode 100644
index 0000000000000000000000000000000000000000..98b728a93f0ef7308d7fc1aced72ebfda4778c3c
--- /dev/null
+++ b/plugins/localdump/profiles/pgsql_remote.ini.example
@@ -0,0 +1,34 @@
+# ======================================================================
+#
+# REMOTE PGSQL INSTANCE
+# untested yet
+#
+# ======================================================================
+
+[detect]
+
+process = 'postgres|postmaster'
+tcp-port = 5432
+tcp-target = dbserver.example.com
+tcp-process = 'postgres|postmaster'
+
+
+[set]
+
+su = ''
+dbuser = 'mydbuser'
+dbpassword = 'mypassword'
+
+params = '--host= {tcp-target} --port={tcp-port} --username={dbuser}'
+env = 'PGPASSWORD={{dbpassword}}'
+
+# https://www.postgresql.org/docs/current/libpq-envars.html
+# from pgsql help:
+; Connection options:
+;   -h, --host=HOSTNAME      database server host or socket directory (default: "/var/run/postgresql")
+;   -p, --port=PORT          database server port (default: "5432")
+;   -U, --username=USERNAME  database user name (default: "postgres")
+;   -w, --no-password        never prompt for password
+;   -W, --password           force password prompt (should happen automatically)
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/profiles/sqlite.ini.example b/plugins/localdump/profiles/sqlite.ini.example
new file mode 100644
index 0000000000000000000000000000000000000000..9a8bd3acf415af6b93394a4fe92e676879a20ff3
--- /dev/null
+++ b/plugins/localdump/profiles/sqlite.ini.example
@@ -0,0 +1,25 @@
+# ======================================================================
+#
+# LOCAL SQLITE DATABASES
+#
+# ======================================================================
+
+[detect]
+
+# the filetype to detect using file command
+type = "sqlite"
+
+# list of files to backup
+# file[] = "/var/www/database/logs.db"
+
+
+[set]
+
+su = ''
+dbuser = ''
+dbpassword = ''
+
+params = ''
+env = ''
+
+# ----------------------------------------------------------------------
diff --git a/plugins/localdump/sqlite.sh b/plugins/localdump/sqlite.sh
index b6bbb85af6558c0848229e4656b0a31d433aaf1f..b454d0cd582a11fb27ad1bf3dcb389d8633d4f32 100755
--- a/plugins/localdump/sqlite.sh
+++ b/plugins/localdump/sqlite.sh
@@ -2,8 +2,7 @@
 # ================================================================================
 #
 # LOCALDUMP :: SQLITE
-# create gzipped plain text backups from each sqlite database in
-# backup-dbfiles.job
+# create gzipped plain text backups from each sqlite database
 #
 # --------------------------------------------------------------------------------
 # ah - Axel Hahn <axel.hahn@iml.unibe.ch>
@@ -12,6 +11,7 @@
 # 2018-02-02  ah,ds  v1.0  first lines
 # 2018-02-09  ah,ds  v1.1  write a .meta file after successful backup
 # 2022-03-17         v1.2  WIP: add lines with prefix __DB__
+# 2024-02-20  ah     v1.3  Handle profiles; restore into existing db file; restore file owner and perms
 # ================================================================================
 
 if [ -z "$BACKUP_TARGETDIR" ]; then
@@ -23,22 +23,17 @@ fi
 # CONFIG
 # --------------------------------------------------------------------------------
 
-FILEDEFS=${DIR_JOBS}/backup-dbfiles.job
-
 # --------------------------------------------------------------------------------
 # FUNCTIONS
 # --------------------------------------------------------------------------------
 
-# make sqlite3 backups of all sqlite = ... in backup-dbfiles.job
+# start multiple sqlite3 backups
+# files are taken from loaded profiles/sqlite*.ini - section [detect] -> files[]
 function doSqliteBackup(){
-  if ! _j_getvar ${FILEDEFS} "sqlite" | grep . ; then
-    echo "__DB__$SERVICENAME SKIP: no entries found for sqlite."
-    return 0
-  fi
 
   create_targetdir
 
-  for DATABASE_FILE in $(_j_getvar ${FILEDEFS} "sqlite")
+  for DATABASE_FILE in $( dbdetect.getFiles )
   do
     echo -n "__DB__${SERVICENAME} backup $DATABASE_FILE "
     if [ ! -f "$DATABASE_FILE" ]; then
@@ -47,24 +42,17 @@ function doSqliteBackup(){
       color reset
       rc=$rc+1
     else
-      file "$DATABASE_FILE" | cut -f 2 -d ":" | grep -i "sqlite" >/dev/null
-      if [ $? -ne 0 ]; then
-        color error
-        echo "ERROR: given database file is not a sqlite database"
-        color reset
-        rc=$rc+1
-      else
-        TARGET=$(get_outfile ${DATABASE_FILE})
-        TARGET=${BACKUP_TARGETDIR}/$(echo ${TARGET} | sed "s#/#_#g").sql
-        META=${TARGET}.gz.meta
-        # echo -n " to $TARGET "
-        sqlite3 "$DATABASE_FILE" .dump >"${TARGET}"
-        fetchrc >/dev/null
-        db._compressDumpfile "${TARGET}" && echo "$DATABASE_FILE" >"${META}"
-
-        ls -l ${TARGET}*
-
-      fi
+      
+      TARGET=$(get_outfile ${DATABASE_FILE})
+      TARGET=${BACKUP_TARGETDIR}/$(echo ${TARGET} | sed "s#/#_#g").sql
+      META=${TARGET}.gz.meta
+      # echo -n " to $TARGET "
+      sqlite3 "$DATABASE_FILE" .dump >"${TARGET}"
+      fetchrc >/dev/null
+      db._compressDumpfile "${TARGET}" && stat "$DATABASE_FILE" >"${META}"
+
+      ls -l ${TARGET}*
+
     fi
   done
 }
@@ -73,29 +61,27 @@ function doSqliteBackup(){
 # param  string  database dump file (gzipped)
 # param  string  optional: database to import; default: database is parsed from file
 function restoreByFile(){
-  sMyfile=$1
-  sMyDb=$2
+  local sMyfile=$1
+  local sMyDb=$2
 
   if [ -f "${sMyDb}" ]; then
-    color error
-    echo ERROR: target file already exists. Remove or rename it first.
+    # echo -n "" > "${sMyDb}"
+    echo "Deleting ${sMyDb} before restore..."
+    rm -f "${sMyDb}"
+  fi
+    
+  echo -n "Restore ${sMyfile} ... "
+  color cmd
+  zcat "${sMyfile}" | sqlite3 "${sMyDb}"
+  fetchrc
+  if [ $myrc -eq 0 ]; then
+    restorePermissions "${sMyfile}" "${sMyDb}"
     ls -l "${sMyDb}"
-    color reset
-    rc=$rc+1
+    echo
   else
-    color cmd
-    zcat "${sMyfile}" | sqlite3 "${sMyDb}"
-    fetchrc
-    if [ $myrc -eq 0 ]; then
-      color ok
-      echo OK, restore was successful
-      color reset
-      ls -l "${sMyDb}"
-    else
-      color error
-      echo ERROR while restoring backup.
-      color reset
-    fi
+    color error
+    echo ERROR while restoring backup.
+    color reset
   fi
 
 }
@@ -108,15 +94,9 @@ function restoreByFile(){
 # ----- check requirements
 j_requireBinary "sqlite3"   1
 
-if [ ! -f "$FILEDEFS" ]; then
-  echo "INFO: file definitions $FILEDEFS do not exist."
-  rc=$rc+1
-fi
-
-
 if [ $rc -ne 0 ]; then
-  rc=0
-  echo "SKIP: sqlite seems not to be here"
+  rc=1
+  color.echo error "ERROR: Your Sqlite data cannot be dumped."
 else
   if [ "$1" = "restore" ]; then
     echo
diff --git a/restore.sh b/restore.sh
index 24e1f4212638fbdc09017f7c1f531e6adeae7af0..bf985f96e07317e49e1433475a67b2977abb33e1 100755
--- a/restore.sh
+++ b/restore.sh
@@ -33,8 +33,8 @@
 
   # . `dirname $0`/inc_config.sh
 
-  . $(dirname $0)/jobhelper.sh
-  . $(dirname $0)/inc_bash.sh
+  . $(dirname $0)/includes/jobhelper.sh || exit 1
+  . $(dirname $0)/includes/inc_bash.sh  || exit 1
 
   # --- load a transfer plugin
   STORAGE_BIN=$(_j_getvar ${STORAGEFILE} "bin")
diff --git a/transfer.sh b/transfer.sh
index dd0c900a3884bfcb84d01839153ea468a4c2874a..c313fcfca755b609b431dccdf9a02043c92f90d2 100755
--- a/transfer.sh
+++ b/transfer.sh
@@ -48,8 +48,8 @@
 
   # . `dirname $0`/inc_config.sh
 
-  . $(dirname $0)/jobhelper.sh
-  . $(dirname $0)/inc_bash.sh
+  . $(dirname $0)/includes/jobhelper.sh || exit 1
+  . $(dirname $0)/includes/inc_bash.sh  || exit 1
 
   typeset -i rc=0
   typeset -i doBackup=1
diff --git a/vendor/color.class.sh b/vendor/color.class.sh
new file mode 100644
index 0000000000000000000000000000000000000000..362078205343127eac90576b64e96e9aca4cd3e9
--- /dev/null
+++ b/vendor/color.class.sh
@@ -0,0 +1,601 @@
+#!/bin/bash
+# ======================================================================
+#
+# COLORS
+#
+# a few shell functions for colored output
+# 
+# ----------------------------------------------------------------------
+# License: GPL 3.0
+# Source: <https://github.com/axelhahn/bash_colorfunctions>
+# Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/>
+# ----------------------------------------------------------------------
+# 2023-08-09  ahahn  0.1  initial lines
+# 2023-08-09  ahahn  0.2  hide output of regex test with grep
+# 2023-08-13  ahahn  0.3  introduce of color presets with foreground and background
+# 2023-08-13  ahahn  0.4  list presets, debug, count of colors
+# 2023-08-13  ahahn  0.5  support of RGB hex code
+# 2023-08-14  ahahn  0.6  fix setting fg and bg as RGB hex code
+# 2023-08-14  ahahn  0.7  remove color.ansi; respect NO_COLOR=1
+# 2023-08-16  ahahn  0.8  add function color.preset
+# ======================================================================
+
+_VERSION=0.8
+typeset -i COLOR_DEBUG; COLOR_DEBUG=0
+
+# ----------------------------------------------------------------------
+# CONSTANTS
+# ----------------------------------------------------------------------
+
+declare -A BGCOLOR_CODE
+declare -A COLOR_CODE
+
+# background colors
+BGCOLOR_CODE[black]="40"
+BGCOLOR_CODE[red]="41"
+BGCOLOR_CODE[green]="42"
+BGCOLOR_CODE[brown]="43"
+BGCOLOR_CODE[blue]="44"
+BGCOLOR_CODE[purple]="45"
+BGCOLOR_CODE[cyan]="46"
+BGCOLOR_CODE[lightgray]="47"
+BGCOLOR_CODE[darkgray]="1;40"
+BGCOLOR_CODE[lightred]="1;41"
+BGCOLOR_CODE[lightgreen]="1;42"
+BGCOLOR_CODE[yellow]="1;43"
+BGCOLOR_CODE[lightblue]="1;44"
+BGCOLOR_CODE[lightpurple]="1;45"
+BGCOLOR_CODE[lightcyan]="1;46"
+BGCOLOR_CODE[white]="1;47"
+
+# foreground colors
+COLOR_CODE[black]="30"
+COLOR_CODE[red]="31"
+COLOR_CODE[green]="32"
+COLOR_CODE[brown]="33"
+COLOR_CODE[blue]="34"
+COLOR_CODE[purple]="35"
+COLOR_CODE[cyan]="36"
+COLOR_CODE[lightgray]="37"
+COLOR_CODE[darkgray]="1;30"
+COLOR_CODE[lightred]="1;31"
+COLOR_CODE[lightgreen]="1;32"
+COLOR_CODE[yellow]="1;33"
+COLOR_CODE[lightblue]="1;34"
+COLOR_CODE[lightpurple]="1;35"
+COLOR_CODE[lightcyan]="1;36"
+COLOR_CODE[white]="1;37"
+
+# custom presets as array of foreground and background color
+#
+#              +--- the label is part of the variable
+#              |
+#              v
+# COLOR_PRESET_error=("white" "red")
+# COLOR_PRESET_ok=("white" "green")
+
+# ----------------------------------------------------------------------
+# PRIVATE FUNCTIONS
+# ----------------------------------------------------------------------
+
+# write debug output - if debugging is enabled
+# Its output is written to STDERR
+# param  string  text to show
+function color.__wd(){
+    test "$COLOR_DEBUG" = "1" && >&2 echo "DEBUG: $*"
+}
+
+# test, if given value is a known color name
+# param  string  colorname to test
+function color.__iscolorname(){
+    test -n "${COLOR_CODE[$1]}" && return 0
+    return 1
+}
+
+# test, if given value is a value 0..7
+# param  string  color to test
+function color.__iscolorcode(){
+    test "$1" = "0" && return 0
+    test "$1" = "1" && return 0
+    test "$1" = "2" && return 0
+    test "$1" = "3" && return 0
+    test "$1" = "4" && return 0
+    test "$1" = "5" && return 0
+    test "$1" = "6" && return 0
+    test "$1" = "7" && return 0
+    return 1
+}
+
+# test, if given value is an ansi code
+# param  string  color to test
+function color.__iscolorvalue(){
+    if grep -E "^([01];|)[34][0-7]$" >/dev/null <<< "$1" ; then
+        return 0
+    fi
+    return 1
+}
+
+# test, if given value is an rgb hexcode eg. #80a0f0
+# param  string  color to test
+function color.__isrgbhex(){
+    if grep -iE "^#[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]$" >/dev/null <<< "$1" ; then
+        return 0
+    fi
+    return 1
+}
+
+# convert rgb hex code eg. #80a0f0 to 3 decimal values
+# output us a string with space separated values for red, green, blue
+# param  string  color as "#RRGGBB" to convert
+function color.__getrgb(){
+    local _r
+    local _g
+    local _b
+    if color.__isrgbhex "$1"; then
+        _r=$( cut -c 2,3 <<< "$1" )
+        _g=$( cut -c 4,5 <<< "$1" )
+        _b=$( cut -c 6,7 <<< "$1" )
+        echo "$((16#$_r)) $((16#$_g)) $((16#$_b))"
+    fi
+}
+
+# test, if given value is a color that can be one of
+# - colorname
+# - value 0..7
+# - ansi code
+# param  string  color to test
+function color.__isacolor(){
+    if color.__iscolorname "$1"; then return 0; fi
+    if color.__iscolorcode "$1"; then return 0; fi
+    if color.__iscolorvalue "$1"; then return 0; fi
+    if color.__isrgbhex "$1"; then return 0; fi
+    color.__wd "$FUNCNAME is acolor: $1 --> No"
+    return 1
+}
+
+# test, if given value is an existing preset
+# param  string  color to test
+function color.__isapreset(){
+    local _colorset
+    eval "_colorset=\$COLOR_PRESET_${1}" 
+    test -n "$_colorset" && return 0
+    return 1
+}
+
+# respect NO_COLOR=1
+# return 1 if colors are allowed to be used.
+function color.__usecolor(){
+    test "$NO_COLOR" = "1" && return 1
+    return 0
+}
+
+# set foreground or background
+# param  string  color as
+#                - basic color 0..7 OR 
+#                - color name eg. "black" OR 
+#                - a valid color value eg. "1;30" OR 
+#                - a hex code eg. "#10404f"
+# param  integer what to set; '3' for for foreground or '4' for background colors
+function color.__fgorbg(){
+    local _color="$1"
+    local _prefix="$2"
+    color.__wd "$FUNCNAME $1 $2"
+    if color.__iscolorname "${_color}"; then
+        color.__wd "yep, ${_color} is a color name."
+        test "$_prefix" = "3" && color.set "${COLOR_CODE[${_color}]}"
+        test "$_prefix" = "4" && color.set "${BGCOLOR_CODE[${_color}]}"
+    else
+        if color.__iscolorcode "${_color}"; then
+            color.__wd "yep, ${_color} is a color code."
+        else
+            if color.__iscolorvalue "${_color}"; then
+                color.__wd "yep, ${_color} is a color value."
+                color.set "${_color}"
+            else
+                if color.__isrgbhex "${_color}"; then
+                    local _r
+                    local _g
+                    local _b
+                    read -r _r _g _b <<< $( color.__getrgb "${_color}" )
+                    color.set "${_prefix}8;2;$_r;$_g;$_b"
+                else
+                    >&2 echo "ERROR: color '${_color}' is not a name nor a value between 0..7 nor a valid color value nor RGB."
+                fi
+            fi
+        fi
+    fi
+}
+
+# ----------------------------------------------------------------------
+# FUNCTIONS :: helpers
+# ----------------------------------------------------------------------
+
+# get count of colors in the current terminal
+function color.count(){
+    tput colors
+}
+
+# enable debug flag
+function color.debugon(){
+    COLOR_DEBUG=1
+    color.__wd "$FUNCNAME - debugging is enabled now"
+}
+
+# disable debug flag
+function color.debugoff(){
+    color.__wd "$FUNCNAME - disabling debugging now"
+    COLOR_DEBUG=0
+}
+
+# show debugging status
+function color.debugstatus(){
+    echo -n "INFO: color.debug - debugging is "
+    if [ $COLOR_DEBUG -eq 0 ]; then
+        echo "DISABLED"
+    else
+        echo "ENABLED"
+    fi
+}
+
+# show help
+function color.help(){
+    local _self; _self='[path]/color.class.sh'
+    color.reset
+    local _debug=$COLOR_DEBUG
+    COLOR_DEBUG=0
+
+    echo "_______________________________________________________________________________"
+    echo
+    color.echo "red"      "   ###   ###  #      ###  ####"
+    color.echo "yellow"   "  #     #   # #     #   # #   #"
+    color.echo "white"    "  #     #   # #     #   # ####"
+    color.echo "yellow"   "  #     #   # #     #   # #  #"
+    color.echo "red"      "   ###   ###  #####  ###  #   #"
+    echo "_________________________________________________________________________/ v$_VERSION"
+    echo
+
+    sed "s#^    ##g" << EOH
+    HELP:
+      'color' is a class like component to simplify the handling of ansi colors and keeps
+      the color settings readable. A set NO_COLOR=1 will be respected.
+
+      Author: Axel Hahn
+      License: GNU GPL 3.0
+      Source: <https://github.com/axelhahn/bash_colorfunctions>
+      Docs: <https://www.axel-hahn.de/docs/bash_colorfunctions/>
+
+
+    FUNCTIONS:
+
+      ---------- Information:
+
+      color.help       this help
+      color.list       show a table with valid color names
+      color.presets    show a table with defined custom presets
+
+      color.count      get count of colors in the current terminal
+
+      color.debugon    enable debugging
+      color.debugoff   disable debugging
+      color.debugstatus  show debugstatus
+
+      ---------- Colored output:
+
+      color.bg COLOR (COLOR2)
+                       set a background color; a 2nd parameter is optional to set
+                       a foreground color too
+      color.fg COLOR (COLOR2)
+                       set a foreground color; a 2nd parameter is optional to set
+                       a background color too
+      color.preset PRESET
+                       Apply the color set of foreground and background of a given 
+                       preset name.
+      color.echo COLOR|PRESET (COLOR2) TEXT
+                       write a colored text with carriage return and reset colors
+                       The 1st param must be a COLOR(code/ name) for the 
+                       foreground or a label of a preset.
+                       The 2nd CAN be a color for the background, but can be 
+                       skipped.
+                       Everything behind is text for the output.
+      color.print COLOR|PRESET (COLOR2) TEXT
+                       see color.echo - the same but without carriage return.
+      color.reset      reset colors
+      color.set RAWCOLOR (RAWCOLOR2 (... RAWCOLOR_N))
+                       set ansi colors; it can handle multiple color values
+
+
+      ---------- Other:
+
+      color.blink      start blinking text
+      color.bold       start bold text
+      color.invert     start inverted text
+      color.underline  start underline text
+
+    VALUES:
+      COLOR            a color; it can be...
+                       - a color keyword, eg black, blue, red, ... for all
+                         known values run 'color.list'
+                       - a value 0..7 to set basic colors 30..37 (or 40..47)
+                       - an ansi color value eg. "30" or "1;42"
+                       - RGB hexcode with '#' as prefix followed by 2 digit 
+                         hexcode for red, green and blue eg. "#10404f" 
+                         (like css rgb color codes)
+      PRESET           Name of a custom preset; see DEFINE PRESETS below.
+      RAWCOLOR         an ansi color value eg. "30" (black foreground) or 
+                       "1;42" (lightgreen background)
+
+
+    DEFINE PRESETS:
+      A shortcut for a combination of foreground + background color. The label
+      is part of a bash variable with the prefix 'COLOR_PRESET_'.
+      The value is a bash array with 2 colors for foreground and background. 
+      See the value description for COLOR above.
+
+      SYNTAX:
+      COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>)
+
+      To see all defined presets use 'color.presets'
+
+
+    EXAMPLES:
+      First you need to source the file $_self.
+      . $_self
+
+      (1)
+      Show output of the command 'ls -l' in blue
+        color.fg "blue"
+        ls -l
+        color.reset
+
+      (2)
+      show a red error message
+        color.echo "red" "ERROR: Something went wrong."
+
+      (3)
+      Use a custom preset:
+        COLOR_PRESET_error=("white" "red")
+        color.echo "error" "ERROR: Something went wrong."
+
+      This defines a preset named "error". "white" is a colorname
+      for the foreground color, "red" ist the background.
+
+EOH
+
+    if [ -n "$NO_COLOR" ]; then
+        echo -n "INFO: NO_COLOR=$NO_COLOR was set. The coloring functionality is "
+        if ! color.__usecolor; then
+            echo "DISBALED."
+        else
+            echo "ENABLED (must be 1 to disable)."
+        fi
+        echo
+    else
+        echo "INFO: NO_COLOR will be respected - but it is not set."
+    fi
+
+    COLOR_DEBUG=$_debug
+}
+
+# a little helper: show colors and the color codes
+function color.list(){
+    color.reset
+    local _debug=$COLOR_DEBUG
+    COLOR_DEBUG=0
+
+    echo
+    echo "List of colors:"
+    echo
+
+    echo "--------------------------------------------------"
+    echo "color          | foreground         | background"
+    echo "--------------------------------------------------"
+    for i in "${!COLOR_CODE[@]}"
+    do
+        printf "%-15s %4s " $i ${COLOR_CODE[$i]} 
+        color.set "${COLOR_CODE[$i]}"
+        color.set "40"
+        printf " Test "
+
+        color.set "1;47"
+        color.set "${COLOR_CODE[$i]}"
+        printf " Test "
+        color.reset
+
+        printf "   %5s " ${BGCOLOR_CODE[$i]} 
+        color.set ${BGCOLOR_CODE[$i]}
+        printf " Test "
+        color.reset
+        echo
+
+    done | sort
+    color.reset
+    echo "--------------------------------------------------"
+    echo
+    COLOR_DEBUG=$_debug
+}
+
+# little helper: sow defined presets and its preview
+function color.presets(){
+    local _label
+    local _value
+    local _colorvar
+    local _fg
+    local _bg
+
+    color.reset
+    local _debug=$COLOR_DEBUG
+    COLOR_DEBUG=0
+
+    if ! set | grep "^COLOR_PRESET_.*=(" >/dev/null; then
+        echo "INFO: No preset was defined yet."
+        echo "To set one define shell variables with an array of 2 colors:"
+        echo "  COLOR_PRESET_<LABEL>=(<FOREGROUND> <BACKGROUND>)"
+        echo "For more help call 'color.help' or see the docs."
+    else
+        echo
+        echo "List of presets:"
+        echo
+        echo "---------------------------------------------------------------------"
+        echo "label      | foreground   | background   | example"
+        echo "---------------------------------------------------------------------"
+
+        set | grep "^COLOR_PRESET_.*=(" | while read -r line
+        do
+            _label=$( cut -f 1 -d '=' <<< "$line" | cut -f 3- -d '_')
+            _example=$( color.print "$_label" "example for peset '$_label'" )
+            _colorvar="COLOR_PRESET_${_label}" 
+            eval "_fg=\${$_colorvar[0]}"
+            eval "_bg=\${$_colorvar[1]}"
+
+            printf "%-10s | %-12s | %-12s | %-50s\n"  "$_label" "${_fg}" "${_bg}" "$_example"
+        done
+        echo "---------------------------------------------------------------------"
+        echo
+    fi
+    COLOR_DEBUG=$_debug
+}
+# ----------------------------------------------------------------------
+# FUNCTIONS :: set color
+# ----------------------------------------------------------------------
+
+# set background color
+# param  string  backround color 0..7 OR color name eg "black" or a valid color value eg "1;30"
+# param  string  optional: foreground color
+function color.bg(){
+    color.__wd "$FUNCNAME $1"
+    color.__fgorbg "$1" 4
+    test -n "$2" && color.fg "$2"
+}
+
+# get a color of a preset
+# param  string   name of preset
+# param  integer  array index; 0= foreground; 1= background
+function color.__getpresetcolor(){
+    local _label=$1
+    local _index=$2
+    local _colorvar
+    _colorvar="COLOR_PRESET_${_label}" 
+    eval "echo \${$_colorvar[$_index]}"
+}
+
+# set foreground color
+# param  string  foreground color 0..7 OR color name eg "black" or a valid color value eg "1;30"
+# param  string  optional: background color
+function color.fg(){
+    color.__wd "$FUNCNAME $1"
+    color.__fgorbg "$1" 3
+    test -n "$2" && color.bg "$2"
+}
+
+
+# set colors of a preset
+# param  string  label of a preet
+function color.preset(){
+    if color.__isapreset "$1"; then
+        local _colorvar
+        local _colfg=$( color.__getpresetcolor "$1" 0)
+        local _colbg=$( color.__getpresetcolor "$1" 1)
+        color.reset
+        test -n "$_colfg" && color.__fgorbg "$_colfg" 3
+        test -n "$_colbg" && color.__fgorbg "$_colbg" 4
+    else
+        >&2 echo "ERROR: this value is not a valid preset: $1. See 'color.presets' to see current presets."
+    fi
+}
+
+# ----------------------------------------------------------------------
+
+# reset all colors to terminal default
+function color.reset(){
+    color.__wd "$FUNCNAME"
+    color.set "0"
+}
+
+# start bold text
+function color.bold(){
+    color.__wd "$FUNCNAME"
+    color.set "1"
+}
+
+# start underline text
+function color.underline(){
+    color.__wd "$FUNCNAME"
+    color.set "4"
+}
+
+# start blinking text
+function color.blink(){
+    color.__wd "$FUNCNAME"
+    color.set "5"
+}
+
+# start inverted text
+function color.invert(){
+    color.__wd "$FUNCNAME"
+    color.set "7"
+}
+
+# ----------------------------------------------------------------------
+
+# write ansicode to set color combination
+# param  string  color 1 as ansi value
+# param  string  color N as ansi value
+function color.set(){
+    local _out=
+    if color.__usecolor; then
+        for mycolor in $*
+        do
+            color.__wd "$FUNCNAME: processing color value '${mycolor}'"
+            _out+="${mycolor}"
+        done
+        color.__wd "$FUNCNAME: output is '\e[${_out}m'"
+        printf "\e[${_out}m"
+    else
+        color.__wd "$FUNCNAME: skipping - coloring is disabled."
+    fi
+}
+
+# ----------------------------------------------------------------------
+# FUNCTIONS :: print
+# ----------------------------------------------------------------------
+
+# show a colored text WITH carriage return
+# param  string  foreground color as code / name / value
+# param  string  optional: background color as code / name / value
+# param  string  text to print
+function color.echo(){
+    color.__wd "$FUNCNAME $*"
+    local _param1="$1"
+    local _param2="$2"
+    shift 1
+    shift 1
+    color.print "$_param1" "$_param2" "$*"
+    echo
+}
+
+# show a colored text without carriage return
+# param  string  foreground color as code / name / value or preset
+# param  string  optional: background color as code / name / value
+# param  string  text to print
+function color.print(){
+    color.__wd "$FUNCNAME $*"
+    if color.__isacolor "$1"; then
+        if color.__isacolor "$2"; then
+            color.fg "$1" "$2"
+            shift 1
+            shift 1
+        else
+            color.fg "$1"
+            shift 1
+        fi
+    elif color.__isapreset "$1"; then
+        color.preset "$1"
+        shift 1
+    else
+        >&2 echo -n "ERROR: Wrong color values detected. Command was: colors.print $*"
+    fi
+    echo -n "$*"
+    color.reset
+}
+
+# ======================================================================
diff --git a/vendor/ini.class.sh b/vendor/ini.class.sh
new file mode 100644
index 0000000000000000000000000000000000000000..3b1dbf8409abe340eaf299b06ad2ac990af3cc13
--- /dev/null
+++ b/vendor/ini.class.sh
@@ -0,0 +1,434 @@
+#!/bin/bash
+# ======================================================================
+#
+# READ INI FILE with Bash
+# https://axel-hahn.de/blog/2018/06/08/bash-ini-dateien-parsen-lesen/
+#
+#  Author:  Axel hahn
+#  License: GNU GPL 3.0
+#  Source:  https://github.com/axelhahn/bash_iniparser
+#  Docs:    https://www.axel-hahn.de/docs/bash_iniparser/
+#
+# ----------------------------------------------------------------------
+# 2024-02-04  v0.1  Initial version
+# 2024-02-08  v0.2  add ini.varexport; improve replacements of quotes
+# 2024-02-10  v0.3  handle spaces and tabs around vars and values
+# 2024-02-12  v0.4  rename local varables
+# 2024-02-20  v0.5  handle special chars in keys; add ini.dump + ini.help
+# 2024-02-21  v0.6  harden ini.value for keys with special chars; fix fetching last value
+# 2024-03-17  v0.7  add ini.validate
+# 2024-03-17  v0.8  errors are written to STDERR; update help; use [[:space:]] in regex; update help
+# ======================================================================
+
+INI_FILE=
+INI_SECTION=
+
+# ----------------------------------------------------------------------
+# SETTER
+# ----------------------------------------------------------------------
+
+# Set the INI file - and optional section - for short calls.
+# param  string  filename
+# param  string  optional: section
+function ini.set(){
+    INI_FILE=
+    INI_SECTION=
+    if [ ! -f "$1" ]; then
+        echo "ERROR: file does not exist: $1" >&2
+        exit 1
+    fi
+    INI_FILE="$1"
+
+    test -n "$2" && ini.setsection "$2"
+    
+}
+
+# Set the INI section for short calls.
+# param  string  section
+function ini.setsection(){
+    if [ -z "$INI_FILE" ]; then
+        echo "ERROR: ini file needs to be set first. Use ini.set <INIFILE> [<SECTION>]." >&2
+        exit 1
+    fi
+    if [ -n "$1" ]; then
+        if ini.sections "$INI_FILE" | grep "^${1}$" >/dev/null; then
+            INI_SECTION=$1
+        else
+            echo "ERROR: Section [$1] does not exist in [$INI_FILE]." >&2
+            exit 1
+        fi
+    fi
+}
+
+# ----------------------------------------------------------------------
+# GETTER
+# ----------------------------------------------------------------------
+
+# Get all sections
+# param1 - name of the ini file
+function ini.sections(){
+        local myinifile=${1:-$INI_FILE}
+        grep "^\[" "$myinifile" | sed 's,^\[,,' | sed 's,\].*,,'
+}
+
+# Get all content inside a section
+# param1 - name of the ini file
+# param2 - name of the section in ini file
+function ini.section(){
+        local myinifile=${1:-$INI_FILE}
+        local myinisection=${2:-$INI_SECTION}
+        sed -e "0,/^\[${myinisection}\]/ d" -e '/^\[/,$ d' "$myinifile" \
+            | grep -v "^[#;]" \
+            | sed -e "s/^[ \t]*//g" -e "s/[ \t]*=[ \t]*/=/g"
+}
+
+# Get all keys inside a section
+# param1 - name of the ini file
+# param2 - name of the section in ini file
+function ini.keys(){
+        local myinifile=${1:-$INI_FILE}
+        local myinisection=${2:-$INI_SECTION}
+        ini.section "${myinifile}" "${myinisection}" \
+            | grep "^[\ \t]*[^=]" \
+            | cut -f 1 -d "=" \
+            | sort -u
+}
+
+
+# Get a value of a variable in a given section
+# param1 - name of the ini file
+# param2 - name of the section in ini file
+# param3 - name of the variable to read
+function ini.value(){
+
+        if [ -n "$2" ] && [ -z "$3" ]; then
+            ini.value "$INI_FILE" "$1" "$2"
+        elif [ -z "$2" ]; then
+            ini.value "$INI_FILE" "$INI_SECTION" "$1"
+        else
+            local myinifile=$1
+            local myinisection=$2
+            local myvarname=$3
+            local out
+            regex="${myvarname//[^a-zA-Z0-9:()]/.}"
+            out=$(ini.section "${myinifile}" "${myinisection}" \
+                | sed -e "s,^[[:space:]]*,,g" -e "s,[[:space:]]*=,=,g"  \
+                | grep -F "${myvarname}=" \
+                | grep "^${regex}=" \
+                | cut -f 2- -d "=" \
+                | sed -e 's,^[[:space:]]*,,' -e 's,[[:space:]]*$,,' 
+                )
+            grep "\[\]$" <<< "$myvarname" >/dev/null || out="$( echo "$out" | tail -1 )"
+
+            # delete quote chars on start and end
+            grep '^".*"$' <<< "$out" >/dev/null && out=$(echo "$out" | sed -e's,^"\(.*\)"$,\1,g')
+            grep "^'.*'$" <<< "$out" >/dev/null && out=$(echo "$out" | sed -e"s,^'\(.*\)'$,\1,g")
+            echo "$out"
+        fi
+}
+
+# dump the ini file for visuall check of the parsing functions
+# param  string  filename
+ini.dump() {
+    local myinifile=${1:-$INI_FILE}
+    echo -en "\e[1;33m"
+    echo "+----------------------------------------"
+    echo "|"
+    echo "| $myinifile"
+    echo "|"
+    echo -e "+----------------------------------------\e[0m"
+    echo -e "\e[34m"
+    sed "s,^,    ,g" "${myinifile}"
+    echo -e "\e[0m"
+
+    echo "    Parsed data:"
+    echo
+    ini.sections "$myinifile" | while read -r myinisection; do
+        if ! ini.keys "$myinifile" "$myinisection" | grep -q "."; then
+            echo -e "    ----- section \e[35m[$myinisection]\e[0m"
+        else
+            echo -e "    --+-- section \e[35m[$myinisection]\e[0m"
+            echo    "      |"
+            ini.keys "$myinifile" "$myinisection" | while read -r mykey; do
+                value="$(ini.value "$myinifile" "$myinisection" "$mykey")"
+                # printf "        %-15s => %s\n" "$mykey" "$value"
+                printf "      \`---- %-20s => " "$mykey"
+                echo -e "\e[1;36m$value\e[0m"
+            done
+        fi
+        echo
+    done
+    echo
+}
+
+function ini.help(){
+
+    # local _self
+    # if _is_sourced; then
+    #     _self="ini."
+    # else
+    #     _self="$( basename "$0" ) "
+    # fi
+
+    cat <<EOH
+
+    INI.CLASS.SH
+
+    A bash implementation to read ini files.
+
+    Author:  Axel hahn
+    License: GNU GPL 3.0
+    Source:  https://github.com/axelhahn/bash_iniparser
+    Docs:    https://www.axel-hahn.de/docs/bash_iniparser/
+
+    Usage:
+
+    (1)
+    source the file ini.class.sh
+
+    (2)
+    ini.help
+    to show this help with all available functions.
+
+
+    BASIC ACCESS:
+
+    ini.value <INIFILE> <SECTION> <KEY>
+        Get a avlue of a variable in a given section.
+
+        Tho shorten ini.value with 3 parameters:
+
+        ini.set <INIFILE> [<SECTION>]
+
+        or
+
+        ini.set <INIFILE>
+        ini.setsection <SECTION>
+
+        This sets the ini file and/ or section as default.
+        Afterwards you can use:
+
+        ini.value <KEY>
+        and
+        ini.value <SECTION> <KEY>
+
+    OTHER GETTERS:
+
+    ini.sections <INIFILE>
+        Get all sections in the ini file.
+        The <INIFILE> is not needed if ini.set <INIFILE> was used before.
+
+    ini.keys <INIFILE> <SECTION>
+        Get all keys in the given section.
+        The <INIFILE> is not needed if ini.set <INIFILE> was used before.
+        The <SECTION> is not needed if ini.setsection <SECTION> was used 
+        before.
+
+    ini.dump <INIFILE>
+        Get a pretty overview of the ini file.
+        You get a colored view of the content and a parsed view of the
+        sections and keys + values.
+
+    VALIDATION:
+
+    ini.validate <INIFILE> <VALIDATIONINI> <FLAG>
+        Validate your ini file with the rules of a given validation ini file.
+        The ini for validation contains rules for 
+        * writing sections and keys
+        * sections and keys thet must exist or can exist
+        * describe values of keys to ensure to get vald data (eg a regex)
+        see https://www.axel-hahn.de/docs/bash_iniparser/Validation.html
+        The <FLAG> is optional. By default it is 0 and shows error information
+        only on STDOUT. Set it to 1 to see more output about the validation 
+        process.
+
+EOH
+}
+
+# Create bash code to export all variables as hash.
+# Example: eval "$( ini.varexport "cfg_" "$inifile" )"
+#
+# param  string  prefix for the variables
+# param  string  ini file to read
+function ini.varexport(){
+    local myprefix="$1"
+    local myinifile="$2"
+    local var=
+
+    for myinisection in $(ini.sections "$myinifile"); do
+        var="${myprefix}${myinisection}"
+        echo "declare -A ${var}; "
+        echo "export ${var}; "
+        
+        for mykey in $(ini.keys "$myinifile" "$myinisection"); do
+            value="$(ini.value "$myinifile" "$myinisection" "$mykey")"
+            echo ${var}[$mykey]="\"$value\""
+        done
+    done
+    
+}
+
+# validate the ini file
+# param  string  path of ini file to validate
+# param  string  path of ini file with validation rules
+# param  bool    optional: show more output; default: 0
+function ini.validate(){
+
+    function _vd(){
+        test "$bShowAll" -ne "0" && echo "$*"
+    }
+
+    local myinifile="$1"
+    local myvalidationfile="$2"
+    local bShowAll="${3:-0}"
+
+    local ERROR="\e[1;31mERROR\e[0m"
+    local iErr; typeset -i iErr=0
+
+    # TODO: make all used vars local
+
+    _vd "START: Validate ini '${myinifile}'"
+    _vd "       with '${myvalidationfile}'"
+    if [ ! -f "${myinifile}" ]; then
+        echo -e "$ERROR: Ini file in first param '${myinifile}' does not exist." >&2
+        return 1
+    fi
+
+    if [ ! -f "${myvalidationfile}" ]; then
+        echo -e "$ERROR: Validation file in 2nd param '${myvalidationfile}' does not exist." >&2
+        return 1
+    fi
+
+    eval "$( ini.varexport "validate_" "$myvalidationfile" )"
+    
+    if [ -z "${validate_sections[*]}" ]; then
+        echo -e "$ERROR: Validation file in 2nd param '${myvalidationfile}' has no section definition [sections]." >&2
+        echo "       Hint: Maybe it is no validation file (yet) or you flipped the parameters." >&2
+        return 1
+    fi
+    if [ -z "${validate_varsMust[*]}${validate_varsCan[*]}" ]; then
+        echo -e "$ERROR: Validation file in 2nd param '${myvalidationfile}' has no key definition [varsMust] and [varsCan]." >&2
+        echo "       Hint: Maybe it is no validation file (yet) or you flipped the parameters." >&2
+        return 1
+    fi
+
+
+    # ----- Check if all MUST sections are present
+    if [ -n "${validate_sections['must']}" ]; then
+        _vd "--- Sections that MUST exist:"
+        for section in $( tr "," " " <<< "${validate_sections['must']}");
+        do
+            if ini.sections "$myinifile" | grep -q "^$section$" ; then
+                _vd "OK: Section [$section] is present."
+            else
+                echo -e "$ERROR: Section [$section] is not present." >&2
+                iErr+=1
+            fi
+        done
+    fi
+
+    # ----- Loop over sections
+    _vd "--- Validate section names"
+    for section in $( ini.sections "$myinifile" )
+    do
+        # ----- Check if our section name has the allowed syntax
+        if ! grep -q "${validate_style['section']}" <<< "$section" ; then
+            echo -e "$ERROR: Section [$section] violates style rule '${validate_style['section']}'" >&2
+        fi
+
+        # ----- Check if our sections are in MUST or CAN
+        if ! grep -Fq ",${section}," <<< ",${validate_sections['must']},${validate_sections['must']},"; then
+            echo -e "$ERROR: Unknown section name: [$section] - ist is not listed as MUST nor CAN." >&2
+            iErr+=1
+        else
+            _vd "OK: section [$section] is valid"
+            _vd "  Check keys of section [$section]"
+
+            # ----- Check MUST keys in the current section
+            for myKeyEntry in "${!validate_varsMust[@]}"; do
+                if ! grep -q "^${section}\." <<< "${myKeyEntry}"; then
+                    continue
+                fi
+                mustkey="$( echo "$myKeyEntry" | cut -f2 -d '.')"
+                # TODO
+                keyregex="$( echo "$mustkey" | sed -e's,\[,\\[,g' )"
+                if ini.keys "$myinifile" "$section" | grep -q "^${keyregex}$"; then
+                    _vd "  OK: [$section] -> $mustkey is a MUST"
+                else
+                    echo -e "  $ERROR: [$section] -> $mustkey is a MUST key but was not found im section [$section]." >&2
+                    iErr+=1
+                fi
+
+            done
+
+            # ----- Check if our keys are MUST or CAN keys
+            for mykey in $( ini.keys "$myinifile" "$section"); do
+
+                if ! grep -q "${validate_style['key']}" <<< "$mykey" ; then
+                    echo -e "$ERROR: Key [$section] -> $mykey violates style rule '${validate_style['key']}'" >&2
+                fi
+
+                keyregex="$( echo "${mykey}" | sed -e's,\[,\\[,g' | sed -e's,\],\\],g' )"
+
+                local mustKeys
+                mustKeys="$( echo "${!validate_varsMust[@]}" | tr ' ' ',')"
+                local canKeys
+                canKeys="$( echo "${!validate_varsCan[@]}" | tr ' ' ',')"
+
+                if ! grep -Fq ",${section}.$mykey," <<< ",${canKeys},${mustKeys},"; then
+                    echo -e "  $ERROR: [$section] -> $mykey is invalid." >&2
+                    iErr+=1
+                else
+
+                    local valKey
+                    valKey="${section}.${mykey}"
+                    if [ -n "${validate_varsCan[$valKey]}" ] && [ -n "${validate_varsMust[$valKey]}" ]; then
+                        echo -e "  $ERROR: '$valKey' is defined twice - in varsMust and varsCan as well. Check validation file '$myvalidationfile'." >&2
+                    fi
+
+                    sValidate="${validate_varsCan[${section}.${mykey}]}"
+                    if [ -z "$sValidate" ]; then
+                        sValidate="${validate_varsMust[${section}.${mykey}]}"
+                    fi
+                    if [ -z "$sValidate" ]; then
+                        _vd "  OK: [$section] -> $mykey exists (no check)"
+                    else
+                        local checkType
+                        local checkValue
+                        local value
+
+                        checkType=$( cut -f 1 -d ':' <<< "$sValidate" )
+                        checkValue=$( cut -f 2- -d ':' <<< "$sValidate" )
+                        value="$(ini.value "$myinifile" "$section" "$mykey")"
+                        local regex
+                        case $checkType in
+                            'INTEGER') regex="^[1-9][0-9]*$"          ;;
+                            'ONEOF')   regex="^(${checkValue//,/|})$" ;;
+                            'REGEX')   regex="$checkValue"            ;;
+                            *)
+                                echo -e "  $ERROR: ceck type '$checkType' is not supported." >&2
+                        esac
+                        if [ -n "$regex" ]; then
+                            if ! grep -Eq "${regex}" <<< "$value" ; then
+                                echo -e "  $ERROR: [$section] -> $mykey is valid but value '$value' does NOT match '$regex'" >&2
+                            else
+                                _vd "  OK: [$section] -> $mykey is valid and value matches '$regex'"
+                            fi
+                        fi
+
+                    fi
+                fi
+            done
+        fi
+    done
+
+    if [ $iErr -gt 0 ]; then
+        echo "RESULT: Errors were found for $myinifile" >&2
+    else
+        _vd "RESULT: OK, Ini file $myinifile looks fine."
+    fi
+    return $iErr
+
+}
+
+# ----------------------------------------------------------------------