This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch airflow-modifications
in repository https://gitbox.apache.org/repos/asf/airflow-openldap.git

commit 9b18b3b205d7c19786d529a7428b3be452ef3add
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Jul 4 15:52:23 2020 +0200

    Add Airflow-specific modifications - image can be build on its own
    
    It uses only officially released code.
    
    Part of https://github.com/apache/airflow/issues/9401
---
 .idea/.gitignore                                   |   8 +
 .idea/docker-openldap.iml                          |   9 +
 .idea/misc.xml                                     |   6 +
 .idea/modules.xml                                  |   8 +
 .idea/vcs.xml                                      |   6 +
 build_and_push.sh                                  |  38 +
 image/Dockerfile                                   |  39 +-
 image/base-image/Dockerfile                        |  10 +
 image/base-image/build.sh                          |  62 ++
 image/base-image/file/dpkg_nodoc                   |   9 +
 image/base-image/file/dpkg_nolocales               |   2 +
 .../base-image/service-available/:cron/download.sh |   4 +
 .../base-image/service-available/:cron/install.sh  |  14 +
 .../base-image/service-available/:cron/process.sh  |   4 +
 .../base-image/service-available/:cron/startup.sh  |  12 +
 .../:logrotate/assets/config/logrotate.conf        |  36 +
 .../:logrotate/assets/config/logrotate_syslogng    |  39 +
 .../service-available/:logrotate/download.sh       |   4 +
 .../service-available/:logrotate/install.sh        |   4 +
 .../service-available/:logrotate/startup.sh        |   6 +
 .../service-available/:runit/download.sh           |   4 +
 .../:ssl-tools/assets/cfssl-default-env            |  44 +
 .../:ssl-tools/assets/default-ca/README.md         |   2 +
 .../assets/default-ca/config/ca-config.json        |  13 +
 .../assets/default-ca/config/ca-csr.json           |  16 +
 .../assets/default-ca/config/req-csr.json.tmpl     |  19 +
 .../assets/default-ca/default-ca-key.pem           |   6 +
 .../:ssl-tools/assets/default-ca/default-ca.csr    |  11 +
 .../:ssl-tools/assets/default-ca/default-ca.pem    |  18 +
 .../:ssl-tools/assets/default-env                  |  10 +
 .../:ssl-tools/assets/jsonssl-default-env          |  10 +
 .../:ssl-tools/assets/tool/cfssl-helper            | 238 ++++++
 .../:ssl-tools/assets/tool/jsonssl-helper          | 122 +++
 .../:ssl-tools/assets/tool/ssl-auto-renew          | 152 ++++
 .../:ssl-tools/assets/tool/ssl-helper              | 100 +++
 .../service-available/:ssl-tools/download.sh       |  69 ++
 .../service-available/:ssl-tools/startup.sh        |   5 +
 .../:syslog-ng-core/assets/config/syslog-ng.conf   | 152 ++++
 .../assets/config/syslog_ng_default                |  12 +
 .../service-available/:syslog-ng-core/download.sh  |   4 +
 .../service-available/:syslog-ng-core/install.sh   |   8 +
 .../service-available/:syslog-ng-core/process.sh   |   9 +
 .../service-available/:syslog-ng-core/startup.sh   |  22 +
 image/base-image/tool/add-multiple-process-stack   |   4 +
 image/base-image/tool/add-service-available        |  30 +
 image/base-image/tool/complex-bash-env             |  91 ++
 image/base-image/tool/install-service              |  41 +
 image/base-image/tool/log-helper                   | 121 +++
 image/base-image/tool/run                          | 930 +++++++++++++++++++++
 image/base-image/tool/setuser                      |  64 ++
 image/base-image/tool/wait-process                 |  16 +
 51 files changed, 2660 insertions(+), 3 deletions(-)

diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/docker-openldap.iml b/.idea/docker-openldap.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/docker-openldap.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..3a37236
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_14" 
project-jdk-name="14" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..2b3ab4d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/docker-openldap.iml" 
filepath="$PROJECT_DIR$/.idea/docker-openldap.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/build_and_push.sh b/build_and_push.sh
new file mode 100755
index 0000000..cd1ef52
--- /dev/null
+++ b/build_and_push.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+set -euo pipefail
+DOCKERHUB_USER=${DOCKERHUB_USER:="apache"}
+DOCKERHUB_REPO=${DOCKERHUB_REPO:="airflow"}
+OPENLDAP_VERSION="2.4.50"
+AIRFLOW_OPENLDAP_VERSION="2020.07.10"
+COMMIT_SHA=$(git rev-parse HEAD)
+
+cd "$( dirname "${BASH_SOURCE[0]}" )" || exit 1
+
+TAG="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:openldap-${AIRFLOW_OPENLDAP_VERSION}-${OPENLDAP_VERSION}"
+
+cd image
+
+docker build . \
+    --pull \
+    --build-arg "OPENLDAP_VERSION=${OPENLDAP_VERSION}" \
+    --build-arg "AIRFLOW_OPENLDAP_VERSION=${AIRFLOW_OPENLDAP_VERSION}" \
+    --build-arg "COMMIT_SHA=${COMMIT_SHA}" \
+    --tag "${TAG}"
+
+docker push "${TAG}"
diff --git a/image/Dockerfile b/image/Dockerfile
index cfac93e..fc91ae0 100644
--- a/image/Dockerfile
+++ b/image/Dockerfile
@@ -1,6 +1,28 @@
-# Use osixia/light-baseimage
-# sources: https://github.com/osixia/docker-light-baseimage
-FROM osixia/light-baseimage:1.2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+FROM debian:buster-slim
+
+COPY base-image /container
+RUN /container/build.sh
+
+ARG AIRFLOW_OPENLDAP_VERSION
+ARG OPENLDAP_VERSION
+ARG COMMIT_SHA
 
 ARG LDAP_OPENLDAP_GID
 ARG LDAP_OPENLDAP_UID
@@ -8,6 +30,17 @@ ARG LDAP_OPENLDAP_UID
 ARG PQCHECKER_VERSION=2.0.0
 ARG PQCHECKER_MD5=c005ce596e97d13e39485e711dcbc7e1
 
+LABEL org.apache.airflow.component="apache-rat"
+LABEL org.apache.airflow.apache_rat.version="${OPENLDAP_VERSION}"
+LABEL 
org.apache.airflow.airflow_apache_rat.version="${AIRFLOW_OPENLDAP_VERSION}"
+LABEL org.apache.airflow.commit_sha="${COMMIT_SHA}"
+
+MAINTAINER "Apache Airflow Community <[email protected]>"
+
+ENV LANG="en_US.UTF-8" \
+    LANGUAGE="en_US:en" \
+    LC_ALL="en_US.UTF-8"
+
 # Add openldap user and group first to make sure their IDs get assigned 
consistently, regardless of whatever dependencies get added
 # If explicit uid or gid is given, use it.
 RUN if [ -z "${LDAP_OPENLDAP_GID}" ]; then groupadd -g 911 -r openldap; else 
groupadd -r -g ${LDAP_OPENLDAP_GID} openldap; fi \
diff --git a/image/base-image/Dockerfile b/image/base-image/Dockerfile
new file mode 100644
index 0000000..be4b87b
--- /dev/null
+++ b/image/base-image/Dockerfile
@@ -0,0 +1,10 @@
+FROM debian:buster-slim
+
+COPY . /container
+RUN /container/build.sh
+
+ENV LANG="en_US.UTF-8" \
+    LANGUAGE="en_US:en" \
+    LC_ALL="en_US.UTF-8"
+
+ENTRYPOINT ["/container/tool/run"]
diff --git a/image/base-image/build.sh b/image/base-image/build.sh
new file mode 100755
index 0000000..342444e
--- /dev/null
+++ b/image/base-image/build.sh
@@ -0,0 +1,62 @@
+#!/bin/sh -ex
+
+## Add bash tools to /sbin
+ln -s /container/tool/* /sbin/
+
+mkdir -p /container/service
+mkdir -p /container/environment /container/environment/startup
+chmod 700 /container/environment/ /container/environment/startup
+
+groupadd -g 8377 docker_env
+
+# dpkg options
+cp /container/file/dpkg_nodoc /etc/dpkg/dpkg.cfg.d/01_nodoc
+cp /container/file/dpkg_nolocales /etc/dpkg/dpkg.cfg.d/01_nolocales
+
+# General config
+export LC_ALL=C
+export DEBIAN_FRONTEND=noninteractive
+MINIMAL_APT_GET_INSTALL='apt-get install -y --no-install-recommends'
+
+## Prevent initramfs updates from trying to run grub and lilo.
+## 
https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/
+## http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=594189
+export INITRD=no
+printf no > /container/environment/INITRD
+
+apt-get update
+
+## Fix some issues with APT packages.
+## See https://github.com/dotcloud/docker/issues/1024
+dpkg-divert --local --rename --add /sbin/initctl
+ln -sf /bin/true /sbin/initctl
+
+## Replace the 'ischroot' tool to make it always return true.
+## Prevent initscripts updates from breaking /dev/shm.
+## 
https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/
+## https://bugs.launchpad.net/launchpad/+bug/974584
+dpkg-divert --local --rename --add /usr/bin/ischroot
+ln -sf /bin/true /usr/bin/ischroot
+
+## Install apt-utils.
+$MINIMAL_APT_GET_INSTALL apt-utils apt-transport-https ca-certificates locales 
procps dirmngr gnupg iproute2 python3-minimal python3-yaml
+
+## Upgrade all packages.
+apt-get dist-upgrade -y --no-install-recommends -o 
Dpkg::Options::="--force-confold"
+
+# fix locale
+echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
+locale-gen en_US
+update-locale LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8
+
+printf en_US.UTF-8 > /container/environment/LANG
+printf en_US.UTF-8 > /container/environment/LANGUAGE
+printf en_US.UTF-8 > /container/environment/LC_CTYPE
+
+apt-get clean
+rm -rf /tmp/* /var/tmp/*
+rm -rf /var/lib/apt/lists/*
+
+# Remove useless files
+rm -rf /container/file
+rm -rf /container/build.sh /container/Dockerfile
diff --git a/image/base-image/file/dpkg_nodoc b/image/base-image/file/dpkg_nodoc
new file mode 100644
index 0000000..7320020
--- /dev/null
+++ b/image/base-image/file/dpkg_nodoc
@@ -0,0 +1,9 @@
+path-exclude /usr/share/doc/*
+# we need to keep copyright files for legal reasons
+path-include /usr/share/doc/*/copyright
+path-exclude /usr/share/man/*
+path-exclude /usr/share/groff/*
+path-exclude /usr/share/info/*
+# lintian stuff is small, but really unnecessary
+path-exclude /usr/share/lintian/*
+path-exclude /usr/share/linda/*
diff --git a/image/base-image/file/dpkg_nolocales 
b/image/base-image/file/dpkg_nolocales
new file mode 100644
index 0000000..384dc19
--- /dev/null
+++ b/image/base-image/file/dpkg_nolocales
@@ -0,0 +1,2 @@
+path-exclude /usr/share/locale/*
+path-include /usr/share/locale/en*
diff --git a/image/base-image/service-available/:cron/download.sh 
b/image/base-image/service-available/:cron/download.sh
new file mode 100755
index 0000000..b4f814a
--- /dev/null
+++ b/image/base-image/service-available/:cron/download.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+# download cron from apt-get
+LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends cron
diff --git a/image/base-image/service-available/:cron/install.sh 
b/image/base-image/service-available/:cron/install.sh
new file mode 100755
index 0000000..ece2191
--- /dev/null
+++ b/image/base-image/service-available/:cron/install.sh
@@ -0,0 +1,14 @@
+#!/bin/sh -e
+
+chmod 600 /etc/crontab
+
+# Fix https://github.com/phusion/baseimage-docker/issues/345
+sed -i 's/^\s*session\s\+required\s\+pam_loginuid.so/# &/' /etc/pam.d/cron
+
+## Remove useless cron entries.
+# Checks for lost+found and scans for mtab.
+rm -f /etc/cron.daily/standard
+rm -f /etc/cron.daily/upstart
+rm -f /etc/cron.daily/dpkg
+rm -f /etc/cron.daily/password
+rm -f /etc/cron.weekly/fstrim
diff --git a/image/base-image/service-available/:cron/process.sh 
b/image/base-image/service-available/:cron/process.sh
new file mode 100755
index 0000000..6b4d633
--- /dev/null
+++ b/image/base-image/service-available/:cron/process.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+
+exec /usr/sbin/cron -f
diff --git a/image/base-image/service-available/:cron/startup.sh 
b/image/base-image/service-available/:cron/startup.sh
new file mode 100755
index 0000000..5f79925
--- /dev/null
+++ b/image/base-image/service-available/:cron/startup.sh
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+
+# prevent NUMBER OF HARD LINKS > 1 error
+# https://github.com/phusion/baseimage-docker/issues/198
+touch /etc/crontab /etc/cron.d /etc/cron.daily /etc/cron.hourly 
/etc/cron.monthly /etc/cron.weekly
+
+find /etc/cron.d/ -exec touch {} \;
+find /etc/cron.daily/ -exec touch {} \;
+find /etc/cron.hourly/ -exec touch {} \;
+find /etc/cron.monthly/ -exec touch {} \;
+find /etc/cron.weekly/ -exec touch {} \;
diff --git 
a/image/base-image/service-available/:logrotate/assets/config/logrotate.conf 
b/image/base-image/service-available/:logrotate/assets/config/logrotate.conf
new file mode 100644
index 0000000..cb2e78c
--- /dev/null
+++ b/image/base-image/service-available/:logrotate/assets/config/logrotate.conf
@@ -0,0 +1,36 @@
+# see "man logrotate" for details
+# rotate log files weekly
+weekly
+
+# use the syslog group by default, since this is the owning group
+# of /var/log/syslog.
+# su root syslog
+
+# keep 4 weeks worth of backlogs
+rotate 4
+
+# create new (empty) log files after rotating old ones
+create
+
+# uncomment this if you want your log files compressed
+#compress
+
+# packages drop log rotation information into this directory
+include /etc/logrotate.d
+
+# no packages own wtmp, or btmp -- we'll rotate them here
+/var/log/wtmp {
+    missingok
+    monthly
+    create 0664 root utmp
+    rotate 1
+}
+
+/var/log/btmp {
+    missingok
+    monthly
+    create 0660 root utmp
+    rotate 1
+}
+
+# system-specific logs may be configured here
diff --git 
a/image/base-image/service-available/:logrotate/assets/config/logrotate_syslogng
 
b/image/base-image/service-available/:logrotate/assets/config/logrotate_syslogng
new file mode 100644
index 0000000..93d6b02
--- /dev/null
+++ 
b/image/base-image/service-available/:logrotate/assets/config/logrotate_syslogng
@@ -0,0 +1,39 @@
+/var/log/syslog {
+       rotate 7
+       daily
+       missingok
+       notifempty
+       delaycompress
+       compress
+       postrotate
+               if [ -f /var/run/syslog-ng.pid ]; then
+                       kill -HUP `cat /var/run/syslog-ng.pid`
+               fi
+       endscript
+}
+
+/var/log/mail.info
+/var/log/mail.warn
+/var/log/mail.err
+/var/log/mail.log
+/var/log/daemon.log
+/var/log/kern.log
+/var/log/auth.log
+/var/log/user.log
+/var/log/lpr.log
+/var/log/cron.log
+/var/log/debug
+/var/log/messages {
+       rotate 4
+       weekly
+       missingok
+       notifempty
+       compress
+       delaycompress
+       sharedscripts
+       postrotate
+               if [ -f /var/run/syslog-ng.pid ]; then
+                       kill -HUP `cat /var/run/syslog-ng.pid`
+               fi
+       endscript
+}
\ No newline at end of file
diff --git a/image/base-image/service-available/:logrotate/download.sh 
b/image/base-image/service-available/:logrotate/download.sh
new file mode 100755
index 0000000..155c7ab
--- /dev/null
+++ b/image/base-image/service-available/:logrotate/download.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+# download logrotate from apt-get
+LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends logrotate
diff --git a/image/base-image/service-available/:logrotate/install.sh 
b/image/base-image/service-available/:logrotate/install.sh
new file mode 100755
index 0000000..5323d03
--- /dev/null
+++ b/image/base-image/service-available/:logrotate/install.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+rm -f /etc/logrotate.conf
+rm -f /etc/logrotate.d/syslog-ng
diff --git a/image/base-image/service-available/:logrotate/startup.sh 
b/image/base-image/service-available/:logrotate/startup.sh
new file mode 100755
index 0000000..2d2d122
--- /dev/null
+++ b/image/base-image/service-available/:logrotate/startup.sh
@@ -0,0 +1,6 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+ln -sf "${CONTAINER_SERVICE_DIR}/:logrotate/assets/config/logrotate.conf" 
/etc/logrotate.conf
+ln -sf "${CONTAINER_SERVICE_DIR}/:logrotate/assets/config/logrotate_syslogng" 
/etc/logrotate.d/syslog-ng
+
+chmod 444 -R "${CONTAINER_SERVICE_DIR}"/:logrotate/assets/config/*
diff --git a/image/base-image/service-available/:runit/download.sh 
b/image/base-image/service-available/:runit/download.sh
new file mode 100755
index 0000000..e1d66a2
--- /dev/null
+++ b/image/base-image/service-available/:runit/download.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+# download runit from apt-get
+LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends runit
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/cfssl-default-env 
b/image/base-image/service-available/:ssl-tools/assets/cfssl-default-env
new file mode 100644
index 0000000..ed8d49f
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/cfssl-default-env
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Default CA config
+#
+CFSSL_DEFAULT_CACERT="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca.pem"
+CFSSL_DEFAULT_CA_KEY="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca-key.pem"
+CFSSL_DEFAULT_CA_CONFIG="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/config/ca-config.json"
+CFSSL_DEFAULT_CSR="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl"
+
+# default csr file params
+CFSSL_DEFAULT_CA_CSR_CN=${CFSSL_DEFAULT_CA_CSR_CN:-${HOSTNAME}}
+
+CFSSL_DEFAULT_CA_CSR_KEY_ALGO=${CFSSL_DEFAULT_CA_CSR_KEY_ALGO:-"ecdsa"}
+CFSSL_DEFAULT_CA_CSR_KEY_SIZE=${CFSSL_DEFAULT_CA_CSR_KEY_SIZE:-384}
+
+CFSSL_DEFAULT_CA_CSR_ORGANIZATION=${CFSSL_DEFAULT_CA_CSR_ORGANIZATION:-"A1A 
Car Wash"}
+CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT=${CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT:-"Information
 Technology Dep."}
+CFSSL_DEFAULT_CA_CSR_LOCATION=${CFSSL_DEFAULT_CA_CSR_LOCATION:-"Albuquerque"}
+CFSSL_DEFAULT_CA_CSR_STATE=${CFSSL_DEFAULT_CA_CSR_STATE:-"New Mexico"}
+CFSSL_DEFAULT_CA_CSR_COUNTRY=${CFSSL_DEFAULT_CA_CSR_COUNTRY:-"US"}
+
+#
+# General CFSSL config
+#
+
+CFSSL_RETRY=${CFSSL_RETRY:-3}
+CFSSL_RETRY_DELAY=${CFSSL_RETRY_DELAY:-1}
+
+# remote config
+CFSSL_REMOTE=${CFSSL_REMOTE:-}
+CFSSL_REMOTE_HTTPS_CA_CERT=${CFSSL_REMOTE_HTTPS_CA_CERT:-}
+
+# local config
+CFSSL_CA_CERT=${CFSSL_CA_CERT:-${CFSSL_DEFAULT_CACERT}}
+CFSSL_CA_KEY=${CFSSL_CA_KEY:-${CFSSL_DEFAULT_CA_KEY}}
+
+# gencert
+CFSSL_CSR=${CFSSL_CSR:-${CFSSL_DEFAULT_CSR}}
+CFSSL_CSR_JSON=${CFSSL_CSR_JSON:-}
+CFSSL_CONFIG=${CFSSL_CONFIG:-${CFSSL_CA_CONFIG}}
+CFSSL_CONFIG_JSON=${CFSSL_CONFIG_JSON:-${CFSSL_CA_CONFIG_JSON}}
+CFSSL_HOSTNAME=${CFSSL_HOSTNAME:-${HOSTNAME}}
+CFSSL_PROFILE=${CFSSL_PROFILE:-}
+CFSSL_LABEL=${CFSSL_LABEL:-}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/README.md 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/README.md
new file mode 100644
index 0000000..5c4f202
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/default-ca/README.md
@@ -0,0 +1,2 @@
+# How to generate the default CA:
+cfssl gencert -initca config/ca-csr.json | cfssljson -bare default-ca
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-config.json
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-config.json
new file mode 100644
index 0000000..e492de1
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-config.json
@@ -0,0 +1,13 @@
+{
+  "signing": {
+    "default": {
+        "usages": [
+          "signing",
+          "key encipherment",
+          "server auth",
+          "client auth"
+        ],
+        "expiry": "8760h"
+    }
+  }
+}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-csr.json
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-csr.json
new file mode 100644
index 0000000..d9fb6d3
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-csr.json
@@ -0,0 +1,16 @@
+{
+  "CN": "docker-light-baseimage",
+  "key": {
+    "algo": "ecdsa",
+    "size": 384
+  },
+  "names": [
+    {
+      "O": "A1A Car Wash",
+      "OU": "Information Technology Dep.",
+      "L": "Albuquerque",
+      "ST": "New Mexico",
+      "C": "US"
+    }
+  ]
+}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl
new file mode 100644
index 0000000..d9f4545
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl
@@ -0,0 +1,19 @@
+{
+  "CN": "{{ CFSSL_DEFAULT_CA_CSR_CN }}",
+  "hosts": [
+    "{{ CFSSL_DEFAULT_CA_CSR_CN }}"
+  ],
+  "key": {
+    "algo": "{{ CFSSL_DEFAULT_CA_CSR_KEY_ALGO }}",
+    "size": {{ CFSSL_DEFAULT_CA_CSR_KEY_SIZE }}
+  },
+  "names": [
+    {
+      "O": "{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION }}",
+      "OU": "{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT }}",
+      "L": "{{ CFSSL_DEFAULT_CA_CSR_LOCATION }}",
+      "ST": "{{ CFSSL_DEFAULT_CA_CSR_STATE }}",
+      "C": "{{ CFSSL_DEFAULT_CA_CSR_COUNTRY }}"
+    }
+  ]
+}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca-key.pem
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca-key.pem
new file mode 100644
index 0000000..cffc6ed
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca-key.pem
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDC/DGTr5mqDp1hUVqDKmoc8r13ziQ0Gfx20YCmZLR8LNamT7+1y6eSk
+CBTZxWiGLcSgBwYFK4EEACKhZANiAATGX/9dqbqQIJfEpvo/BpozXjW0hQGVlqE5
+iL39jLuC1jx8uG05XZEIB1GwaU98Vs/H9JQf67u+fegh7BqC9gNvIcbnJauYW1Md
+cqyLd2ySGN07ol9uRxk3upBgiTwdWi0=
+-----END EC PRIVATE KEY-----
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.csr
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.csr
new file mode 100644
index 0000000..2de0013
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.csr
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkDCCARYCAQAwgZYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxBMUEgQ2FyIFdh
+c2gxJDAiBgNVBAsTG0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgRGVwLjEUMBIGA1UE
+BxMLQWxidXF1ZXJxdWUxEzARBgNVBAgTCk5ldyBNZXhpY28xHzAdBgNVBAMTFmRv
+Y2tlci1saWdodC1iYXNlaW1hZ2UwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATGX/9d
+qbqQIJfEpvo/BpozXjW0hQGVlqE5iL39jLuC1jx8uG05XZEIB1GwaU98Vs/H9JQf
+67u+fegh7BqC9gNvIcbnJauYW1MdcqyLd2ySGN07ol9uRxk3upBgiTwdWi2gADAK
+BggqhkjOPQQDAwNoADBlAjBiYclv+pS3gnE5p1nGf00IqcJeEK38SuOoAbUrwmNd
+eKyYHiaAnR/XDq/ceD9qMfgCMQCyBTfO6Jy8wzrUSUMDA2CA707I7+rz6iHc+F9T
+EYf0QEb/FzOj1Mt5LpdKqUnL0gg=
+-----END CERTIFICATE REQUEST-----
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.pem
 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.pem
new file mode 100644
index 0000000..ab543a3
--- /dev/null
+++ 
b/image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0zCCAlmgAwIBAgIUCfQ+m0pgZ/BjYAJvxrn/bdGNZokwCgYIKoZIzj0EAwMw
+gZYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxBMUEgQ2FyIFdhc2gxJDAiBgNVBAsT
+G0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgRGVwLjEUMBIGA1UEBxMLQWxidXF1ZXJx
+dWUxEzARBgNVBAgTCk5ldyBNZXhpY28xHzAdBgNVBAMTFmRvY2tlci1saWdodC1i
+YXNlaW1hZ2UwHhcNMTUxMjIzMTM1MzAwWhcNMjAxMjIxMTM1MzAwWjCBljELMAkG
+A1UEBhMCVVMxFTATBgNVBAoTDEExQSBDYXIgV2FzaDEkMCIGA1UECxMbSW5mb3Jt
+YXRpb24gVGVjaG5vbG9neSBEZXAuMRQwEgYDVQQHEwtBbGJ1cXVlcnF1ZTETMBEG
+A1UECBMKTmV3IE1leGljbzEfMB0GA1UEAxMWZG9ja2VyLWxpZ2h0LWJhc2VpbWFn
+ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMZf/12pupAgl8Sm+j8GmjNeNbSFAZWW
+oTmIvf2Mu4LWPHy4bTldkQgHUbBpT3xWz8f0lB/ru7596CHsGoL2A28hxuclq5hb
+Ux1yrIt3bJIY3TuiX25HGTe6kGCJPB1aLaNmMGQwDgYDVR0PAQH/BAQDAgEGMBIG
+A1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFE+l6XolXDAYnGLTl4W6ULKHrm74
+MB8GA1UdIwQYMBaAFE+l6XolXDAYnGLTl4W6ULKHrm74MAoGCCqGSM49BAMDA2gA
+MGUCMQCXLZj8okyxW6UTL7hribUUbu63PbjuwIXnwi420DdNsvA9A7fcQEXScWFL
+XAGC8rkCMGcqwXZPSRfwuI9r+R11gTrP92hnaVxs9sjRikctpkQpOyNlIXFPopFK
+8FdfWPypvA==
+-----END CERTIFICATE-----
diff --git a/image/base-image/service-available/:ssl-tools/assets/default-env 
b/image/base-image/service-available/:ssl-tools/assets/default-env
new file mode 100644
index 0000000..96e5380
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/default-env
@@ -0,0 +1,10 @@
+#!/bin/bash
+SSL_HELPER_TOOL=${SSL_HELPER_TOOL:-"cfssl-helper"}
+
+SSL_HELPER_AUTO_RENEW=${SSL_HELPER_AUTO_RENEW:-false}
+SSL_HELPER_AUTO_RENEW_CRON_EXP=${SSL_HELPER_AUTO_RENEW_CRON_EXP:-"0 0 * * *"} 
# every day at 00:00
+SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED:-}
+SSL_HELPER_AUTO_RENEW_FROM_FILES=${SSL_HELPER_AUTO_RENEW_FROM_FILES:-false}
+SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE:-}
+SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE:-}
+SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE:-}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/jsonssl-default-env 
b/image/base-image/service-available/:ssl-tools/assets/jsonssl-default-env
new file mode 100644
index 0000000..0aa00df
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/jsonssl-default-env
@@ -0,0 +1,10 @@
+#!/bin/bash
+JSONSSL_FILE_DEFAULT="${CONTAINER_SERVICE_DIR}/ssl-tools/assets/certs/certs.json"
+
+JSONSSL_FILE=${JSONSSL_FILE:-} # don't set default immediatly because we print 
a warning in jsonssl-helper
+JSONSSL_HOSTNAME=${JSONSSL_HOSTNAME:-${HOSTNAME}}
+JSONSSL_PROFILE=${JSONSSL_PROFILE:-} # traefik / traefik_up_to_v1_6
+
+JSONSSL_GET_CA_CERT_CMD=${JSONSSL_GET_CA_CERT_CMD:-}
+JSONSSL_GET_CERT_CMD=${JSONSSL_GET_CERT_CMD:-}
+JSONSSL_GET_KEY_CMD=${JSONSSL_GET_KEY_CMD:-}
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/tool/cfssl-helper 
b/image/base-image/service-available/:ssl-tools/assets/tool/cfssl-helper
new file mode 100755
index 0000000..9e14422
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/tool/cfssl-helper
@@ -0,0 +1,238 @@
+#!/bin/bash
+log-helper level eq trace && set -x
+
+# This tool helps to generate tls certificates with cfssl
+# It takes cfssl configuration from environment variable.
+# See cfssl-default-env file
+
+PREFIX=$1
+CERT_FILE=$2
+KEY_FILE=$3
+CA_FILE=$4
+
+log-helper debug "cfssl-helper is launched, everybody on the floor!"
+
+# before 0.2.5 retro compatibility, will be removed.
+mkdir -p "${CONTAINER_SERVICE_DIR}/:cfssl/assets/default-ca"
+ln -sf "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca.pem" 
"${CONTAINER_SERVICE_DIR}/:cfssl/assets/default-ca/default-ca.pem"
+
+if [ -z "${PREFIX}" ] || [ -z "${CERT_FILE}" ] || [ -z "${KEY_FILE}" ] || [ -z 
"${CA_FILE}" ]; then
+    log-helper error "Usage: cfssl-helper prefix cert_file key_file ca_file"
+    exit 1
+fi
+
+if [ ! -e "${CERT_FILE}" ] && [ ! -e "${KEY_FILE}" ]; then
+    
+    log-helper info "No certificate file and certificate key provided, 
generate:"
+    log-helper info "${CERT_FILE} and ${KEY_FILE}"
+    
+    LOG_LEVEL_PARAM=""
+    
+    case ${CONTAINER_LOG_LEVEL} in
+        0 )
+        LOG_LEVEL_PARAM="-loglevel 4";;
+        1 )
+        LOG_LEVEL_PARAM="-loglevel 3";;
+        2 )
+        LOG_LEVEL_PARAM="-loglevel 2";;
+        3 )
+        LOG_LEVEL_PARAM="-loglevel 1";;
+        4 )
+        LOG_LEVEL_PARAM="-loglevel 0";;
+        5 )
+        LOG_LEVEL_PARAM="-loglevel 0";;
+    esac
+    
+    # set env vars
+    PREFIX=${PREFIX^^} # uppercase
+    
+    # search for prefixed env var first
+    
+    # set prefix variable name
+    # example : PREFIX_CFSSL_REMOTE='MARIADB_CFSSL_REMOTE'
+    PREFIX_CFSSL_REMOTE=${PREFIX}_CFSSL_REMOTE
+    PREFIX_CFSSL_REMOTE_HTTPS_CA_CERT=${PREFIX}_CFSSL_REMOTE_HTTPS_CA_CERT
+    PREFIX_CFSSL_CA_CERT=${PREFIX}_CFSSL_CA_CERT
+    PREFIX_CFSSL_CA_KEY=${PREFIX}_CFSSL_CA_KEY
+    PREFIX_CFSSL_CSR=${PREFIX}_CFSSL_CSR
+    PREFIX_CFSSL_CSR_JSON=${PREFIX}_CFSSL_CSR_JSON
+    PREFIX_CFSSL_CONFIG=${PREFIX}_CFSSL_CONFIG
+    PREFIX_CFSSL_CONFIG_JSON=${PREFIX}_CFSSL_CONFIG_JSON
+    PREFIX_CFSSL_HOSTNAME=${PREFIX}_CFSSL_HOSTNAME
+    PREFIX_CFSSL_PROFILE=${PREFIX}_CFSSL_PROFILE
+    PREFIX_CFSSL_LABEL=${PREFIX}_CFSSL_LABEL
+    PREFIX_CFSSL_RETRY=${PREFIX}_CFSSL_RETRY
+    PREFIX_CFSSL_RETRY_DELAY=${PREFIX}_CFSSL_RETRY_DELAY
+    
+    # assign CFSSL_REMOTE=${!PREFIX_CFSSL_REMOTE} if value is not empty 
otherwise CFSSL_REMOTE=CFSSL_REMOTE
+    CFSSL_REMOTE=${!PREFIX_CFSSL_REMOTE:-$CFSSL_REMOTE}
+    
CFSSL_REMOTE_HTTPS_CA_CERT=${!PREFIX_CFSSL_REMOTE_HTTPS_CA_CERT:-$CFSSL_REMOTE_HTTPS_CA_CERT}
+    CFSSL_CA_CERT=${!PREFIX_CFSSL_CA_CERT:-$CFSSL_CA_CERT}
+    CFSSL_CA_KEY=${!PREFIX_CFSSL_CA_KEY:-$CFSSL_CA_KEY}
+    CFSSL_CSR=${!PREFIX_CFSSL_CSR:-$CFSSL_CSR}
+    CFSSL_CSR_JSON=${!PREFIX_CFSSL_CSR_JSON:-$CFSSL_CSR_JSON}
+    CFSSL_CONFIG=${!PREFIX_CFSSL_CONFIG:-$CFSSL_CONFIG}
+    CFSSL_CONFIG_JSON=${!PREFIX_CFSSL_CONFIG_JSON:-$CFSSL_CONFIG_JSON}
+    CFSSL_HOSTNAME=${!PREFIX_CFSSL_HOSTNAME:-$CFSSL_HOSTNAME}
+    CFSSL_PROFILE=${!PREFIX_CFSSL_PROFILE:-$CFSSL_PROFILE}
+    CFSSL_LABEL=${!PREFIX_CFSSL_LABEL:-$CFSSL_LABEL}
+    CFSSL_RETRY=${!PREFIX_CFSSL_RETRY:-$CFSSL_RETRY}
+    CFSSL_RETRY_DELAY=${!PREFIX_CFSSL_RETRY_DELAY:-$CFSSL_RETRY_DELAY}
+    
+    source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/cfssl-default-env"
+    
+    # set csr file
+    CSR_FILE="/tmp/csr-file"
+    if [ -n "${CFSSL_CSR_JSON}" ]; then
+        log-helper debug "use CFSSL_CSR_JSON value as csr file"
+        echo "${CFSSL_CSR_JSON}" > "${CSR_FILE}"
+        elif [ -n "${CFSSL_CSR}" ]; then
+        log-helper debug "use ${CFSSL_CSR} as csr file"
+        cp -f "${CFSSL_CSR}" "${CSR_FILE}"
+        
+        # it's the default csr
+        if [ "${CFSSL_CSR}" = "${CFSSL_DEFAULT_CSR}" ]; then
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_CN 
}}|${CFSSL_DEFAULT_CA_CSR_CN}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_KEY_ALGO 
}}|${CFSSL_DEFAULT_CA_CSR_KEY_ALGO}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_KEY_SIZE 
}}|${CFSSL_DEFAULT_CA_CSR_KEY_SIZE}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_CERT_ORGANIZATION_UNIT 
}}|${CFSSL_CERT_ORGANIZATION_UNIT}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION 
}}|${CFSSL_DEFAULT_CA_CSR_ORGANIZATION}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT 
}}|${CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_LOCATION 
}}|${CFSSL_DEFAULT_CA_CSR_LOCATION}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_STATE 
}}|${CFSSL_DEFAULT_CA_CSR_STATE}|g" "${CSR_FILE}"
+            sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_COUNTRY 
}}|${CFSSL_DEFAULT_CA_CSR_COUNTRY}|g" "${CSR_FILE}"
+        fi
+    else
+        log-helper error "error: no csr file provided"
+        log-helper error "CFSSL_CSR_JSON and CFSSL_CSR are empty"
+        exit 1
+    fi
+    
+    # generate cert
+    CONFIG_FILE="/tmp/config-file"
+    CERT_NAME="cert"
+    
+    REMOTE_PARAM=""
+    CA_CERT_PARAM=""
+    CA_KEY_PARAM=""
+    CONFIG_PARAM=""
+    HOSTNAME_PARAM=""
+    PROFILE_PARAM=""
+    LABEL_PARAM=""
+    
+    if [ -n "${CFSSL_REMOTE}" ]; then
+        REMOTE_PARAM="-remote=${CFSSL_REMOTE}"
+        
+        # add remote https ca cert to known certificates if not empty
+        if [ -n "${CFSSL_REMOTE_HTTPS_CA_CERT}" ]; then
+            if [ -e "${CFSSL_REMOTE_HTTPS_CA_CERT}" ]; then
+                [[ ! -d "/etc/ssl/certs/" ]] && mkdir -p /etc/ssl/certs/
+                cat "${CFSSL_REMOTE_HTTPS_CA_CERT}" >> 
/etc/ssl/certs/ca-certificates.crt
+            else
+                log-helper error "error: remote https ca cert file 
${CFSSL_REMOTE_HTTPS_CA_CERT} not found"
+            fi
+        fi
+        
+    else
+        
+        # files path with : may cause issue with cfssl tools due to :
+        # ReadBytes - 
https://github.com/cloudflare/cfssl/blob/master/helpers/helpers.go#L573
+        # : is used to split env from file path
+        # so we copy ca cert and key to tmp
+        if [ -n "${CFSSL_CA_CERT}" ]; then
+            
+            CFSSL_CA_CERT_FILE="/tmp/ca-cert-file"
+            cp -f "${CFSSL_CA_CERT}" "${CFSSL_CA_CERT_FILE}"
+            chmod 644 "${CFSSL_CA_CERT_FILE}"
+            
+            CA_CERT_PARAM="-ca ${CFSSL_CA_CERT_FILE}"
+        fi
+        
+        if [ -n "${CFSSL_CA_KEY}" ]; then
+            
+            CFSSL_CA_KEY_FILE="/tmp/ca-key-file"
+            cp -f "${CFSSL_CA_KEY}" "${CFSSL_CA_KEY_FILE}"
+            chmod 600 "${CFSSL_CA_CERT_FILE}"
+            
+            CA_KEY_PARAM="-ca-key ${CFSSL_CA_KEY_FILE}"
+        fi
+        
+    fi
+    
+    if [ -n "${CFSSL_CONFIG_JSON}" ]; then
+        log-helper debug "use CFSSL_CONFIG_JSON value as config file"
+        echo "${CFSSL_CONFIG_JSON}" > "${CONFIG_FILE}"
+        CONFIG_PARAM="-config ${CONFIG_FILE}"
+        
+        elif [ -n "${CFSSL_CONFIG}" ]; then
+        log-helper debug "use ${CFSSL_CONFIG} as config file"
+        cp -f "${CFSSL_CONFIG}" "${CONFIG_FILE}"
+        CONFIG_PARAM="-config ${CONFIG_FILE}"
+    fi
+
+    if [ -n "$ADDITIONAL_HOSTNAMES" ]; then
+        log-helper debug "additional hostnames found"
+        CFSSL_HOSTNAME="${CFSSL_HOSTNAME},${ADDITIONAL_HOSTNAMES}"
+    fi
+    
+    [[ -n "${CFSSL_HOSTNAME}" ]] && HOSTNAME_PARAM="-hostname 
${CFSSL_HOSTNAME}"
+    [[ -n "${CFSSL_PROFILE}" ]] && PROFILE_PARAM="-profile ${CFSSL_PROFILE}"
+    [[ -n "${CFSSL_LABEL}" ]] && LABEL_PARAM="-label ${CFSSL_LABEL}"
+    
+    retry=0
+    while [  $retry -lt "${CFSSL_RETRY}" ]; do
+        log-helper debug "cfssl gencert ${LOG_LEVEL_PARAM} ${REMOTE_PARAM} 
${CA_CERT_PARAM} ${CA_KEY_PARAM} ${CONFIG_PARAM} ${HOSTNAME_PARAM} 
${PROFILE_PARAM} ${LABEL_PARAM} ${CSR_FILE} | cfssljson -bare /tmp/${CERT_NAME}"
+        eval cfssl gencert "${LOG_LEVEL_PARAM}" "${REMOTE_PARAM}" 
"${CA_CERT_PARAM}" "${CA_KEY_PARAM}" "${CONFIG_PARAM}" "${HOSTNAME_PARAM}" 
"${PROFILE_PARAM}" "${LABEL_PARAM}" "${CSR_FILE}" | cfssljson -bare 
"/tmp/${CERT_NAME}" && break
+        sleep "${CFSSL_RETRY_DELAY}"
+        ((retry++))
+    done
+    
+    # move generated files
+    [[ ! -e "/tmp/${CERT_NAME}.pem" ]] && exit 1
+    log-helper debug "move /tmp/${CERT_NAME}.pem to ${CERT_FILE}"
+    mv "/tmp/${CERT_NAME}.pem" "${CERT_FILE}"
+    
+    log-helper debug "move /tmp/${CERT_NAME}-key.pem to ${KEY_FILE}"
+    mv "/tmp/${CERT_NAME}-key.pem" "${KEY_FILE}"
+    
+    # if ca file don't exists
+    if [ ! -e "${CA_FILE}" ]; then
+        
+        if [ -n "${CFSSL_REMOTE}" ]; then
+            log-helper debug "Get CA certificate from ${CFSSL_REMOTE}"
+            
+            retry=0
+            while [  $retry -lt "${CFSSL_RETRY}" ]; do
+                log-helper debug "cfssl info ${LOG_LEVEL_PARAM} 
${REMOTE_PARAM} ${CONFIG_PARAM} ${PROFILE_PARAM} ${LABEL_PARAM}"
+                eval cfssl info "${LOG_LEVEL_PARAM}" "${REMOTE_PARAM}" 
"${CONFIG_PARAM}" "${PROFILE_PARAM}" "${LABEL_PARAM}" | sed -e 
"s/.*certificate\":\"\(.*-----\)\".*/\1/g" | sed 's/\\n/\n/g' > "${CA_FILE}" && 
log-helper debug "CA certificate returned save as ${CA_FILE}" && break
+                sleep "${CFSSL_RETRY_DELAY}"
+                ((retry++))
+            done
+            
+            [[ ! -e "${CA_FILE}" ]] && exit 1
+            
+            elif [ -n "${CFSSL_CA_CERT}" ]; then
+            log-helper info "Link ${CFSSL_CA_CERT} to ${CA_FILE}"
+            ln -sf "${CFSSL_CA_CERT}" "${CA_FILE}"
+        fi
+        
+    fi
+    
+    # delete tmp files
+    rm -f /tmp/${CERT_NAME}.csr ${CONFIG_FILE} "${CSR_FILE}"
+    [[ -e "${CFSSL_CA_CERT_FILE}" ]] && rm "${CFSSL_CA_CERT_FILE}"
+    [[ -e "${CFSSL_CA_KEY_FILE}" ]] && rm "${CFSSL_CA_KEY_FILE}"
+    
+    log-helper debug "done :)"
+    
+    elif [ ! -e "${KEY_FILE}" ]; then
+    log-helper error "Certificate file ${CERT_FILE} exists but not key file 
${KEY_FILE}"
+    exit 1
+    elif [ ! -e "${CERT_FILE}" ]; then
+    log-helper error "Key file ${KEY_FILE} exists but not certificate file 
${CERT_FILE}"
+    exit 1
+else
+    log-helper debug "Files ${CERT_FILE} and ${KEY_FILE} exists, fix files 
permissions"
+    chmod 644 "${CERT_FILE}"
+    chmod 600 "${KEY_FILE}"
+fi
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/tool/jsonssl-helper 
b/image/base-image/service-available/:ssl-tools/assets/tool/jsonssl-helper
new file mode 100755
index 0000000..abd524a
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/tool/jsonssl-helper
@@ -0,0 +1,122 @@
+#!/bin/bash
+log-helper level eq trace && set -x
+
+# This tool helps get certificates from json files
+# like kubernetes secrets or traefik acme.json
+# It takes its configuration from environment variable.
+# See json-default-env file
+
+PREFIX=$1
+CERT_FILE=$2
+KEY_FILE=$3
+CA_FILE=$4
+
+log-helper debug "jsonssl-helper is launched, everybody on the floor!"
+
+if [ -z "${PREFIX}" ] || [ -z "${CERT_FILE}" ] || [ -z "${KEY_FILE}" ] || [ -z 
"${CA_FILE}" ]; then
+    log-helper error "Usage: jsonssl-helper prefix cert_file key_file ca_file"
+    exit 1
+fi
+
+if [ ! -e "${CERT_FILE}" ] && [ ! -e "${KEY_FILE}" ]; then
+    
+    # set env vars
+    PREFIX=${PREFIX^^} # uppercase
+    
+    # search for prefixed env var first
+    
+    # set prefix variable name
+    # example : PREFIX_JSONSSL_FILE='MARIADB_JSONSSL_FILE'
+    PREFIX_JSONSSL_FILE=${PREFIX}_JSONSSL_FILE
+    PREFIX_JSONSSL_HOSTNAME=${PREFIX}_JSONSSL_HOSTNAME
+    
+    PREFIX_JSONSSL_PROFILE=${PREFIX}_JSONSSL_PROFILE
+    PREFIX_JSONSSL_GET_CA_CERT_CMD=${PREFIX}_JSONSSL_GET_CA_CERT_CMD
+    PREFIX_JSONSSL_GET_CERT_CMD=${PREFIX}_JSONSSL_GET_CERT_CMD
+    PREFIX_JSONSSL_GET_KEY_CMD=${PREFIX}_JSONSSL_GET_KEY_CMD
+    
+    # assign JSONSSL_FILE=${!PREFIX_JSONSSL_FILE} if value is not empty 
otherwise JSONSSL_FILE=JSONSSL_FILE
+    JSONSSL_FILE=${!PREFIX_JSONSSL_FILE:-$JSONSSL_FILE}
+    JSONSSL_HOSTNAME=${!PREFIX_JSONSSL_HOSTNAME:-$JSONSSL_HOSTNAME}
+    
+    JSONSSL_PROFILE=${!PREFIX_JSONSSL_PROFILE:-$JSONSSL_PROFILE}
+    
JSONSSL_GET_CA_CERT_CMD=${!PREFIX_JSONSSL_GET_CA_CERT_CMD:-$JSONSSL_GET_CA_CERT_CMD}
+    JSONSSL_GET_CERT_CMD=${!PREFIX_JSONSSL_GET_CERT_CMD:-$JSONSSL_GET_CERT_CMD}
+    JSONSSL_GET_KEY_CMD=${!PREFIX_JSONSSL_GET_KEY_CMD:-$JSONSSL_GET_KEY_CMD}
+    
+    source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/jsonssl-default-env"
+    
+    if [ -z "${JSONSSL_FILE}" ]; then
+        log-helper info "Variable JSONSSL_FILE is empty, set to default 
location:"
+        log-helper info "JSONSSL_FILE=${JSONSSL_FILE_DEFAULT}"
+        JSONSSL_FILE=${JSONSSL_FILE_DEFAULT}
+    fi
+    
+    if [ ! -e "${JSONSSL_FILE}" ]; then
+        log-helper error "JSONSSL_FILE file '${JSONSSL_FILE}' not found"
+        exit 1
+    fi
+    
+    # Json file profile, only traefik for now
+    if [ "${JSONSSL_PROFILE,,}" = "traefik" ]; then
+        # Let's Encrypt CA certificate is in cert file after the domain 
certificate.
+        # So we took what's after the first cert.
+        JSONSSL_GET_CA_CERT_CMD="awk '{if(found) print} /END 
CERTIFICATE/{found=1}' ${CERT_FILE}"
+        
+        JSONSSL_GET_CERT_CMD="cat ${JSONSSL_FILE} | jq -r '[.Certificates[]] | 
map(select(.Domain.Main == \"${JSONSSL_HOSTNAME}\")) | .[0].Certificate' | 
base64 -d"
+        JSONSSL_GET_KEY_CMD="cat ${JSONSSL_FILE} | jq -r '[.Certificates[]] | 
map(select(.Domain.Main == \"${JSONSSL_HOSTNAME}\")) | .[0].Key' | base64 -d"
+        elif [ "${JSONSSL_PROFILE,,}" = "traefik_up_to_v1_6" ]; then
+        # Let's Encrypt CA certificate is in cert file after the domain 
certificate.
+        # So we took what's after the first cert.
+        JSONSSL_GET_CA_CERT_CMD="awk '{if(found) print} /END 
CERTIFICATE/{found=1}' ${CERT_FILE}"
+        
+        JSONSSL_GET_CERT_CMD="cat ${JSONSSL_FILE} | jq -r 
'[.[\"DomainsCertificate\"].Certs[].Certificate] | map(select(.Domain == 
\"${JSONSSL_HOSTNAME}\")) | .[0].Certificate' | base64 -d"
+        JSONSSL_GET_KEY_CMD="cat ${JSONSSL_FILE} | jq -r 
'[.[\"DomainsCertificate\"].Certs[].Certificate] | map(select(.Domain == 
\"${JSONSSL_HOSTNAME}\")) | .[0].PrivateKey' | base64 -d"
+    fi
+    
+    log-helper debug "Run JSONSSL_GET_CERT_CMD: ${JSONSSL_GET_CERT_CMD}"
+    log-helper debug "put return in ${CERT_FILE}"
+    eval "${JSONSSL_GET_CERT_CMD}" > "${CERT_FILE}"
+    
+    if [ ! -s "$CERT_FILE" ]; then
+        log-helper error "Generated file '${CERT_FILE}' is empty"
+        log-helper error "Set loglevel to debug for more information"
+        exit 1
+    fi
+    
+    log-helper debug "Run JSONSSL_GET_KEY_CMD: ${JSONSSL_GET_KEY_CMD}"
+    log-helper debug "put return in ${KEY_FILE}"
+    eval "$JSONSSL_GET_KEY_CMD" > "${KEY_FILE}"
+    
+    if [ ! -s "${KEY_FILE}" ]; then
+        log-helper error "Generated file '${KEY_FILE}' is empty"
+        log-helper error "Set loglevel to debug for more information"
+        exit 1
+    fi
+    
+    # if CA cert doesn't exist
+    if [ ! -e "$CA_FILE" ]; then
+        log-helper debug "Run JSONSSL_GET_CA_CERT_CMD: 
${JSONSSL_GET_CA_CERT_CMD}"
+        log-helper debug "put return in ${CA_FILE}"
+        eval "$JSONSSL_GET_CA_CERT_CMD" > "${CA_FILE}"
+        
+        if [ ! -s "$CA_FILE" ]; then
+            log-helper error "Generated file '${CA_FILE}' is empty"
+            log-helper error "Set loglevel to debug for more information"
+            exit 1
+        fi
+    fi
+    
+    log-helper debug "done :)"
+    
+    elif [ ! -e "${KEY_FILE}" ]; then
+    log-helper error "Certificate file ${CERT_FILE} exists but not key file 
${KEY_FILE}"
+    exit 1
+    elif [ ! -e "${CERT_FILE}" ]; then
+    log-helper error "Key file ${KEY_FILE} exists but not certificate file 
${CERT_FILE}"
+    exit 1
+else
+    log-helper debug "Files ${CERT_FILE} and ${KEY_FILE} exists, fix files 
permissions"
+    chmod 644 "${CERT_FILE}"
+    chmod 600 "${KEY_FILE}"
+fi
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/tool/ssl-auto-renew 
b/image/base-image/service-available/:ssl-tools/assets/tool/ssl-auto-renew
new file mode 100755
index 0000000..78d8f7d
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/tool/ssl-auto-renew
@@ -0,0 +1,152 @@
+#!/bin/bash -e
+
+# This file aims to be called by a cron task
+# and not directly. See ssl-helper.
+
+source /container/run/environment.sh
+
+SSL_HELPER_TOOL=$1
+PREFIX=$2
+CERT_FILE=$3
+KEY_FILE=$4
+CA_FILE=$5
+IMPACTED_SERVICES=$6
+JSONSSL_FILE=$7
+FROM_FILES=$8
+CERT_FROM_FILE=$9
+KEY_FROM_FILE=${10}
+CA_CERT_FROM_FILE=${11}
+
+function stop_impacted_services() {
+    # Stop impacted services
+    if [ -n "${IMPACTED_SERVICES}" ]; then
+        log-helper info "Services to stop: ${IMPACTED_SERVICES}"
+        
+        impacted_services_table=("${IMPACTED_SERVICES}")
+        for service in "${impacted_services_table[@]}"
+        do
+            log-helper info "Stopping ${service}..."
+            sv stop "/container/run/process/${service}"
+        done
+        
+        log-helper info "All services are stopped"
+    fi
+}
+
+function start_impacted_services() {
+    # restart impacted services
+    if [ -n "${IMPACTED_SERVICES}" ]; then
+        
+        impacted_services_table=("${IMPACTED_SERVICES}")
+        for service in "${impacted_services_table[@]}"
+        do
+            log-helper info "Starting ${service}..."
+            sv start "/container/run/process/${service}"
+        done
+        
+        log-helper info "All services are started"
+    fi
+}
+
+# renew from container files
+if [ "${FROM_FILES,,}" = "true" ]; then
+    
+    log-helper info "Check renew from files"
+    renew=false
+    
+    # File previous md5
+    CERT_PREVIOUS_MD5=$(cat 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5") || true
+    KEY_PREVIOUS_MD5=$(cat 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5") || true
+    CA_CERT_PREVIOUS_MD5=$(cat 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5") || true
+    
+    # from file current md5
+    FROM_CERT_MD5=$(md5sum "${CERT_FROM_FILE}" | awk '{ print $1 }')
+    FROM_KEY_MD5=$(md5sum "${KEY_FROM_FILE}" | awk '{ print $1 }')
+    FROM_CA_CERT_MD5=$(md5sum "${CA_CERT_FROM_FILE}" | awk '{ print $1 }')
+    
+    [[ "$CERT_PREVIOUS_MD5" != "$FROM_CERT_MD5" ]] && renew=true
+    [[ "$KEY_PREVIOUS_MD5" != "$FROM_KEY_MD5" ]] && renew=true
+    [[ "$CA_CERT_PREVIOUS_MD5" != "$FROM_CA_CERT_MD5" ]] && renew=true
+    
+    if ! $renew; then
+        log-helper info "Certificate files are identicals"
+        exit 0
+    fi
+    
+    log-helper info "Certificate files are differents"
+    
+    stop_impacted_services
+    
+    if [ "${CERT_FROM_FILE}" != "${CERT_FILE}" ]; then
+        log-helper info "Copy ${CERT_FROM_FILE} to ${CERT_FILE}"
+        cp -f "${CERT_FROM_FILE}" "${CERT_FILE}"
+    fi
+    
+    if [ "${KEY_FROM_FILE}" != "${KEY_FILE}" ]; then
+        log-helper info "Copy ${KEY_FROM_FILE} to ${KEY_FILE}"
+        cp -f "${KEY_FROM_FILE}" "${KEY_FILE}"
+    fi
+    
+    if [ "${CA_CERT_FROM_FILE}" != "${CA_FILE}" ]; then
+        log-helper info "Copy ${CA_CERT_FROM_FILE} to ${CA_FILE}"
+        cp -f "${CA_CERT_FROM_FILE}" "${CA_FILE}"
+    fi
+    
+    log-helper info "Update file md5 with new values"
+    echo "${FROM_CERT_MD5}" > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5"
+    echo "${FROM_KEY_MD5}" > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5"
+    echo "${FROM_CA_CERT_MD5}" > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5"
+    
+    start_impacted_services
+    
+    # renew with cfssl or jsonssl
+else
+    log-helper info "Check renew for cfssl or jsonssl"
+    
+    cert_ok=false
+    ca_ok=false
+    
+    # the certificate will expired in the next day
+    if openssl x509 -checkend 259200 -noout -in "${CERT_FILE}"; then
+        log-helper info "The certificate '${CERT_FILE}' is ok for the next 3 
days at least."
+        cert_ok=true
+    fi
+    
+    if openssl x509 -checkend 259200 -noout -in "${CA_FILE}"; then
+        log-helper info "The CA certificate '${CA_FILE}' is ok for the next 3 
days at least."
+        ca_ok=true
+    fi
+    
+    if [ "${SSL_HELPER_TOOL}" = "jsonssl-helper" ]; then
+        log-helper info "Check if ${JSONSSL_FILE} has changed"
+        JSONSSL_FILE_PREVIOUS_MD5=$(cat 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5") || true
+        JSONSSL_FILE_MD5=$(md5sum "${JSONSSL_FILE}" | awk '{ print $1 }')
+        
+        [[ "${JSONSSL_FILE_PREVIOUS_MD5}" != "${JSONSSL_FILE_MD5}" ]] && 
cert_ok=false
+    fi
+    
+    if ${cert_ok} && ${ca_ok}; then
+        log-helper info "Nothing to do :)"
+        exit 0
+    fi
+    
+    log-helper info "Auto-renew on the way!"
+    
+    stop_impacted_services
+    
+    log-helper info "Remove certificate files"
+    rm -f "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}"
+    
+    log-helper info "Regenerate certificate with ${SSL_HELPER_TOOL}"
+    ${SSL_HELPER_TOOL} "${PREFIX}" "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}"
+    
+    start_impacted_services
+    
+    if [ "${SSL_HELPER_TOOL}" = "jsonssl-helper" ]; then
+        log-helper info "Update file md5 with new values"
+        echo "${JSONSSL_FILE_MD5}" > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5"
+    fi
+    
+fi
+
+log-helper info "Auto-renew finished! Champagne!"
diff --git 
a/image/base-image/service-available/:ssl-tools/assets/tool/ssl-helper 
b/image/base-image/service-available/:ssl-tools/assets/tool/ssl-helper
new file mode 100755
index 0000000..8a5d717
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/assets/tool/ssl-helper
@@ -0,0 +1,100 @@
+#!/bin/bash -e
+log-helper level eq trace && set -x
+
+# This tool helps to generate tls certificates with cfssl
+# or get certificates from a json file
+
+PREFIX=$1
+CERT_FILE=$2
+KEY_FILE=$3
+CA_FILE=$4
+
+log-helper debug "Hi! I'm ssl-helper, what button should i press ?"
+
+# set env vars
+PREFIX=${PREFIX^^} # uppercase
+
+PREFIX_SSL_HELPER_TOOL=${PREFIX}_SSL_HELPER_TOOL
+PREFIX_SSL_HELPER_AUTO_RENEW=${PREFIX}_SSL_HELPER_AUTO_RENEW
+PREFIX_SSL_HELPER_AUTO_RENEW_CRON_EXP=${PREFIX}_SSL_HELPER_AUTO_RENEW_CRON_EXP
+PREFIX_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${PREFIX}_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED
+PREFIX_SSL_HELPER_AUTO_RENEW_FROM_FILES=${PREFIX}_SSL_HELPER_AUTO_RENEW_FROM_FILES
+PREFIX_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE
+PREFIX_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE
+PREFIX_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE
+
+SSL_HELPER_TOOL=${!PREFIX_SSL_HELPER_TOOL:-$SSL_HELPER_TOOL}
+SSL_HELPER_AUTO_RENEW=${!PREFIX_SSL_HELPER_AUTO_RENEW:-$SSL_HELPER_AUTO_RENEW}
+SSL_HELPER_AUTO_RENEW_CRON_EXP=${!PREFIX_SSL_HELPER_AUTO_RENEW_CRON_EXP:-$SSL_HELPER_AUTO_RENEW_CRON_EXP}
+SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${!PREFIX_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED:-$SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED}
+SSL_HELPER_AUTO_RENEW_FROM_FILES=${!PREFIX_SSL_HELPER_AUTO_RENEW_FROM_FILES:-$SSL_HELPER_AUTO_RENEW_FROM_FILES}
+SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}
+SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}
+SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}
+
+source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-env"
+
+# call the certificate tool cfssl-helper (default) or jsonssl-helper
+${SSL_HELPER_TOOL,,} "${PREFIX}" "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}"
+
+# auto-renew certificates just before it expired
+# or if source files have changed
+if [ "${SSL_HELPER_AUTO_RENEW,,}" = "true" ]; then
+    
+    # only for multiple process images (uses cron)
+    if [ ! -e "/container/multiple_process_stack_added" ]; then
+        log-helper error "auto-renew is available only with multiple process 
images"
+        exit 1
+    fi
+    
+    # if SSL_HELPER_AUTO_RENEW_FROM_FILES=true check certificate source files
+    if [ "${SSL_HELPER_AUTO_RENEW_FROM_FILES,,}" = "true" ]; then
+        
+        [[ -z "${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}" ]] && 
SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${CERT_FILE}
+        [[ -z "${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}" ]] && 
SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${KEY_FILE}
+        [[ -z "${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}" ]] && 
SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${CA_FILE}
+        
+        if [ ! -e "${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}" ] || [ ! -e 
"${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}" ] || [ ! -e 
"${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}" ]; then
+            log-helper error "with SSL_HELPER_AUTO_RENEW_FROM_FILES=true the 
following files must exists:"
+            log-helper error 
"SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}"
+            log-helper error 
"SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}"
+            log-helper error 
"SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}"
+            exit 1
+        fi
+        
+        mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname 
"${CERT_FILE}")"
+        mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname 
"${KEY_FILE}")"
+        mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname 
"${CA_FILE}")"
+        
+        # calculate certificates files md5
+        md5sum "${CERT_FILE}" | awk '{ print $1 }' > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5"
+        md5sum "${KEY_FILE}" | awk '{ print $1 }' > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5"
+        md5sum "${CA_FILE}" | awk '{ print $1 }' > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5"
+        
+    fi
+    
+    if [ "${SSL_HELPER_TOOL,,}" = "jsonssl-helper" ]; then
+        
+        PREFIX_JSONSSL_FILE=${PREFIX}_JSONSSL_FILE
+        JSONSSL_FILE=${!PREFIX_JSONSSL_FILE:-$JSONSSL_FILE}
+        
+        source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/jsonssl-default-env"
+        
+        if [ -z "${JSONSSL_FILE}" ]; then
+            JSONSSL_FILE=${JSONSSL_FILE_DEFAULT}
+        fi
+        
+        # calculate jsonssl file md5
+        mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname 
"${JSONSSL_FILE}")"
+        md5sum "${JSONSSL_FILE}" | awk '{ print $1 }' > 
"${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5"
+        
+    fi
+    
+    # add cron job
+    echo "${SSL_HELPER_AUTO_RENEW_CRON_EXP} root /usr/sbin/ssl-auto-renew 
${SSL_HELPER_TOOL,,} ${PREFIX} ${CERT_FILE} ${KEY_FILE} ${CA_FILE} 
\"${SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED}\" \"${JSONSSL_FILE}\" 
\"${SSL_HELPER_AUTO_RENEW_FROM_FILES}\" 
\"${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}\" 
\"${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}\" 
\"${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}\" 2>&1 | /usr/bin/logger -t 
cron_ssl_auto_renew" > "/etc/cron.d/${PREFIX}"
+    chmod 600 "/etc/cron.d/${PREFIX}"
+    
+    # disable auto-renew if it was added
+    elif [ -e "/etc/cron.d/${PREFIX}" ]; then
+    rm -f "/etc/cron.d/${PREFIX}"
+fi
diff --git a/image/base-image/service-available/:ssl-tools/download.sh 
b/image/base-image/service-available/:ssl-tools/download.sh
new file mode 100755
index 0000000..985b1bb
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/download.sh
@@ -0,0 +1,69 @@
+#!/bin/bash -e
+
+UARCH=$(uname -m)
+echo "Architecture is ${UARCH}"
+
+case "${UARCH}" in
+    
+    "x86_64")
+        HOST_ARCH="amd64"
+    ;;
+    
+    "arm64" | "aarch64")
+        HOST_ARCH="arm64"
+    ;;
+    
+    "armv7l" | "armv6l" | "armhf")
+        HOST_ARCH="arm"
+    ;;
+    
+    "i386")
+        HOST_ARCH="386"
+    ;;
+    
+    *)
+        echo "Architecture not supported. Exiting."
+        exit 1
+    ;;
+esac
+
+echo "Going to use ${HOST_ARCH} cfssl binaries"
+
+# download curl and ca-certificate from apt-get if needed
+to_install=()
+
+if [ "$(dpkg-query -W -f='${Status}' curl 2>/dev/null | grep -c "ok 
installed")" -eq 0 ]; then
+    to_install+=("curl")
+fi
+
+if [ "$(dpkg-query -W -f='${Status}' ca-certificates 2>/dev/null | grep -c "ok 
installed")" -eq 0 ]; then
+    to_install+=("ca-certificates")
+fi
+
+if [ ${#to_install[@]} -ne 0 ]; then
+    LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends "${to_install[@]}"
+fi
+
+LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends openssl jq
+
+# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923479
+if [[ "${HOST_ARCH}" == 'arm' ]]; then
+    LC_ALL=C DEBIAN_FRONTEND=noninteractive c_rehash
+fi
+
+echo "Download cfssl ..."
+echo "curl -o /usr/sbin/cfssl -SL 
https://github.com/osixia/cfssl/releases/download/1.4.1/cfssl_linux-${HOST_ARCH}";
+curl -o /usr/sbin/cfssl -SL 
"https://github.com/osixia/cfssl/releases/download/1.4.1/cfssl_linux-${HOST_ARCH}";
+chmod 700 /usr/sbin/cfssl
+
+echo "Download cfssljson ..."
+echo "curl -o /usr/sbin/cfssljson -SL 
https://github.com/osixia/cfssl/releases/download/1.4.1/cfssljson_linux-${HOST_ARCH}";
+curl -o /usr/sbin/cfssljson -SL 
"https://github.com/osixia/cfssl/releases/download/1.4.1/cfssljson_linux-${HOST_ARCH}";
+chmod 700 /usr/sbin/cfssljson
+
+echo "Project sources: https://github.com/cloudflare/cfssl";
+
+# remove tools installed to download cfssl
+if [ ${#to_install[@]} -ne 0 ]; then
+    apt-get remove -y --purge --auto-remove "${to_install[@]}"
+fi
diff --git a/image/base-image/service-available/:ssl-tools/startup.sh 
b/image/base-image/service-available/:ssl-tools/startup.sh
new file mode 100755
index 0000000..0189099
--- /dev/null
+++ b/image/base-image/service-available/:ssl-tools/startup.sh
@@ -0,0 +1,5 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+
+chmod 700 "${CONTAINER_SERVICE_DIR}"/:ssl-tools/assets/tool/*
+ln -sf "${CONTAINER_SERVICE_DIR}"/:ssl-tools/assets/tool/* /usr/sbin
diff --git 
a/image/base-image/service-available/:syslog-ng-core/assets/config/syslog-ng.conf
 
b/image/base-image/service-available/:syslog-ng-core/assets/config/syslog-ng.conf
new file mode 100644
index 0000000..839c118
--- /dev/null
+++ 
b/image/base-image/service-available/:syslog-ng-core/assets/config/syslog-ng.conf
@@ -0,0 +1,152 @@
+@version: 3.19
+@include "scl.conf"
+
+# Syslog-ng configuration file, compatible with default Debian syslogd
+# installation.
+
+# First, set some global options.
+options { chain_hostnames(off); flush_lines(0); use_dns(no); dns-cache(no); 
use_fqdn(no);
+    owner("root"); group("adm"); perm(0640); stats_freq(0);
+    bad_hostname("^gconfd$");
+};
+
+########################
+# Sources
+########################
+# This is the default behavior of sysklogd package
+# Logs may come from unix stream, but not from another machine.
+#
+source s_src {
+       unix-dgram("/dev/log");
+       internal();
+};
+
+# If you wish to get logs from remote machine you should uncomment
+# this and comment the above source line.
+#
+#source s_net { tcp(ip(127.0.0.1) port(1000)); };
+
+########################
+# Destinations
+########################
+# First some standard logfile
+#
+destination d_auth { file("/var/log/auth.log"); };
+destination d_cron { file("/var/log/cron.log"); };
+destination d_daemon { file("/var/log/daemon.log"); };
+destination d_kern { file("/var/log/kern.log"); };
+destination d_lpr { file("/var/log/lpr.log"); };
+destination d_mail { file("/var/log/mail.log"); };
+destination d_syslog { file("/var/log/syslog"); };
+destination d_user { file("/var/log/user.log"); };
+destination d_uucp { file("/var/log/uucp.log"); };
+
+# This files are the log come from the mail subsystem.
+#
+destination d_mailinfo { file("/var/log/mail.info"); };
+destination d_mailwarn { file("/var/log/mail.warn"); };
+destination d_mailerr { file("/var/log/mail.err"); };
+
+# Logging for INN news system
+#
+destination d_newscrit { file("/var/log/news/news.crit"); };
+destination d_newserr { file("/var/log/news/news.err"); };
+destination d_newsnotice { file("/var/log/news/news.notice"); };
+
+# Some 'catch-all' logfiles.
+#
+destination d_debug { file("/var/log/debug"); };
+destination d_error { file("/var/log/error"); };
+destination d_messages { file("/var/log/messages"); };
+
+# The named pipe /dev/xconsole is for the nsole' utility.  To use it,
+# you must invoke nsole' with the -file' option:
+#
+#    $ xconsole -file /dev/xconsole [...]
+#
+destination d_xconsole { pipe("/dev/xconsole"); };
+
+# Send the messages to an other host
+#
+#destination d_net { tcp("127.0.0.1" port(1000) log_fifo_size(1000)); };
+
+# Debian only
+destination d_ppp { file("/var/log/ppp.log"); };
+
+# stdout for docker
+destination d_stdout { ##SYSLOG_OUTPUT_MODE_DEV_STDOUT##("/dev/stdout"); };
+
+########################
+# Filters
+########################
+# Here's come the filter options. With this rules, we can set which
+# message go where.
+
+filter f_dbg { level(debug); };
+filter f_info { level(info); };
+filter f_notice { level(notice); };
+filter f_warn { level(warn); };
+filter f_err { level(err); };
+filter f_crit { level(crit .. emerg); };
+
+filter f_debug { level(debug) and not facility(auth, authpriv, news, mail); };
+filter f_error { level(err .. emerg) ; };
+filter f_messages { level(info,notice,warn) and
+                    not facility(auth,authpriv,cron,daemon,mail,news); };
+
+filter f_auth { facility(auth, authpriv) and not filter(f_debug); };
+filter f_cron { facility(cron) and not filter(f_debug); };
+filter f_daemon { facility(daemon) and not filter(f_debug); };
+filter f_kern { facility(kern) and not filter(f_debug); };
+filter f_lpr { facility(lpr) and not filter(f_debug); };
+filter f_local { facility(local0, local1, local3, local4, local5,
+                        local6, local7) and not filter(f_debug); };
+filter f_mail { facility(mail) and not filter(f_debug); };
+filter f_news { facility(news) and not filter(f_debug); };
+filter f_syslog3 { not facility(auth, authpriv, mail) and not filter(f_debug); 
};
+filter f_user { facility(user) and not filter(f_debug); };
+filter f_uucp { facility(uucp) and not filter(f_debug); };
+
+filter f_cnews { level(notice, err, crit) and facility(news); };
+filter f_cother { level(debug, info, notice, warn) or facility(daemon, mail); 
};
+
+filter f_ppp { facility(local2) and not filter(f_debug); };
+filter f_console { level(warn .. emerg); };
+
+########################
+# Log paths
+########################
+log { source(s_src); filter(f_auth); destination(d_auth); };
+log { source(s_src); filter(f_cron); destination(d_cron); };
+log { source(s_src); filter(f_daemon); destination(d_daemon); };
+log { source(s_src); filter(f_kern); destination(d_kern); };
+log { source(s_src); filter(f_lpr); destination(d_lpr); };
+log { source(s_src); filter(f_syslog3); destination(d_syslog); 
destination(d_stdout); };
+log { source(s_src); filter(f_user); destination(d_user); };
+log { source(s_src); filter(f_uucp); destination(d_uucp); };
+
+log { source(s_src); filter(f_mail); destination(d_mail); };
+#log { source(s_src); filter(f_mail); filter(f_info); destination(d_mailinfo); 
};
+#log { source(s_src); filter(f_mail); filter(f_warn); destination(d_mailwarn); 
};
+#log { source(s_src); filter(f_mail); filter(f_err); destination(d_mailerr); };
+
+log { source(s_src); filter(f_news); filter(f_crit); destination(d_newscrit); 
};
+log { source(s_src); filter(f_news); filter(f_err); destination(d_newserr); };
+log { source(s_src); filter(f_news); filter(f_notice); 
destination(d_newsnotice); };
+#log { source(s_src); filter(f_cnews); destination(d_console_all); };
+#log { source(s_src); filter(f_cother); destination(d_console_all); };
+
+#log { source(s_src); filter(f_ppp); destination(d_ppp); };
+
+log { source(s_src); filter(f_debug); destination(d_debug); };
+log { source(s_src); filter(f_error); destination(d_error); };
+log { source(s_src); filter(f_messages); destination(d_messages); };
+
+# All messages send to a remote site
+#
+#log { source(s_src); destination(d_net); };
+
+###
+# Include all config files in /etc/syslog-ng/conf.d/
+###
+@include "/etc/syslog-ng/conf.d/*.conf"
diff --git 
a/image/base-image/service-available/:syslog-ng-core/assets/config/syslog_ng_default
 
b/image/base-image/service-available/:syslog-ng-core/assets/config/syslog_ng_default
new file mode 100644
index 0000000..c9e7057
--- /dev/null
+++ 
b/image/base-image/service-available/:syslog-ng-core/assets/config/syslog_ng_default
@@ -0,0 +1,12 @@
+# If a variable is not set here, then the corresponding
+# parameter will not be changed.
+# If a variables is set, then every invocation of
+# syslog-ng's init script will set them using dmesg.
+
+# log level of messages which should go to console
+# see syslog(3) for details
+#
+#CONSOLE_LOG_LEVEL=1
+
+# Command line options to syslog-ng
+SYSLOGNG_OPTS="--no-caps"
diff --git a/image/base-image/service-available/:syslog-ng-core/download.sh 
b/image/base-image/service-available/:syslog-ng-core/download.sh
new file mode 100755
index 0000000..92bc3df
--- /dev/null
+++ b/image/base-image/service-available/:syslog-ng-core/download.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+# download syslog-ng-core from apt-get
+LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y 
--no-install-recommends syslog-ng-core
diff --git a/image/base-image/service-available/:syslog-ng-core/install.sh 
b/image/base-image/service-available/:syslog-ng-core/install.sh
new file mode 100755
index 0000000..cc8a4a2
--- /dev/null
+++ b/image/base-image/service-available/:syslog-ng-core/install.sh
@@ -0,0 +1,8 @@
+#!/bin/sh -e
+
+mkdir -p /var/lib/syslog-ng
+rm -f /etc/default/syslog-ng
+
+touch /var/log/syslog
+chmod u=rw,g=r,o= /var/log/syslog
+rm -f /etc/syslog-ng/syslog-ng.conf
diff --git a/image/base-image/service-available/:syslog-ng-core/process.sh 
b/image/base-image/service-available/:syslog-ng-core/process.sh
new file mode 100755
index 0000000..c842af4
--- /dev/null
+++ b/image/base-image/service-available/:syslog-ng-core/process.sh
@@ -0,0 +1,9 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+
+PIDFILE="/var/run/syslog-ng.pid"
+SYSLOGNG_OPTS=""
+
+[ -r /etc/default/syslog-ng ] && . /etc/default/syslog-ng
+
+exec /usr/sbin/syslog-ng --pidfile "$PIDFILE" -F $SYSLOGNG_OPTS
diff --git a/image/base-image/service-available/:syslog-ng-core/startup.sh 
b/image/base-image/service-available/:syslog-ng-core/startup.sh
new file mode 100755
index 0000000..e0e1c5a
--- /dev/null
+++ b/image/base-image/service-available/:syslog-ng-core/startup.sh
@@ -0,0 +1,22 @@
+#!/bin/sh -e
+log-helper level eq trace && set -x
+
+ln -sf 
"${CONTAINER_SERVICE_DIR}/:syslog-ng-core/assets/config/syslog_ng_default" 
/etc/default/syslog-ng
+ln -sf "${CONTAINER_SERVICE_DIR}/:syslog-ng-core/assets/config/syslog-ng.conf" 
/etc/syslog-ng/syslog-ng.conf
+
+# If /dev/log is either a named pipe or it was placed there accidentally,
+# e.g. because of the issue documented at 
https://github.com/phusion/baseimage-docker/pull/25,
+# then we remove it.
+if [ ! -S /dev/log ]; then rm -f /dev/log; fi
+if [ ! -S /var/lib/syslog-ng/syslog-ng.ctl ]; then rm -f 
/var/lib/syslog-ng/syslog-ng.ctl; fi
+
+# determine output mode on /dev/stdout because of the issue documented at 
https://github.com/phusion/baseimage-docker/issues/468
+if [ -p /dev/stdout ]; then
+    sed -i 's/##SYSLOG_OUTPUT_MODE_DEV_STDOUT##/pipe/' 
/etc/syslog-ng/syslog-ng.conf
+else
+    sed -i 's/##SYSLOG_OUTPUT_MODE_DEV_STDOUT##/file/' 
/etc/syslog-ng/syslog-ng.conf
+fi
+
+# If /var/log is writable by another user logrotate will fail
+/bin/chown root:root /var/log
+/bin/chmod 0755 /var/log
diff --git a/image/base-image/tool/add-multiple-process-stack 
b/image/base-image/tool/add-multiple-process-stack
new file mode 100755
index 0000000..131d8a7
--- /dev/null
+++ b/image/base-image/tool/add-multiple-process-stack
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+echo "Install the multiple process stack: runit, syslog-ng-core, logrotate and 
cron"
+/container/tool/add-service-available :runit :syslog-ng-core :logrotate :cron
+touch /container/multiple_process_stack_added
diff --git a/image/base-image/tool/add-service-available 
b/image/base-image/tool/add-service-available
new file mode 100755
index 0000000..081f7c3
--- /dev/null
+++ b/image/base-image/tool/add-service-available
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+
+# Usage :
+# RUN /container/tool/add-service-available [service1] [service2] ...
+
+SERVICE_DIR="/container/service"
+SERVICE_AVAILABLE_DIR="/container/service-available"
+DOWNLOAD_FILENAME="download.sh"
+
+for i in "$@"
+do
+    
+    echo "add-service-available: ${i}"
+    if  [ -d "${SERVICE_AVAILABLE_DIR}/${i}" ]; then
+        
+        if [ -f "${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}" ]; then
+            echo "run ${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}"
+            ${SERVICE_AVAILABLE_DIR}/"${i}"/"${DOWNLOAD_FILENAME}"
+            echo "remove ${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}"
+            rm -f "${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}"
+        fi
+        
+        echo "move ${SERVICE_AVAILABLE_DIR}/${i} to ${SERVICE_DIR}/${i}"
+        mv "${SERVICE_AVAILABLE_DIR}/${i}" "${SERVICE_DIR}/${i}"
+        
+    else
+        echo "service-available: ${i} not found in 
${SERVICE_AVAILABLE_DIR}/${i}"
+        exit 1
+    fi
+done
diff --git a/image/base-image/tool/complex-bash-env 
b/image/base-image/tool/complex-bash-env
new file mode 100755
index 0000000..829bcd1
--- /dev/null
+++ b/image/base-image/tool/complex-bash-env
@@ -0,0 +1,91 @@
+#!/bin/bash -e
+
+call=$1
+
+function iterate() {
+    local env_var_name=$1
+    local env_var=${!env_var_name}
+    
+    if [ "$(complex-bash-env isTable "$env_var")" = true ]; then
+        complex-bash-env stripTablePrefix "${env_var}"
+    else
+        echo "${env_var_name}"
+    fi
+}
+
+function isTable() {
+    local env_var=$1
+    if [ "$(echo "${env_var}" | grep "#COMPLEX_BASH_ENV:TABLE:" -c )" -eq 1 ]; 
then
+        echo true
+    else
+        echo false
+    fi
+}
+
+function isRow() {
+    local env_var=$1
+    if [ "$(echo "${env_var}" | grep "#COMPLEX_BASH_ENV:ROW:" -c )" -eq 1 ]; 
then
+        echo true
+    else
+        echo false
+    fi
+}
+
+function getRowKey() {
+    local env_var=$1
+    local row_key_var_name
+    row_key_var_name=$(complex-bash-env getRowKeyVarName "$env_var")
+    echo "${!row_key_var_name}"
+}
+
+function getRowValue() {
+    local env_var=$1
+    local row_value_var_name
+    row_value_var_name=$(complex-bash-env getRowValueVarName "$env_var")
+    echo "${!row_value_var_name}"
+}
+
+function getRowKeyVarName() {
+    local env_var=$1
+    local row=($(complex-bash-env getRow "$env_var"))
+    echo "${row[0]}"
+}
+
+function getRowValueVarName() {
+    local env_var=$1
+    local row=($(complex-bash-env getRow "$env_var"))
+    echo "${row[1]}"
+}
+
+function getRow() {
+    local env_var
+    env_var=$1
+    if [ "$(complex-bash-env isRow "$env_var")" = true ]; then
+        local env_var
+        env_var=$(complex-bash-env stripRowPrefix "$env_var")
+        echo "${env_var}"
+    else
+        echo "$env_var is not a complex bash env row"
+        exit 1
+    fi
+}
+
+function stripTablePrefix() {
+    local env_var=$1
+    stripPrefix "$env_var" "#COMPLEX_BASH_ENV:TABLE:"
+}
+
+function stripRowPrefix() {
+    local env_var=$1
+    stripPrefix "$env_var" "#COMPLEX_BASH_ENV:ROW:"
+}
+
+function stripPrefix() {
+    local env_var=$1
+    local prefix=$2
+    local r=${env_var#$prefix}
+    echo "${r}"
+}
+
+shift
+$call "$@"
diff --git a/image/base-image/tool/install-service 
b/image/base-image/tool/install-service
new file mode 100755
index 0000000..b8a5782
--- /dev/null
+++ b/image/base-image/tool/install-service
@@ -0,0 +1,41 @@
+#!/usr/bin/python3 -u
+import os, os.path, subprocess
+
+SERVICE_DIR = "/container/service"
+INSTALL_FILENAME = "install.sh"
+PROCESS_FILENAME = "process.sh"
+nb_process = 0
+
+print("install-service")
+# Auto run global install script if available
+if os.path.isfile(SERVICE_DIR + os.sep + INSTALL_FILENAME):
+    print(("run " + SERVICE_DIR + os.sep + INSTALL_FILENAME))
+    subprocess.call([SERVICE_DIR + os.sep + INSTALL_FILENAME],shell=True)
+
+    print(("remove " + SERVICE_DIR + os.sep + INSTALL_FILENAME + "\n"))
+    os.remove(SERVICE_DIR + os.sep + INSTALL_FILENAME)
+
+# Process install script of services in /container/service
+for service in sorted(os.listdir(SERVICE_DIR)):
+
+    if os.path.isfile(SERVICE_DIR + os.sep + service + os.sep + 
INSTALL_FILENAME):
+        print(("run " + SERVICE_DIR + os.sep + service + os.sep + 
INSTALL_FILENAME))
+        subprocess.call([SERVICE_DIR + os.sep + service + os.sep + 
INSTALL_FILENAME],shell=True)
+
+        print(("remove " + SERVICE_DIR + os.sep + service + os.sep + 
INSTALL_FILENAME))
+        os.remove(SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME)
+
+    if os.path.isfile(SERVICE_DIR + os.sep + service + os.sep + 
PROCESS_FILENAME):
+        nb_process += 1
+
+
+print((str(nb_process) + " process found."))
+
+# Multiple process image
+if nb_process > 1:
+    if not os.path.exists("/container/multiple_process_stack_added"):
+        print("This image has multiple process.")
+        subprocess.call(["apt-get update"],shell=True)
+        
subprocess.call(["/container/tool/add-multiple-process-stack"],shell=True)
+        print("For better image build process consider adding:")
+        print("\"/container/tool/add-multiple-process-stack\" after an apt-get 
update in your Dockerfile.")
diff --git a/image/base-image/tool/log-helper b/image/base-image/tool/log-helper
new file mode 100755
index 0000000..ad1c527
--- /dev/null
+++ b/image/base-image/tool/log-helper
@@ -0,0 +1,121 @@
+#!/bin/bash -e
+
+# log helper base on environment variable CONTAINER_LOG_LEVEL
+# CONTAINER_LOG_LEVEL environment variable is set by run tool based on 
--log-level argument (info by default)
+# or you can set it directly with docker --env argument
+
+# Usage example: log-helper info CONTAINER_LOG_LEVEL is info or more
+# the message "CONTAINER_LOG_LEVEL is info or more" will be printed only if 
log level is info, debug or trace
+
+LOG_LEVEL_NONE=0
+LOG_LEVEL_ERROR=1
+LOG_LEVEL_WARNING=2
+LOG_LEVEL_INFO=3
+LOG_LEVEL_DEBUG=4
+LOG_LEVEL_TRACE=5
+
+# default log level if CONTAINER_LOG_LEVEL is not set -> info
+log_level=${CONTAINER_LOG_LEVEL:-${LOG_LEVEL_INFO}}
+
+call=$1 # function to call (error, warning, info, debug, trace, level)
+if [[ ! "$call" =~ ^(error|warning|info|debug|trace|level)$ ]]; then
+    echo "Error: Function $call not found"
+    echo "Allowed functions are: error, warning, info, debug, trace, level"
+    echo "usage example: log-helper info hello !"
+    exit 1
+fi
+
+
+echo_msg="" # message to print if required log level is set
+echo_param="" # echo command parameters
+
+function error() {
+    getEchoParams $@
+    
+    if [ $log_level -ge 1 ]; then
+        echo $echo_param  "$echo_msg"
+    fi
+}
+
+function warning() {
+    getEchoParams $@
+    
+    if [ $log_level -ge 2 ]; then
+        echo $echo_param  "$echo_msg"
+    fi
+}
+
+function info() {
+    getEchoParams $@
+    
+    if [ $log_level -ge 3 ]; then
+        echo $echo_param "$echo_msg"
+    fi
+}
+
+function debug() {
+    getEchoParams $@
+    
+    if [ $log_level -ge 4 ]; then
+        echo $echo_param "$echo_msg"
+    fi
+}
+
+function trace() {
+    getEchoParams $@
+    
+    if [ $log_level -ge 5 ]; then
+        echo $echo_param "$echo_msg"
+    fi
+}
+
+function getMsgFromStdin() {
+    if [ -z "$2" ]; then
+        echo_msg=$(cat)
+    fi
+}
+
+function getEchoParams() {
+    
+    echo_msg="$@"
+    
+    if [[ "$1" =~ ^(-e|-n|-E)$ ]]; then
+        echo_param=$1
+        echo_msg=${echo_msg#$1 }
+    fi
+    
+    # read from pipe if echo_msg is empty
+    [[ -n "$echo_msg" ]] || getMsgFromStdin
+}
+
+function level() {
+    
+    local operator=$1
+    local loglevel_str=$2
+    local loglevel_str=${loglevel_str^^} # uppercase
+    
+    if [[ ! "$operator" =~ ^(eq|ne|gt|ge|lt|le)$ ]]; then
+        echo "Error: Operator $operator not allowed"
+        echo "Allowed operators are: eq, ne, gt, ge, lt, le"
+        echo "Help: http://www.tldp.org/LDP/abs/html/comparison-ops.html";
+        exit 1
+    fi
+    
+    if [ -z "$loglevel_str" ]; then
+        echo "Error: No log level provided"
+        echo "Allowed log level are: none, error, warning, info, debug, trace"
+        echo "usage example: log-helper level eq info"
+        exit 1
+    fi
+    
+    local log_level_var=LOG_LEVEL_$loglevel_str
+    
+    if [ $log_level -$operator ${!log_level_var} ]; then
+        exit 0
+    else
+        exit 1
+    fi
+}
+
+shift
+$call "$@"
diff --git a/image/base-image/tool/run b/image/base-image/tool/run
new file mode 100755
index 0000000..d254485
--- /dev/null
+++ b/image/base-image/tool/run
@@ -0,0 +1,930 @@
+#!/usr/bin/python3 -u
+# -*- coding: utf-8 -*-
+
+import os, os.path, sys, stat, signal, errno, argparse, time, json, re, yaml, 
ast, socket, shutil, pwd, grp
+
+KILL_PROCESS_TIMEOUT = int(os.environ.get('KILL_PROCESS_TIMEOUT', 30))
+KILL_ALL_PROCESSES_TIMEOUT = int(os.environ.get('KILL_ALL_PROCESSES_TIMEOUT', 
30))
+
+LOG_LEVEL_NONE = 0
+LOG_LEVEL_ERROR = 1
+LOG_LEVEL_WARNING  = 2
+LOG_LEVEL_INFO  = 3
+LOG_LEVEL_DEBUG = 4
+LOG_LEVEL_TRACE = 5
+
+SHENV_NAME_WHITELIST_REGEX = re.compile('\W')
+
+log_level = None
+
+environ_backup = dict(os.environ)
+terminated_child_processes = {}
+
+IMPORT_STARTUP_FILENAME="startup.sh"
+IMPORT_PROCESS_FILENAME="process.sh"
+IMPORT_FINISH_FILENAME="finish.sh"
+
+IMPORT_ENVIRONMENT_DIR="/container/environment"
+IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR="/container/environment/startup"
+
+ENV_FILES_YAML_EXTENSIONS = ('.yaml', '.startup.yaml')
+ENV_FILES_JSON_EXTENSIONS = ('.json', '.startup.json')
+ENV_FILES_STARTUP_EXTENSIONS = ('.startup.yaml', '.startup.json')
+
+IMPORT_SERVICE_DIR="/container/service"
+
+RUN_DIR="/container/run"
+RUN_STATE_DIR = RUN_DIR + "/state"
+RUN_ENVIRONMENT_DIR = RUN_DIR + "/environment"
+RUN_ENVIRONMENT_FILE_EXPORT = RUN_DIR + "/environment.sh"
+RUN_STARTUP_DIR = RUN_DIR + "/startup"
+RUN_STARTUP_FINAL_FILE = RUN_DIR + "/startup.sh"
+RUN_PROCESS_DIR = RUN_DIR + "/process"
+RUN_SERVICE_DIR = RUN_DIR + "/service"
+
+ENVIRONMENT_LOG_LEVEL_KEY = 'CONTAINER_LOG_LEVEL'
+ENVIRONMENT_SERVICE_DIR_KEY = 'CONTAINER_SERVICE_DIR'
+ENVIRONMENT_STATE_DIR_KEY = 'CONTAINER_STATE_DIR'
+
+class AlarmException(Exception):
+       pass
+
+def error(message):
+       if log_level >= LOG_LEVEL_ERROR:
+               sys.stderr.write("*** %s\n" % message)
+
+def warning(message):
+       if log_level >= LOG_LEVEL_WARNING:
+               sys.stderr.write("*** %s\n" % message)
+
+def info(message):
+       if log_level >= LOG_LEVEL_INFO:
+               sys.stderr.write("*** %s\n" % message)
+
+def debug(message):
+       if log_level >= LOG_LEVEL_DEBUG:
+               sys.stderr.write("*** %s\n" % message)
+
+def trace(message):
+       if log_level >= LOG_LEVEL_TRACE:
+               sys.stderr.write("*** %s\n" % message)
+
+def debug_env_dump():
+       debug("------------ Environment dump ------------")
+       for name, value in list(os.environ.items()):
+               debug(name + " = " +  value)
+       debug("------------------------------------------")
+
+def ignore_signals_and_raise_keyboard_interrupt(signame):
+       signal.signal(signal.SIGTERM, signal.SIG_IGN)
+       signal.signal(signal.SIGINT, signal.SIG_IGN)
+       raise KeyboardInterrupt(signame)
+
+def raise_alarm_exception():
+       raise AlarmException('Alarm')
+
+def listdir(path):
+       try:
+               result = os.stat(path)
+       except OSError:
+               return []
+       if stat.S_ISDIR(result.st_mode):
+               return sorted(os.listdir(path))
+       else:
+               return []
+
+def is_exe(path):
+       try:
+               return os.path.isfile(path) and os.access(path, os.X_OK)
+       except OSError:
+               return False
+
+def xstr(s):
+    if s is None:
+        return ''
+    return str(s)
+
+def set_env_hostname_to_etc_hosts():
+       try:
+               if "HOSTNAME" in os.environ:
+                       socket_hostname = socket.gethostname()
+
+                       if os.environ["HOSTNAME"] != socket_hostname:
+                               ip_address = 
socket.gethostbyname(socket_hostname)
+                               with open("/etc/hosts", "a") as myfile:
+                                       myfile.write(ip_address+" 
"+os.environ["HOSTNAME"]+"\n")
+       except:
+               trace("set_env_hostname_to_etc_hosts: failed at some point...")
+
+def python_dict_to_bash_envvar(name, python_dict):
+
+       for value in python_dict:
+               python_to_bash_envvar(name+"_KEY", value)
+               python_to_bash_envvar(name+"_VALUE", python_dict.get(value))
+
+       values = "#COMPLEX_BASH_ENV:ROW: "+name+"_KEY "+name+"_VALUE"
+       os.environ[name] = xstr(values)
+       trace("python2bash : set : " + name + " = "+ os.environ[name])
+
+def python_list_to_bash_envvar(name, python_list):
+
+       values="#COMPLEX_BASH_ENV:TABLE:"
+
+       i=1
+       for value in python_list:
+               child_name = name + "_ROW_" + str(i)
+               values += " " + child_name
+               python_to_bash_envvar(child_name, value)
+               i = i +1
+
+       os.environ[name] = xstr(values)
+       trace("python2bash : set : " + name + " = "+ os.environ[name])
+
+def python_to_bash_envvar(name, value):
+
+       try:
+               value = ast.literal_eval(value)
+       except:
+               pass
+
+       if isinstance(value, list):
+               python_list_to_bash_envvar(name,value)
+
+       elif isinstance(value, dict):
+               python_dict_to_bash_envvar(name,value)
+
+       else:
+               os.environ[name] = xstr(value)
+               trace("python2bash : set : " + name + " = "+ os.environ[name])
+
+def decode_python_envvars():
+       _environ = dict(os.environ)
+       for name, value in list(_environ.items()):
+               if value.startswith("#PYTHON2BASH:") :
+                       value = value.replace("#PYTHON2BASH:","",1)
+                       python_to_bash_envvar(name, value)
+
+def decode_json_envvars():
+       _environ = dict(os.environ)
+       for name, value in list(_environ.items()):
+               if value.startswith("#JSON2BASH:") :
+                       value = value.replace("#JSON2BASH:","",1)
+                       try:
+                               value = json.loads(value)
+                               python_to_bash_envvar(name,value)
+                       except:
+                               os.environ[name] = xstr(value)
+                               warning("failed to parse : " + xstr(value))
+                               trace("set : " + name + " = "+ os.environ[name])
+
+def decode_envvars():
+       decode_json_envvars()
+       decode_python_envvars()
+
+def generic_import_envvars(path, override_existing_environment):
+       if not os.path.exists(path):
+               trace("generic_import_envvars "+ path+ " don't exists")
+               return
+       new_env = {}
+       for envfile in listdir(path):
+               filePath = path + os.sep + envfile
+               if os.path.isfile(filePath) and "." not in envfile:
+                       name = os.path.basename(envfile)
+                       with open(filePath, "r") as f:
+                               # Text files often end with a trailing newline, 
which we
+                               # don't want to include in the env variable 
value. See
+                               # 
https://github.com/phusion/baseimage-docker/pull/49
+                               value = re.sub('\n\Z', '', f.read())
+                       new_env[name] = value
+                       trace("import " + name + " from " + filePath + " --- ")
+
+       for name, value in list(new_env.items()):
+               if override_existing_environment or name not in os.environ:
+                       os.environ[name] = value
+                       trace("set : " + name + " = "+ os.environ[name])
+               else:
+                       debug("ignore : " + name + " = " + xstr(value) + " 
(keep " + name + " = " + os.environ[name] + " )")
+
+def import_run_envvars():
+       clear_environ()
+       generic_import_envvars(RUN_ENVIRONMENT_DIR, True)
+
+def import_envvars():
+       generic_import_envvars(IMPORT_ENVIRONMENT_DIR, False)
+       generic_import_envvars(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR, False)
+
+def export_run_envvars(to_dir = True):
+       if to_dir and not os.path.exists(RUN_ENVIRONMENT_DIR):
+               warning("export_run_envvars: "+RUN_ENVIRONMENT_DIR+" don't 
exists")
+               return
+       shell_dump = ""
+       for name, value in list(os.environ.items()):
+               if name in ['USER', 'GROUP', 'UID', 'GID', 'SHELL']:
+                       continue
+               if to_dir:
+                       with open(RUN_ENVIRONMENT_DIR + os.sep + name, "w") as 
f:
+                               f.write(value)
+                       trace("export " + name + " to " + RUN_ENVIRONMENT_DIR + 
os.sep + name + " --- ")
+               shell_dump += "export " + sanitize_shenvname(name) + "=" + 
shquote(value) + "\n"
+
+       with open(RUN_ENVIRONMENT_FILE_EXPORT, "w") as f:
+               f.write(shell_dump)
+       trace("export "+RUN_ENVIRONMENT_FILE_EXPORT+" --- ")
+
+def create_run_envvars():
+       set_dir_env()
+       set_log_level_env()
+       import_envvars()
+       import_env_files()
+       decode_envvars()
+       export_run_envvars()
+
+def clear_run_envvars():
+       try:
+               shutil.rmtree(RUN_ENVIRONMENT_DIR)
+               os.makedirs(RUN_ENVIRONMENT_DIR)
+               os.chmod(RUN_ENVIRONMENT_DIR, 700)
+       except:
+               warning("clear_run_envvars: failed at some point...")
+
+def print_env_files_order(file_extensions):
+
+       if not os.path.exists(IMPORT_ENVIRONMENT_DIR):
+               warning("print_env_files_order "+IMPORT_ENVIRONMENT_DIR+" don't 
exists")
+               return
+
+       to_print = 'Caution: previously defined variables will not be 
overriden.\n'
+
+       file_found = False
+       for subdir, dirs, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)):
+               for file in files:
+                       filepath = subdir + os.sep + file
+                       if filepath.endswith(file_extensions):
+                               file_found = True
+                               filepath = subdir + os.sep + file
+                               to_print += filepath + '\n'
+
+       if file_found:
+               if log_level < LOG_LEVEL_DEBUG:
+                       to_print+='\nTo see how this files are processed and 
environment variables values,\n'
+                       to_print+='run this container with \'--loglevel debug\''
+
+               info('Environment files will be proccessed in this order : \n' 
+ to_print)
+
+def import_env_files():
+
+       if not os.path.exists(IMPORT_ENVIRONMENT_DIR):
+               warning("import_env_files: "+IMPORT_ENVIRONMENT_DIR+" don't 
exists")
+               return
+
+       file_extensions = ENV_FILES_YAML_EXTENSIONS + ENV_FILES_JSON_EXTENSIONS
+       print_env_files_order(file_extensions)
+
+       for subdir, dirs, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)):
+               for file in files:
+                       if file.endswith(file_extensions):
+                               filepath = subdir + os.sep + file
+
+                               try:
+                                       with open(filepath, "r") as f:
+
+                                               debug("--- process file : " + 
filepath+ " ---")
+
+                                               if 
file.endswith(ENV_FILES_YAML_EXTENSIONS):
+                                                       env_vars = yaml.load(f)
+
+                                               elif 
file.endswith(ENV_FILES_JSON_EXTENSIONS):
+                                                       env_vars = json.load(f)
+
+                                               for name, value in 
list(env_vars.items()):
+                                                       if not name in 
os.environ:
+                                                               if 
isinstance(value, list) or isinstance(value, dict):
+                                                                       
os.environ[name] = '#PYTHON2BASH:' + xstr(value)
+                                                               else:
+                                                                       
os.environ[name] = xstr(value)
+                                                               trace("set : " 
+ name + " = "+ os.environ[name])
+                                                       else:
+                                                               debug("ignore : 
" + name + " = " + xstr(value) + " (keep " + name + " = " + os.environ[name] + 
" )")
+                               except:
+                                       warning('failed to parse: ' + filepath)
+
+def remove_startup_env_files():
+
+       if os.path.isdir(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR):
+               try:
+                       shutil.rmtree(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR)
+               except:
+                       warning("remove_startup_env_files: failed to remove 
"+IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR)
+
+       if not os.path.exists(IMPORT_ENVIRONMENT_DIR):
+               warning("remove_startup_env_files: "+IMPORT_ENVIRONMENT_DIR+" 
don't exists")
+               return
+
+       for subdir, dirs, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)):
+               for file in files:
+                       filepath = subdir + os.sep + file
+                       if filepath.endswith(ENV_FILES_STARTUP_EXTENSIONS):
+                               try:
+                                       os.remove(filepath)
+                                       info("Remove file "+filepath)
+                               except:
+                                       warning("remove_startup_env_files: 
failed to remove "+filepath)
+
+def restore_environ():
+       clear_environ()
+       trace("--- Restore initial environment ---")
+       os.environ.update(environ_backup)
+
+def clear_environ():
+       trace("--- Clear existing environment ---")
+       os.environ.clear()
+
+def set_startup_scripts_env():
+       info("Set environment for startup files")
+       clear_run_envvars() # clear previous environment
+       create_run_envvars() # create run envvars with all env files
+
+def set_process_env(keep_startup_env = False):
+       info("Set environment for container process")
+       if not keep_startup_env:
+               remove_startup_env_files()
+       clear_run_envvars()
+
+       restore_environ()
+       create_run_envvars() # recreate env var without startup env files
+
+def setup_run_directories(args):
+
+       directories = (RUN_PROCESS_DIR, RUN_STARTUP_DIR, RUN_STATE_DIR, 
RUN_ENVIRONMENT_DIR)
+       for directory in directories:
+               if not os.path.exists(directory):
+                       os.makedirs(directory)
+
+                       if directory == RUN_ENVIRONMENT_DIR:
+                               os.chmod(directory, 700)
+
+       if not os.path.exists(RUN_ENVIRONMENT_FILE_EXPORT):
+               open(RUN_ENVIRONMENT_FILE_EXPORT, 'a').close()
+               os.chmod(RUN_ENVIRONMENT_FILE_EXPORT, 640)
+               uid = pwd.getpwnam("root").pw_uid
+               gid = grp.getgrnam("docker_env").gr_gid
+               os.chown(RUN_ENVIRONMENT_FILE_EXPORT, uid, gid)
+
+       if state_is_first_start():
+
+               if args.copy_service:
+                       copy_service_to_run_dir()
+
+               set_dir_env()
+
+               base_path = os.environ[ENVIRONMENT_SERVICE_DIR_KEY]
+               nb_service = len(listdir(base_path))
+
+               if nb_service > 0 :
+                       info("Search service in " + ENVIRONMENT_SERVICE_DIR_KEY 
+ " = "+base_path+" :")
+                       for d in listdir(base_path):
+                               d_path = base_path + os.sep + d
+                               if os.path.isdir(d_path):
+                                       if is_exe(d_path + os.sep + 
IMPORT_STARTUP_FILENAME):
+                                               info('link ' + d_path + os.sep 
+ IMPORT_STARTUP_FILENAME + ' to ' + RUN_STARTUP_DIR + os.sep + d)
+                                               try:
+                                                       os.symlink(d_path + 
os.sep + IMPORT_STARTUP_FILENAME, RUN_STARTUP_DIR + os.sep + d)
+                                               except OSError as detail:
+                                                       warning('failed to link 
' +  d_path + os.sep + IMPORT_STARTUP_FILENAME + ' to ' + RUN_STARTUP_DIR + 
os.sep + d + ': ' + xstr(detail))
+
+                                       if is_exe(d_path + os.sep + 
IMPORT_PROCESS_FILENAME):
+                                               info('link ' + d_path + os.sep 
+ IMPORT_PROCESS_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 
'run')
+
+                                               if not 
os.path.exists(RUN_PROCESS_DIR + os.sep + d):
+                                                       
os.makedirs(RUN_PROCESS_DIR + os.sep + d)
+                                               else:
+                                                       warning('directory ' + 
RUN_PROCESS_DIR + os.sep + d + ' already exists')
+
+                                               try:
+                                                       os.symlink(d_path + 
os.sep + IMPORT_PROCESS_FILENAME, RUN_PROCESS_DIR + os.sep + d + os.sep + 'run')
+                                               except OSError as detail:
+                                                       warning('failed to link 
' + d_path + os.sep + IMPORT_PROCESS_FILENAME + ' to ' + RUN_PROCESS_DIR + 
os.sep + d + os.sep + 'run : ' + xstr(detail))
+
+                                       if not args.skip_finish_files and 
is_exe(d_path + os.sep + IMPORT_FINISH_FILENAME):
+                                               info('link ' + d_path + os.sep 
+ IMPORT_FINISH_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 
'finish')
+
+                                               if not 
os.path.exists(RUN_PROCESS_DIR + os.sep + d):
+                                                       
os.makedirs(RUN_PROCESS_DIR + os.sep + d)
+
+                                               try:
+                                                       os.symlink(d_path + 
os.sep + IMPORT_FINISH_FILENAME, RUN_PROCESS_DIR + os.sep + d + os.sep + 
'finish')
+                                               except OSError as detail:
+                                                       warning('failed to link 
' + d_path + os.sep + IMPORT_FINISH_FILENAME + ' to ' + RUN_PROCESS_DIR + 
os.sep + d + os.sep + 'finish : ' + xstr(detail))
+
+def set_dir_env():
+       if state_is_service_copied_to_run_dir():
+               os.environ[ENVIRONMENT_SERVICE_DIR_KEY] = RUN_SERVICE_DIR
+       else:
+               os.environ[ENVIRONMENT_SERVICE_DIR_KEY] = IMPORT_SERVICE_DIR
+       trace("set : " + ENVIRONMENT_SERVICE_DIR_KEY + " = " + 
os.environ[ENVIRONMENT_SERVICE_DIR_KEY])
+
+       os.environ[ENVIRONMENT_STATE_DIR_KEY] = RUN_STATE_DIR
+       trace("set : " + ENVIRONMENT_STATE_DIR_KEY + " = " + 
os.environ[ENVIRONMENT_STATE_DIR_KEY])
+
+def set_log_level_env():
+       os.environ[ENVIRONMENT_LOG_LEVEL_KEY] = xstr(log_level)
+       trace("set : "+ENVIRONMENT_LOG_LEVEL_KEY+" = " + 
os.environ[ENVIRONMENT_LOG_LEVEL_KEY])
+
+def copy_service_to_run_dir():
+
+       if os.path.exists(RUN_SERVICE_DIR):
+               warning("Copy "+IMPORT_SERVICE_DIR+" to "+RUN_SERVICE_DIR + " 
ignored")
+               warning(RUN_SERVICE_DIR + " already exists")
+               return
+
+       info("Copy "+IMPORT_SERVICE_DIR+" to "+RUN_SERVICE_DIR)
+
+       try:
+               shutil.copytree(IMPORT_SERVICE_DIR, RUN_SERVICE_DIR)
+       except shutil.Error as e:
+               warning(e)
+
+       state_set_service_copied_to_run_dir()
+
+def state_set_service_copied_to_run_dir():
+       open(RUN_STATE_DIR+"/service-copied-to-run-dir", 'a').close()
+
+def state_is_service_copied_to_run_dir():
+       return os.path.exists(RUN_STATE_DIR+'/service-copied-to-run-dir')
+
+def state_set_first_startup_done():
+       open(RUN_STATE_DIR+"/first-startup-done", 'a').close()
+
+def state_is_first_start():
+       return os.path.exists(RUN_STATE_DIR+'/first-startup-done') == False
+
+def state_set_startup_done():
+       open(RUN_STATE_DIR+"/startup-done", 'a').close()
+
+def state_reset_startup_done():
+       try:
+               os.remove(RUN_STATE_DIR+"/startup-done")
+       except OSError:
+               pass
+
+def is_multiple_process_container():
+       return len(listdir(RUN_PROCESS_DIR)) > 1
+
+def is_single_process_container():
+       return len(listdir(RUN_PROCESS_DIR)) == 1
+
+def get_container_process():
+       for p in listdir(RUN_PROCESS_DIR):
+               return RUN_PROCESS_DIR + os.sep + p + os.sep + 'run'
+
+def is_runit_installed():
+       return os.path.exists('/usr/bin/sv')
+
+_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
+
+def shquote(s):
+       """Return a shell-escaped version of the string *s*."""
+       if not s:
+               return "''"
+       if _find_unsafe(s) is None:
+               return s
+
+       # use single quotes, and put single quotes into double quotes
+       # the string $'b is then quoted as '$'"'"'b'
+       return "'" + s.replace("'", "'\"'\"'") + "'"
+
+def sanitize_shenvname(s):
+       return re.sub(SHENV_NAME_WHITELIST_REGEX, "_", s)
+
+# Waits for the child process with the given PID, while at the same time
+# reaping any other child processes that have exited (e.g. adopted child
+# processes that have terminated).
+def waitpid_reap_other_children(pid):
+       global terminated_child_processes
+
+       status = terminated_child_processes.get(pid)
+       if status:
+               # A previous call to waitpid_reap_other_children(),
+               # with an argument not equal to the current argument,
+               # already waited for this process. Return the status
+               # that was obtained back then.
+               del terminated_child_processes[pid]
+               return status
+
+       done = False
+       status = None
+       while not done:
+               try:
+                       # 
https://github.com/phusion/baseimage-docker/issues/151#issuecomment-92660569
+                       this_pid, status = os.waitpid(pid, os.WNOHANG)
+                       if this_pid == 0:
+                               this_pid, status = os.waitpid(-1, 0)
+                       if this_pid == pid:
+                               done = True
+                       else:
+                               # Save status for later.
+                               terminated_child_processes[this_pid] = status
+               except OSError as e:
+                       if e.errno == errno.ECHILD or e.errno == errno.ESRCH:
+                               return None
+                       else:
+                               raise
+       return status
+
+def stop_child_process(name, pid, signo = signal.SIGTERM, time_limit = 
KILL_PROCESS_TIMEOUT):
+       info("Shutting down %s (PID %d)..." % (name, pid))
+       try:
+               os.kill(pid, signo)
+       except OSError:
+               pass
+       signal.alarm(time_limit)
+       try:
+               try:
+                       waitpid_reap_other_children(pid)
+               except OSError:
+                       pass
+       except AlarmException:
+               warning("%s (PID %d) did not shut down in time. Forcing it to 
exit." % (name, pid))
+               try:
+                       os.kill(pid, signal.SIGKILL)
+               except OSError:
+                       pass
+               try:
+                       waitpid_reap_other_children(pid)
+               except OSError:
+                       pass
+       finally:
+               signal.alarm(0)
+
+def run_command_killable(command):
+       status = None
+       debug_env_dump()
+       pid = os.spawnvp(os.P_NOWAIT, command[0], command)
+       try:
+               status = waitpid_reap_other_children(pid)
+       except BaseException:
+               warning("An error occurred. Aborting.")
+               stop_child_process(command[0], pid)
+               raise
+       if status != 0:
+               if status is None:
+                       error("%s exited with unknown status\n" % command[0])
+               else:
+                       error("%s failed with status %d\n" % (command[0], 
os.WEXITSTATUS(status)))
+               sys.exit(1)
+
+def run_command_killable_and_import_run_envvars(command):
+       run_command_killable(command)
+       import_run_envvars()
+       export_run_envvars(False)
+
+def kill_all_processes(time_limit):
+       info("Killing all processes...")
+       try:
+               os.kill(-1, signal.SIGTERM)
+       except OSError:
+               pass
+       signal.alarm(time_limit)
+       try:
+               # Wait until no more child processes exist.
+               done = False
+               while not done:
+                       try:
+                               os.waitpid(-1, 0)
+                       except OSError as e:
+                               if e.errno == errno.ECHILD:
+                                       done = True
+                               else:
+                                       raise
+       except AlarmException:
+               warning("Not all processes have exited in time. Forcing them to 
exit.")
+               try:
+                       os.kill(-1, signal.SIGKILL)
+               except OSError:
+                       pass
+       finally:
+               signal.alarm(0)
+
+def container_had_startup_script():
+       return (len(listdir(RUN_STARTUP_DIR)) > 0 or 
is_exe(RUN_STARTUP_FINAL_FILE))
+
+def run_startup_files(args):
+
+       # Run /container/run/startup/*
+       for name in listdir(RUN_STARTUP_DIR):
+               filename = RUN_STARTUP_DIR + os.sep + name
+               if is_exe(filename):
+                       info("Running %s..." % filename)
+                       run_command_killable_and_import_run_envvars([filename])
+
+       # Run /container/run/startup.sh.
+       if is_exe(RUN_STARTUP_FINAL_FILE):
+               info("Running "+RUN_STARTUP_FINAL_FILE+"...")
+               
run_command_killable_and_import_run_envvars([RUN_STARTUP_FINAL_FILE])
+
+def wait_for_process_or_interrupt(pid):
+       status = waitpid_reap_other_children(pid)
+       return (True, status)
+
+def run_process(args, background_process_name, background_process_command):
+       background_process_pid = 
run_background_process(background_process_name,background_process_command)
+       background_process_exited = False
+       exit_status = None
+
+       if len(args.main_command) == 0:
+               background_process_exited, exit_status = 
wait_background_process(background_process_name, background_process_pid)
+       else:
+               exit_status = run_foreground_process(args.main_command)
+
+       return background_process_pid, background_process_exited, exit_status
+
+def run_background_process(name, command):
+       info("Running "+ name +"...")
+       pid = os.spawnvp(os.P_NOWAIT, command[0], command)
+       debug("%s started as PID %d" % (name, pid))
+       return pid
+
+def wait_background_process(name, pid):
+       exit_code = None
+       exit_status = None
+       process_exited = False
+
+       process_exited, exit_code = wait_for_process_or_interrupt(pid)
+       if process_exited:
+               if exit_code is None:
+                       info(name + " exited with unknown status")
+                       exit_status = 1
+               else:
+                       exit_status = os.WEXITSTATUS(exit_code)
+                       info("%s exited with status %d" % (name, exit_status))
+       return (process_exited, exit_status)
+
+def run_foreground_process(command):
+       exit_code = None
+       exit_status = None
+
+       info("Running %s..." % " ".join(command))
+       pid = os.spawnvp(os.P_NOWAIT, command[0], command)
+       try:
+               exit_code = waitpid_reap_other_children(pid)
+               if exit_code is None:
+                       info("%s exited with unknown status." % command[0])
+                       exit_status = 1
+               else:
+                       exit_status = os.WEXITSTATUS(exit_code)
+                       info("%s exited with status %d." % (command[0], 
exit_status))
+       except KeyboardInterrupt:
+               stop_child_process(command[0], pid)
+               raise
+       except BaseException:
+               warning("An error occurred. Aborting.")
+               stop_child_process(command[0], pid)
+               raise
+
+       return exit_status
+
+def shutdown_runit_services():
+       debug("Begin shutting down runit services...")
+       os.system("/usr/bin/sv -w %d force-stop %s/* > /dev/null" % 
(KILL_PROCESS_TIMEOUT, RUN_PROCESS_DIR))
+
+def wait_for_runit_services():
+       debug("Waiting for runit services to exit...")
+       done = False
+       while not done:
+               done = os.system("/usr/bin/sv status "+RUN_PROCESS_DIR+"/* | 
grep -q '^run:'") != 0
+               if not done:
+                       time.sleep(0.1)
+                       shutdown_runit_services()
+
+def run_multiple_process_container(args):
+       if not is_runit_installed():
+               error("Error: runit is not installed and this is a multiple 
process container.")
+               return
+
+       background_process_exited=False
+       background_process_pid=None
+
+       try:
+               runit_command=["/usr/bin/runsvdir", "-P", RUN_PROCESS_DIR]
+               background_process_pid, background_process_exited, exit_status 
= run_process(args, "runit daemon", runit_command)
+
+               sys.exit(exit_status)
+       finally:
+               shutdown_runit_services()
+               if not background_process_exited:
+                       stop_child_process("runit daemon", 
background_process_pid)
+               wait_for_runit_services()
+
+def run_single_process_container(args):
+       background_process_exited=False
+       background_process_pid=None
+
+       try:
+               container_process=get_container_process();
+               background_process_pid, background_process_exited, exit_status 
= run_process(args, container_process, [container_process])
+
+               sys.exit(exit_status)
+       finally:
+               if not background_process_exited:
+                       stop_child_process(container_process, 
background_process_pid)
+
+def run_no_process_container(args):
+       if len(args.main_command) == 0:
+               args.main_command=['bash'] # run bash by default
+
+       exit_status = run_foreground_process(args.main_command)
+       sys.exit(exit_status)
+
+def run_finish_files():
+
+       # iterate process dir to find finish files
+       for name in listdir(RUN_PROCESS_DIR):
+               filename = RUN_PROCESS_DIR + os.sep + name + os.sep + "finish"
+               if is_exe(filename):
+                       info("Running %s..." % filename)
+                       run_command_killable_and_import_run_envvars([filename])
+
+def wait_states(states):
+       for state in states:
+               filename = RUN_STATE_DIR + os.sep + state
+               info("Wait state: " + state)
+
+               while not os.path.exists(filename):
+                       time.sleep(0.1)
+                       debug("Check file " + filename)
+                       pass
+               debug("Check file " + filename + " [Ok]")
+
+def run_cmds(args, when):
+       debug("Run commands before " + when + "...")
+       if len(args.cmds) > 0:
+
+               for cmd in args.cmds:
+                       if (len(cmd) > 1 and cmd[1] == when) or (len(cmd) == 1 
and when == "startup"):
+                               info("Running '"+cmd[0]+"'...")
+                               
run_command_killable_and_import_run_envvars(cmd[0].split())
+
+def main(args):
+
+       info(ENVIRONMENT_LOG_LEVEL_KEY + " = " + xstr(log_level) + " (" + 
log_level_switcher_inv.get(log_level) + ")")
+       state_reset_startup_done()
+
+       if args.set_env_hostname_to_etc_hosts:
+               set_env_hostname_to_etc_hosts()
+
+       wait_states(args.wait_states)
+       setup_run_directories(args)
+
+       if not args.skip_env_files:
+               set_startup_scripts_env()
+
+       run_cmds(args,"startup")
+
+       if not args.skip_startup_files and container_had_startup_script():
+               run_startup_files(args)
+
+       state_set_startup_done()
+       state_set_first_startup_done()
+
+       if not args.skip_env_files:
+               set_process_env(args.keep_startup_env)
+
+       run_cmds(args,"process")
+
+       debug_env_dump()
+
+       if is_single_process_container() and not args.skip_process_files:
+               run_single_process_container(args)
+
+       elif is_multiple_process_container() and not args.skip_process_files:
+               run_multiple_process_container(args)
+
+       else:
+               run_no_process_container(args)
+
+# Parse options.
+parser = argparse.ArgumentParser(description = 'Initialize the system.', 
epilog='Osixia! Light Baseimage: 
https://github.com/osixia/docker-light-baseimage')
+parser.add_argument('main_command', metavar = 'MAIN_COMMAND', type = str, 
nargs = '*',
+       help = 'The main command to run, leave empty to only run container 
process.')
+parser.add_argument('-e', '--skip-env-files', dest = 'skip_env_files',
+       action = 'store_const', const = True, default = False,
+       help = 'Skip getting environment values from environment file(s).')
+parser.add_argument('-s', '--skip-startup-files', dest = 'skip_startup_files',
+       action = 'store_const', const = True, default = False,
+       help = 'Skip running '+RUN_STARTUP_DIR+'/* and '+RUN_STARTUP_FINAL_FILE 
+ ' file(s).')
+parser.add_argument('-p', '--skip-process-files', dest = 'skip_process_files',
+       action = 'store_const', const = True, default = False,
+       help = 'Skip running container process file(s).')
+parser.add_argument('-f', '--skip-finish-files', dest = 'skip_finish_files',
+       action = 'store_const', const = True, default = False,
+       help = 'Skip running container finish file(s).')
+parser.add_argument('-o', '--run-only', type=str, 
choices=["startup","process","finish"], dest = 'run_only', default = None,
+       help = 'Run only this file type and ignore others.')
+parser.add_argument('-c', '--cmd', metavar=('COMMAND', 
'WHEN={startup,process,finish}'), dest = 'cmds', type = str,
+       action = 'append', default = [], nargs = "+",
+       help = 'Run this command before WHEN file(s). Default before startup 
file(s).')
+parser.add_argument('-k', '--no-kill-all-on-exit', dest = 'kill_all_on_exit',
+       action = 'store_const', const = False, default = True,
+       help = 'Don\'t kill all processes on the system upon exiting.')
+parser.add_argument('--wait-state', metavar = 'FILENAME', dest = 
'wait_states', type = str,
+       action = 'append', default=[],
+       help = 'Wait until the container state file exists in '+RUN_STATE_DIR+' 
directory before starting. Usefull when 2 containers share '+RUN_DIR+' 
directory via volume.')
+parser.add_argument('--wait-first-startup', dest = 'wait_first_startup',
+       action = 'store_const', const = True, default = False,
+       help = 'Wait until the first startup is done before starting. Usefull 
when 2 containers share '+RUN_DIR+' directory via volume.')
+parser.add_argument('--keep-startup-env', dest = 'keep_startup_env',
+       action = 'store_const', const = True, default = False,
+       help = 'Don\'t remove ' + xstr(ENV_FILES_STARTUP_EXTENSIONS) + ' 
environment files after startup scripts.')
+parser.add_argument('--copy-service', dest = 'copy_service',
+       action = 'store_const', const = True, default = False,
+       help = 'Copy '+IMPORT_SERVICE_DIR+' to '+RUN_SERVICE_DIR+'. Help to fix 
docker mounted files problems.')
+parser.add_argument('--dont-touch-etc-hosts', dest = 
'set_env_hostname_to_etc_hosts',
+       action = 'store_const', const = False, default = True,
+       help = 'Don\'t add in /etc/hosts a line with the container ip and 
$HOSTNAME environment variable value.')
+parser.add_argument('--keepalive', dest = 'keepalive',
+       action = 'store_const', const = True, default = False,
+       help = 'Keep alive container if all startup files and process exited 
without error.')
+parser.add_argument('--keepalive-force', dest = 'keepalive_force',
+       action = 'store_const', const = True, default = False,
+       help = 'Keep alive container in all circonstancies.')
+parser.add_argument('-l', '--loglevel', type=str, 
choices=["none","error","warning","info","debug","trace"], dest = 'log_level', 
default = "info",
+       help = 'Log level (default: info)')
+
+args = parser.parse_args()
+
+log_level_switcher = {"none": LOG_LEVEL_NONE,"error": 
LOG_LEVEL_ERROR,"warning": LOG_LEVEL_WARNING,"info": LOG_LEVEL_INFO,"debug": 
LOG_LEVEL_DEBUG, "trace": LOG_LEVEL_TRACE}
+log_level_switcher_inv = {LOG_LEVEL_NONE: 
"none",LOG_LEVEL_ERROR:"error",LOG_LEVEL_WARNING:"warning",LOG_LEVEL_INFO:"info",LOG_LEVEL_DEBUG:"debug",LOG_LEVEL_TRACE:"trace"}
+log_level = log_level_switcher.get(args.log_level)
+
+# Run only arg
+if args.run_only != None:
+       if args.run_only == "startup" and args.skip_startup_files:
+               error("Error: When '--run-only startup' is set 
'--skip-startup-files' can't be set.")
+               sys.exit(1)
+       elif args.run_only == "process" and args.skip_startup_files:
+               error("Error: When '--run-only process' is set 
'--skip-process-files' can't be set.")
+               sys.exit(1)
+       elif args.run_only == "finish" and args.skip_startup_files:
+               error("Error: When '--run-only finish' is set 
'--skip-finish-files' can't be set.")
+               sys.exit(1)
+
+       if args.run_only == "startup":
+               args.skip_process_files = True
+               args.skip_finish_files = True
+       elif args.run_only == "process":
+               args.skip_startup_files = True
+               args.skip_finish_files = True
+       elif args.run_only == "finish":
+               args.skip_startup_files = True
+               args.skip_process_files = True
+
+# wait for startup args
+if args.wait_first_startup:
+       args.wait_states.insert(0, 'first-startup-done')
+
+# Run main function.
+signal.signal(signal.SIGTERM, lambda signum, frame: 
ignore_signals_and_raise_keyboard_interrupt('SIGTERM'))
+signal.signal(signal.SIGINT, lambda signum, frame: 
ignore_signals_and_raise_keyboard_interrupt('SIGINT'))
+signal.signal(signal.SIGALRM, lambda signum, frame: raise_alarm_exception())
+
+exit_code = 0
+
+try:
+       main(args)
+
+except SystemExit as err:
+       exit_code = err.code
+       if args.keepalive and err.code == 0:
+               try:
+                       info("All process have exited without error, keep 
container alive...")
+                       while True:
+                               time.sleep(60)
+                               pass
+               except:
+                       info("Keep alive process ended.")
+
+except KeyboardInterrupt:
+       warning("Init system aborted.")
+       exit(2)
+
+finally:
+
+       run_cmds(args,"finish")
+
+       # for multiple process images finish script are run by runit
+       if not args.skip_finish_files and not is_multiple_process_container():
+               run_finish_files()
+
+       if args.keepalive_force:
+               try:
+                       info("All process have exited, keep container alive...")
+                       while True:
+                               time.sleep(60)
+                               pass
+               except:
+                       info("Keep alive process ended.")
+
+       if args.kill_all_on_exit:
+               kill_all_processes(KILL_ALL_PROCESSES_TIMEOUT)
+
+       exit(exit_code)
diff --git a/image/base-image/tool/setuser b/image/base-image/tool/setuser
new file mode 100755
index 0000000..06d7430
--- /dev/null
+++ b/image/base-image/tool/setuser
@@ -0,0 +1,64 @@
+#!/usr/bin/python3
+
+'''
+Copyright (c) 2013-2015 Phusion Holding B.V.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+'''
+
+import sys
+import os
+import pwd
+
+
+def abort(message):
+    sys.stderr.write("setuser: %s\n" % message)
+    sys.exit(1)
+
+
+def main():
+    '''
+    A simple alternative to sudo that executes a command as a user by setting
+    the user ID and user parameters to those described by the system and then
+    using execvp(3) to execute the command without the necessity of a TTY
+    '''
+
+    username = sys.argv[1]
+    try:
+        user = pwd.getpwnam(username)
+    except KeyError:
+        abort("user %s not found" % username)
+    os.initgroups(username, user.pw_gid)
+    os.setgid(user.pw_gid)
+    os.setuid(user.pw_uid)
+    os.environ['USER'] = username
+    os.environ['HOME'] = user.pw_dir
+    os.environ['UID'] = str(user.pw_uid)
+    try:
+        os.execvp(sys.argv[2], sys.argv[2:])
+    except OSError as e:
+        abort("cannot execute %s: %s" % (sys.argv[2], str(e)))
+
+if __name__ == '__main__':
+
+    if len(sys.argv) < 3:
+        sys.stderr.write("Usage: /sbin/setuser USERNAME COMMAND [args..]\n")
+        sys.exit(1)
+
+    main()
diff --git a/image/base-image/tool/wait-process 
b/image/base-image/tool/wait-process
new file mode 100755
index 0000000..8ac03f7
--- /dev/null
+++ b/image/base-image/tool/wait-process
@@ -0,0 +1,16 @@
+#!/bin/sh -e
+
+# wait startup to finish
+while ! test -f /container/run/state/startup-done
+do
+    sleep 0.5
+done
+
+for process in "$@"
+do
+    # wait service
+    while ! pgrep -c "${process}" > /dev/null
+    do
+        sleep 0.5
+    done
+done

Reply via email to