diff --git a/tests/getfile.sh b/tests/getfile.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6bd3a5b8b7e98cb710cbb69b4de01ed0e531b5a2
--- /dev/null
+++ b/tests/getfile.sh
@@ -0,0 +1,320 @@
+#!/usr/bin/env bash
+# ======================================================================
+#
+# API CLIENT :: GET A CI FILE FROM PACKAGE SERVER
+#
+# Source: https://git-repo.iml.unibe.ch/iml-open-source/imldeployment-client/
+# ----------------------------------------------------------------------
+# 2021-03-31  v1.0  <axel.hahn@iml.unibe.ch>  init
+# 2021-04-13  v1.1  <axel.hahn@iml.unibe.ch>  add support for custom config
+# 2021-04-15  v1.2  <axel.hahn@iml.unibe.ch>  added debugging of curl request
+# 2021-10-14  v1.3  <axel.hahn@iml.unibe.ch>  add nanoseconds in hashed base data
+# 2023-02-14  v1.4  <axel.hahn@unibe.ch>      compatibility to openssl v3 
+# ======================================================================
+
+# ----------------------------------------------------------------------
+# CONFIG
+# ----------------------------------------------------------------------
+
+version="v1.4"
+about="CI PACKAGE GETTER $version;
+(c) 2021 Institute for Medical Education (IML); University of Bern;
+GNU GPL 3.0"
+
+line="----------------------------------------------------------------------"
+bDebug=0
+customconfig=
+
+. $0.cfg
+
+# ----------------------------------------------------------------------
+# FUNCTIONS
+# ----------------------------------------------------------------------
+
+function showhelp(){
+self=$( basename $0 )
+echo "$line
+$about
+$line
+
+Get packages from a software sattelite of IML ci server.
+
+SYNTAX:
+
+  $self [OPTIONS]
+
+OPTIONS:
+
+  -h          Show this help
+  -v          Show version
+
+  -c CFGFILE  load custom config file after defaults in $self.cfg
+  -d          enable debug infos
+  -e PHASE    phase; overrides env variable IMLCI_PHASE
+  -f FILE     filename to get (without path); overrides env variable IMLCI_FILE
+  -l ITEM     list
+  -o OUTFILE  optional output file
+  -p PROJECT  ci project id; overrides env variable IMLCI_PROJECT
+  -s SECRET   override secret in IMLCI_PKG_SECRET
+  -u URL      URL of iml ci server without trailing /; overrides env variable IMLCI_URL
+  
+VALUES:
+
+  CFGFILE     custom config file. It is useful to handle files of different 
+              projects on a server.
+  PHASE       is a phase of the ci server; one of preview|stage|live
+  FILE        is a filename without path that was created by ci server.
+  OUTFILE     Output file. It can countain a path. If none is given the filename
+              will be taken from FILE and stored in current directory
+  PROJECT     project id of the ci server
+  SECRET      secret to access project data on package server. Your given secret
+              must match the secret on package server to get access to any url.
+  ITEM        type what to list; one of phases|projects|files
+              To list projects a phase must be set.
+              To list files a phase and a project must be set.
+
+DEFAULTS:
+
+  You don't need to set all values by command line. Use a config to set defaults
+  $0.cfg
+
+EXAMPLES:
+
+  If url, secret, project and phase are set in the config you can operate by
+  setting the filename to request.
+
+  $self -f FILE 
+    downloads FILE to the current dir.
+
+  $self -f FILE -o my-own-filename.tgz 
+    downloads FILE as my-own-filename.tgz
+
+  $self -f ALL 
+    there is a special file ALL; it fetches all filenames by executing a directory 
+    listing and then downloads all remote files with their original name
+
+  $self -e preview -l projects
+    list existing projects in phase preview
+
+  $self -l files
+    list existing files of current project
+
+  Remark: The directory listing can be turned off on the package server and
+  results in a 403 status.
+"
+}
+
+# make an http request to fetch the software
+#
+# param  string  method; should be GET
+# param  string  request url (without protocol and server)
+# param  string  optional: filename for output data
+# param  string  optional: secret; default: it will be generated
+#
+# global int     bDebug  (0|1)
+# global string  line    string for a line with dashes
+function makeRequest(){
+
+  local apiMethod=$1
+  local apiRequest=$2
+  local outfile=$3
+  local secret=$4
+
+  # local outfile=$( mktemp )
+
+  if [ $bDebug = 1 ]; then
+    echo $line
+    echo "$apiMethod ${apiHost}${apiRequest}"
+    echo $line
+  fi
+
+  if [ ! -z "$secret" ]; then
+
+    # --- date in http format
+    LANG=en_EN
+    # export TZ=GMT
+    apiTS=$(date "+%a, %d %b %Y %H:%M:%S.%N %Z")
+
+
+# --- generate data to hash: method + uri + timestamp; delimited with line break
+data="${apiMethod}
+${apiRequest}
+${apiTS}
+"
+    # these ase non critical data ... it does not show the ${secret}
+    if [ "$bDebug" = "1" ]; then
+        echo "RAW data for hashed secret:"
+        echo "$data"
+    fi
+
+    # generate hash - split in 2 commands (piping "cut" sends additional line break)
+    myHash=$(echo -n "$data" | openssl dgst -sha1 -hex -hmac "${secret}" | cut -f 2 -d " ")
+    myHash=$(echo -n "$myHash" | base64)
+
+    moreheaders="--fail"
+    test $bDebug = 1 && moreheaders="-i"
+
+    tmpdownloadfile="${outfile}.downloading"
+
+    curl \
+      -H "Accept: application/json" -H "Content-Type: application/json" \
+      -H "Date: ${apiTS}" \
+      -H "Authorization: bash-client:${myHash}" \
+      -X $apiMethod \
+      -o "${tmpdownloadfile}" \
+      $moreheaders \
+      -s \
+      ${IMLCI_URL}${apiRequest}
+
+    rc=$?
+    if [ "$bDebug" = "1" ]; then
+        cat "${tmpdownloadfile}"
+        rm -f "${tmpdownloadfile}"
+        exit 0
+    fi
+
+    if [ $rc -eq 0 ]; then
+        # echo OK.
+
+        # no outfile (= request to a directory)
+        if [ -z "$outfile" ]; then
+            # echo
+            # echo ----- RESPONSE BODY:
+            cat "${tmpdownloadfile}"
+            rm -f "${tmpdownloadfile}" 
+        else
+            mv "${tmpdownloadfile}" "${outfile}"
+            ls -l "${outfile}"
+        fi
+    else
+        echo ERROR: Download failed.
+        exit 1
+    fi
+  else
+    curl\
+      -H "Accept: application/json" -H "Content-Type: application/json" \
+      -X $apiMethod \
+      -o "${tmpdownloadfile}" \
+      ${IMLCI_URL}${apiRequest}
+  fi
+
+}
+
+
+# ----------------------------------------------------------------------
+# MAIN
+# ----------------------------------------------------------------------
+
+if  [ $# -lt 1 ]; then
+  showhelp
+  exit 1
+fi
+
+
+while getopts "c:de:f:hl:o:p:s:u:v" option; do 
+    case ${option} in
+      c) customconfig="$OPTARG" ;;
+      d) bDebug=1 ;;
+      e) export IMLCI_PHASE=$OPTARG ;;
+      f) export IMLCI_FILE=$OPTARG ;;
+      h) showhelp
+         exit 0
+         ;;
+      l) case $OPTARG in
+            phases)
+                IMLCI_PHASE=''
+                IMLCI_PROJECT=''
+                IMLCI_FILE=''
+                ;;
+            projects)
+                IMLCI_PROJECT=''
+                IMLCI_FILE=''
+                ;;
+            files)
+                IMLCI_FILE=''
+                ;;
+            *)
+                echo ERROR: invalid value for option [-l]
+                echo
+                showhelp
+                exit 2
+         esac
+            ;;
+      o) export IMLCI_OUTFILE=$OPTARG ;;
+      p) export IMLCI_PROJECT=$OPTARG ;;
+      s) export IMLCI_PKG_SECRET=$OPTARG ;;
+      u) export IMLCI_URL=$OPTARG ;;
+      v) echo $about; exit 0 ;;
+      *)
+        echo ERROR: invalid option [${option}]
+        echo
+        showhelp
+        exit 2
+    esac
+done
+
+if [ ! -z "$customconfig" ]; then
+    if [ -r "$customconfig" ]; then
+        . "$customconfig" || exit 2
+     else
+        echo "ERROR: unable to read custom config [$customconfig]."
+        exit 2
+     fi
+fi
+
+test -z ${IMLCI_OUTFILE} && IMLCI_OUTFILE=$IMLCI_FILE
+
+if [ $bDebug = 1 ]; then
+    pre=">>>>>> "
+    echo $line
+    echo
+    echo DEBUG INFOS
+    echo
+    echo "${pre} defaults in $0.cfg"
+    cat $0.cfg 2>/dev/null
+    echo
+    if [ ! -z "$customconfig" ]; then 
+        echo "${pre} custom config $customconfig"
+        cat "$customconfig"
+        echo
+    fi
+    echo "${pre} Params (override default values)"
+    echo $*
+    echo
+    echo "${pre} effective values"
+    echo "IMLCI_URL = $IMLCI_URL"
+    echo "IMLCI_PKG_SECRET = $IMLCI_PKG_SECRET"
+    echo "IMLCI_PROJECT = $IMLCI_PROJECT"
+    echo "IMLCI_PHASE = $IMLCI_PHASE"
+    echo "IMLCI_FILE = $IMLCI_FILE"
+    echo "IMLCI_OUTFILE = $IMLCI_OUTFILE"
+
+    echo
+fi
+
+if [ "$IMLCI_FILE" = "ALL" ]; then
+    # echo ALL files were requested ...
+    printf "%-30s" "get list of all files... "
+    tmpfilelist=$( mktemp )
+    $0 -u "${IMLCI_URL}" \
+        -p "${IMLCI_PROJECT}" \
+        -e "${IMLCI_PHASE}" \
+        -s "${IMLCI_PKG_SECRET}" \
+        -l files \
+        -o "${tmpfilelist}"
+    
+    # cat "${tmpfilelist}"
+    cat "${tmpfilelist}" | grep "^file:" | while read fileline
+    do
+        # echo $line
+        myfile=$( echo $fileline | cut -f 2- -d ':' )
+        printf "%-30s" "GET $myfile... "
+        $0 -u "${IMLCI_URL}" \
+            -p "${IMLCI_PROJECT}" \
+            -e "${IMLCI_PHASE}" \
+            -s "${IMLCI_PKG_SECRET}" \
+            -f "${myfile}"
+    done
+else 
+    makeRequest GET "/packages/$IMLCI_PHASE/$IMLCI_PROJECT/$IMLCI_FILE" "$IMLCI_OUTFILE" "$IMLCI_PKG_SECRET"
+fi
diff --git a/tests/getfile.sh.cfg b/tests/getfile.sh.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..f614f3d13e4104fe390e6f2be7fc093510f9dfa4
--- /dev/null
+++ b/tests/getfile.sh.cfg
@@ -0,0 +1,4 @@
+#
+IMLCI_PKG_SECRET=myapikey
+IMLCI_URL=http://localhost:8001
+IMLCI_PHASE=test
diff --git a/tests/readme.md b/tests/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..9d2cd996bfd3e3721c6ce2893e7dee433c558984
--- /dev/null
+++ b/tests/readme.md
@@ -0,0 +1,9 @@
+# Hints
+
+getfile.sh is part of the deployment clients that fatches the packages from package server.
+To get/ update the script
+
+wget -O getfile.sh "https://git-repo.iml.unibe.ch/iml-open-source/imldeployment-client/-/raw/master/bin/getfile.sh?ref_type=heads"
+chmod 755 getfile.sh
+
+see also ../docs/40_Usage.md.