You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2020/07/04 19:32:57 UTC

[airflow-openldap] branch development updated (7ce084b -> 9d8ae27)

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

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


    from 7ce084b  Merge branch 'release-1.4.0' into stable
     new 6cee85f  Add base image from https://github.com/osixia/docker-light-baseimage
     new 9d8ae27  Add Airflow-specific modifications - image can be build on its own

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |   1 +
 build_and_push.sh                                  |  38 +
 image/Dockerfile                                   |  42 +-
 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    |   0
 .../: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 +
 47 files changed, 2609 insertions(+), 3 deletions(-)
 create mode 100755 build_and_push.sh
 create mode 100644 image/base-image/Dockerfile
 create mode 100755 image/base-image/build.sh
 create mode 100644 image/base-image/file/dpkg_nodoc
 create mode 100644 image/base-image/file/dpkg_nolocales
 create mode 100755 image/base-image/service-available/:cron/download.sh
 create mode 100755 image/base-image/service-available/:cron/install.sh
 create mode 100755 image/base-image/service-available/:cron/process.sh
 create mode 100755 image/base-image/service-available/:cron/startup.sh
 create mode 100644 image/base-image/service-available/:logrotate/assets/config/logrotate.conf
 create mode 100644 image/base-image/service-available/:logrotate/assets/config/logrotate_syslogng
 create mode 100755 image/base-image/service-available/:logrotate/download.sh
 create mode 100755 image/base-image/service-available/:logrotate/install.sh
 create mode 100755 image/base-image/service-available/:logrotate/startup.sh
 create mode 100755 image/base-image/service-available/:runit/download.sh
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/cfssl-default-env
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/README.md
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-config.json
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/config/ca-csr.json
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca-key.pem
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.csr
 copy example/extend-osixia-openldap/certs/ca.crt => image/base-image/service-available/:ssl-tools/assets/default-ca/default-ca.pem (100%)
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/default-env
 create mode 100644 image/base-image/service-available/:ssl-tools/assets/jsonssl-default-env
 create mode 100755 image/base-image/service-available/:ssl-tools/assets/tool/cfssl-helper
 create mode 100755 image/base-image/service-available/:ssl-tools/assets/tool/jsonssl-helper
 create mode 100755 image/base-image/service-available/:ssl-tools/assets/tool/ssl-auto-renew
 create mode 100755 image/base-image/service-available/:ssl-tools/assets/tool/ssl-helper
 create mode 100755 image/base-image/service-available/:ssl-tools/download.sh
 create mode 100755 image/base-image/service-available/:ssl-tools/startup.sh
 create mode 100644 image/base-image/service-available/:syslog-ng-core/assets/config/syslog-ng.conf
 create mode 100644 image/base-image/service-available/:syslog-ng-core/assets/config/syslog_ng_default
 create mode 100755 image/base-image/service-available/:syslog-ng-core/download.sh
 create mode 100755 image/base-image/service-available/:syslog-ng-core/install.sh
 create mode 100755 image/base-image/service-available/:syslog-ng-core/process.sh
 create mode 100755 image/base-image/service-available/:syslog-ng-core/startup.sh
 create mode 100755 image/base-image/tool/add-multiple-process-stack
 create mode 100755 image/base-image/tool/add-service-available
 create mode 100755 image/base-image/tool/complex-bash-env
 create mode 100755 image/base-image/tool/install-service
 create mode 100755 image/base-image/tool/log-helper
 create mode 100755 image/base-image/tool/run
 create mode 100755 image/base-image/tool/setuser
 create mode 100755 image/base-image/tool/wait-process


[airflow-openldap] 02/02: Add Airflow-specific modifications - image can be build on its own

Posted by po...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9d8ae2721543dc6671d2d6fc8125ceeb37989a49
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sat Jul 4 17:23:37 2020 +0200

    Add Airflow-specific modifications - image can be build on its own
    
    It uses only officially released code and does not have dependencies
    on base image from osixia.
    
    Part of https://github.com/apache/airflow/issues/9401
---
 .gitignore        |  1 +
 build_and_push.sh | 38 ++++++++++++++++++++++++++++++++++++++
 image/Dockerfile  | 42 +++++++++++++++++++++++++++++++++++++++---
 3 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index aa7b748..29ae96a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .DS_Store
 !/.git*
 /VOLUMES
+/.idea/
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..96568f9 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,13 @@ ARG LDAP_OPENLDAP_UID
 ARG PQCHECKER_VERSION=2.0.0
 ARG PQCHECKER_MD5=c005ce596e97d13e39485e711dcbc7e1
 
+MAINTAINER "Apache Airflow Community <de...@airflow.apache.org>"
+
+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 \
@@ -57,5 +86,12 @@ ADD environment /container/environment/99-default
 # Expose default ldap and ldaps ports
 EXPOSE 389 636
 
+LABEL org.apache.airflow.component="openldap"
+LABEL org.apache.airflow.openldap.version="${OPENLDAP_VERSION}"
+LABEL org.apache.airflow.airflow_openldap.version="${AIRFLOW_OPENLDAP_VERSION}"
+LABEL org.apache.airflow.commit_sha="${COMMIT_SHA}"
+
+ENTRYPOINT ["/container/tool/run"]
+
 # Put ldap config and database dir in a volume to persist data.
 # VOLUME /etc/ldap/slapd.d /var/lib/ldap


[airflow-openldap] 01/02: Add base image from https://github.com/osixia/docker-light-baseimage

Posted by po...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6cee85fe7e7d16cd58bc24652094ec133dd199e9
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sat Jul 4 17:21:56 2020 +0200

    Add base image from https://github.com/osixia/docker-light-baseimage
---
 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 +
 44 files changed, 2549 insertions(+)

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