diff --git a/config/lang/de.json b/config/lang/de.json
index 2701d0da5376e11c67043aba759f5832904655e2..20ce33395f647a675691759439bd919936327784 100644
--- a/config/lang/de.json
+++ b/config/lang/de.json
@@ -242,6 +242,9 @@
     "deploy": "Deploy",
     "deploy-configfile": "Konfiguration",
     "deploy-configfile-hint": "Hier können Variablen in Bash-Syntax hinterlegt werden. Bei einem Build werden diese in [root]/ci-custom-vars geschrieben und lassen sich vom onbuild oder ondeploy Hook lesen.",
+    "deploy-rollout-plugin": "Rollout-Plugin",
+    "deploy-rollout-plugin-hint": "Geben Sie vor, wie die Zielsystem angesteuert werden, um das Paket zu installieren.",
+    "deploy-rollout-plugin-config": "Konfiguration des Plugins",
     "deploy-hint": "Deploy der Queue von Phase [%s]",
     "deploy-impossible": "Deploy der Queue von Phase [%s] ist nicht möglich.",
     "deploy-settings": "Deployment-Einstellungen",
diff --git a/config/lang/en.json b/config/lang/en.json
index ce3575c1c14d687f50108d2aeaa856c8c5b5a693..9f575f385d20965c97c8dc3cd5abf9eaf3d3693b 100644
--- a/config/lang/en.json
+++ b/config/lang/en.json
@@ -244,6 +244,9 @@
     "deploy": "Deploy",
     "deploy-configfile": "Configuration",
     "deploy-configfile-hint": "Here you can place variables in Bash syntax. During the build it will be writen as [root]/ci-custom-vars and is readable in the onbuild oder ondeploy hook.",
+    "deploy-rollout-plugin": "Rollout plugin",
+    "deploy-rollout-plugin-hint": "Here you can define how to trigger a remote system to deploy and install the built package.",
+    "deploy-rollout-plugin-config": "Configuration of the plugin",
     "deploy-hint": "Deploy queue of phase [%s]",
     "deploy-impossible": "Deploy queue of phase [%s] is not possible.",
     "deploy-settings": "Deployment settings",
diff --git a/public_html/deployment/classes/formgen.class.php b/public_html/deployment/classes/formgen.class.php
index a8f38502842d74c055d39dd4c18e885876886bb8..b6f9676733da30f211aed661346936db4f636289 100644
--- a/public_html/deployment/classes/formgen.class.php
+++ b/public_html/deployment/classes/formgen.class.php
@@ -192,7 +192,7 @@ class formgen {
                     $s = preg_replace('/\W/iu', '', $sId . $idOption);
                     $sOptionId = preg_replace('/[äöüß]/i', '', $s);
                     $sFormElement.='    <input type="radio" id="' . $sOptionId . '" value="' . $idOption . '" ';
-                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked"), $aOptionData);
+                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked,disabled"), $aOptionData);
                     $sFormElement.=" " . $this->_addHtmlAtrributes(explode(",", "name"), $elementData);
                     $sFormElement.='/><label for="' . $sOptionId . '">' . $aOptionData["label"] . '</label></div>';
                 }
diff --git a/public_html/deployment/classes/htmlguielements.class.php b/public_html/deployment/classes/htmlguielements.class.php
index 7eb28ba75d56bac71977571f858d0ae39cb8b99e..7efad72e9dacde729c80dc8250176d80785aa913 100644
--- a/public_html/deployment/classes/htmlguielements.class.php
+++ b/public_html/deployment/classes/htmlguielements.class.php
@@ -152,6 +152,7 @@ class htmlguielements{
             'delete'=>'fas fa-trash',
             'deploy'=>'fas fa-forward',
             'deploy-configfile'=>'far fa-file-code',
+            'deploy-rollout-plugin'=>'fas fa-plug',
             'filter'=>'fas fa-filter',
             'foreman'=>'fas fa-hard-hat',
             'gotop'=>'fas fa-arrow-up',
diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php
index dd56aceb4dae443f73a2400b2a40150453bc690e..ae1b32f1641b1c4b188a5cca44d03ad6dbda7b10 100644
--- a/public_html/deployment/classes/project.class.php
+++ b/public_html/deployment/classes/project.class.php
@@ -7,6 +7,7 @@ require_once __DIR__.'/../inc_functions.php';
 require_once 'base.class.php';
 require_once 'htmlguielements.class.php';
 require_once 'messenger.class.php';
+require_once 'rollout_base.class.php';
 
 /* ######################################################################
 
@@ -81,13 +82,25 @@ class project extends base {
      */
     private $_iRcAll = 0;
 
+        
+    /**
+     * reference to html renderer class to draw output items
+     * @var object
+     */
     protected $_oHtml = false;
     
     /**
      * object to access a version control, .e. git
-     * @var type 
+     * @var object
      */
     private $_oVcs = false;
+    
+    /**
+     * object for rollout
+     * @var type
+     */
+    public $oRolloutPlugin = false;
+    
     private $_sBranchname = false;
     
     /**
@@ -1096,6 +1109,12 @@ class project extends base {
         return $sNextPhase;
     }
 
+    /**
+     * get an array with deploy status ...  
+     *    'inprogress'=>do versions differ from phase to phase = rollout of a version is in progress
+          'hasQueue'=>is there a package in a queue (waiting for deployment time to get ready to be installed)
+     * @return array
+     */
     public function getProgress(){
         $this->getAllPhaseInfos();
         return $this->_aData['progress'];
@@ -1310,6 +1329,33 @@ class project extends base {
         return $this->_oVcs;
     }
 
+    /**
+     * get an array of enabled plugins
+     * @param string  $sSection  one of false|"rollout"|...
+     * @return array
+     */
+    public function getConfiguredPlugins($sSection=false){
+        $aReturn=array();
+        if(!$sSection){
+            $aReturn=$this->_aConfig["plugins"];
+        } else {
+            foreach ($this->_aConfig["plugins"]["rollout"] as $sPluginName=>$aItem) {
+                $aReturn[$sPluginName] = $aItem;
+            }
+        }
+        return $aReturn;
+    }
+    
+    /**
+     * get a location of a plugin file with full path
+     * @param string  $sType         type of plugin, i.e. "rollout"
+     * @param string  $sPluginName   Name of plugin
+     * @return string
+     */
+    protected function _getPluginFilename($sType, $sPluginName){
+        return __DIR__.'/../plugins/'.$sType.'/'.$sPluginName.'/'.$sType.'_'.$sPluginName.'.php';
+    }
+    
     /**
      * get a flat array of all existing ssh keys
      * @return array
@@ -1435,6 +1481,28 @@ class project extends base {
         // echo "<pre>" . print_r($aData, true) . "</pre>";
 
         $this->_verifyConfig();
+        
+        // ----- init rollout plugin
+        // set name of the activated plugin for this project
+        $sPluginName=(isset($this->_aPrjConfig['deploy']['enabled_rollout_plugin']) && $this->_aPrjConfig['deploy']['enabled_rollout_plugin']) 
+                ? $this->_aPrjConfig['deploy']['enabled_rollout_plugin']
+                : 'default'
+            ;
+        $this->oRolloutPlugin = false;
+        try{
+            require_once $this->_getPluginFilename('rollout', $sPluginName);
+            $sPluginClassname='rollout_'.$sPluginName;
+            $this->oRolloutPlugin = new $sPluginClassname(array(
+                'lang'=>$this->_aConfig['lang'],
+                'phase'=>false,
+                'globalcfg'=>$this->_aConfig['plugins']['rollout'][$sPluginName],
+                'projectcfg'=>$this->_aPrjConfig,
+            ));
+            // print_r($this->_oRolloutPlugin->getPluginfos());
+            // print_r($this->_oRolloutPlugin->getName());
+        } catch (Exception $ex) {
+            
+        }
         return true;
     }
 
@@ -3219,7 +3287,7 @@ class project extends base {
         $sMessages = '';
         require_once ("formgen.class.php");
 
-        
+                
         $aSelectSlack = array(
                 'type' => 'hidden',
                 'name' => 'messenger[slack]',
@@ -3254,6 +3322,73 @@ class project extends base {
             
         }
         
+        // ---------- Rollout plugins
+        $aRollout = array(
+            'project-select' => array(
+                'type' => 'radio',
+                'name' => 'deploy[enabled_rollout_plugin]',
+                'label' => t("deploy-rollout-plugin"),
+            ),
+            'project-config' => '',
+        );
+        foreach (array_keys($this->getConfiguredPlugins('rollout')) as $sPluginName){
+
+            $sPluginFile=$this->_getPluginFilename('rollout', $sPluginName);
+            $TmpRolloutPlugin = false;
+            $sMyClassname='rollout_'. $sPluginName;
+            $sMyDivId='rollout-'. $sPluginName.'-config';
+            $sMyDivClass='rolloutconfigdiv';
+            $sMyDivClassActive='rolloutconfigdiv-'. $sPluginName;
+            $bActive=$sPluginName === $this->oRolloutPlugin->getId();
+
+            if(file_exists($sPluginFile)){
+                try{
+                    include_once $this->_getPluginFilename('rollout', $sPluginName);
+                    $TmpRolloutPlugin = new $sMyClassname(array(
+                        'lang'=>$this->_aConfig['lang'],
+                        'phase'=>false,
+                        'globalcfg'=>$this->_aConfig['plugins']['rollout'][$sPluginName],
+                        'projectcfg'=>$this->_aPrjConfig,
+                    ));
+                    $aRollout['project-select']['options'][$sPluginName]=array(
+                            'label' => $TmpRolloutPlugin->getName(),
+                            'checked' => $bActive,
+                            'onclick' => '$(\'.'.$sMyDivClass.'\').hide(); $(\'.' . $sMyDivClassActive . '\').show();',
+                        );
+                    
+                    $aRollout['project-config'].=''
+                            . '<div id="'.$sMyDivId.'" class="'.$sMyDivClass.' '.$sMyDivClassActive.'"'
+                            . ($bActive ? '' : 'style="display: none;"' )
+                            . '>'
+                                . $TmpRolloutPlugin->renderFormdata4Project()
+                            . '</div>'
+                            ;
+                    
+                    // generate form firlds for each phase
+                    foreach(array_keys($this->getPhases()) as $sMyPhase){
+                        $aRollout[$sMyPhase].=''
+                            . '<div id="'.$sMyDivId.'-'.$sMyPhase.'" class="'.$sMyDivClass.' '.$sMyDivClassActive.'"'
+                            . ($bActive ? '' : 'style="display: none;"' )
+                            . '>'
+                                . $TmpRolloutPlugin->renderFormdata4Phase($sMyPhase)
+                            . '</div>'
+                            ;
+                    }
+                } catch (Exception $ex) {
+
+                }
+            } else {
+                $aRollout['project-select']['options'][$sPluginName]=array(
+                        'label' => "not found: " . $sMyClassname,
+                        'checked' => false,
+                        'disabled' => "disabled",
+                    );
+
+                
+            }
+        }
+        // ---------- /Rollout plugins
+        
         $aForemanHostgroups = false;
         $iForemanHostgroupDefault = false;
         $sForemanHostgroupDefault = false;
@@ -3364,14 +3499,19 @@ class project extends base {
                                 <li><a href="#tab2" data-toggle="tab">' . $this->_oHtml->getIcon('repository').t('repositoryinfos') . '</a></li>
 
                                 <li><a href="#tab3" data-toggle="tab">' . $this->_oHtml->getIcon('deploy-configfile').t('deploy-configfile') . '</a></li>
-                                <li><a href="#tab4" data-toggle="tab">' . $this->_oHtml->getIcon('phase').t('phases') . '</a></li>
-                                <li><a href="#tab5" data-toggle="tab">' . $this->_oHtml->getIcon('raw-data').t('raw-data') . '</a></li>
+                                <li><a href="#tab4" data-toggle="tab">' . $this->_oHtml->getIcon('deploy-rollout-plugin').t('deploy-rollout-plugin') . '</a></li>
+                                <li><a href="#tab5" data-toggle="tab">' . $this->_oHtml->getIcon('phase').t('phases') . '</a></li>
+                                <li><a href="#tab6" data-toggle="tab">' . $this->_oHtml->getIcon('raw-data').t('raw-data') . '</a></li>
                             </ul>
                             <div class="tab-content">
                             <div class="tab-pane active" id="tab1">
                             
                             ',
                     ),
+
+                    // --------------------------------------------------
+                    // Tab for metadata
+                    // --------------------------------------------------
                     'input' . $i++ => array(
                         'type' => 'text',
                         'name' => 'label',
@@ -3418,6 +3558,9 @@ class project extends base {
                     ),
                     
                     'input' . $i++ => $aSelectSlack,
+                    
+                    // --------------------------------------------------
+                    // Tab soources repository & build
                     // --------------------------------------------------
                     'input' . $i++ => array(
                         'type' => 'markup',
@@ -3494,6 +3637,9 @@ class project extends base {
                             ),
                         ),
                     ),
+
+                    // --------------------------------------------------
+                    // Tab for config and API key
                     // --------------------------------------------------
                     'input' . $i++ => array(
                         'type' => 'markup',
@@ -3529,15 +3675,37 @@ class project extends base {
                         . '</p></div>',
                     ),
                     
+                    // --------------------------------------------------
+                    // Tab rollout plugin
                     // --------------------------------------------------
                     'input' . $i++ => array(
                         'type' => 'markup',
                         'value' => ' </div><div class="tab-pane" id="tab4">
+                            <p>' . t('deploy-rollout-plugin-hint') . '</p>',
+                    ),
+                    // select box for active rollout plugin
+                    $aRollout['project-select'],
+                    
+                    // project based config 
+                    'input' . $i++ => array(
+                        'type' => 'markup',
+                        'value' => ''
+                            . '<hr>'
+                                .'<label class="col-sm-2">'.t('deploy-rollout-plugin-config') .'</label>'
+                                .'<div class="col-sm-10">'. $aRollout['project-config'].'</div>'
+                    ),
+                    // --------------------------------------------------
+                    'input' . $i++ => array(
+                        'type' => 'markup',
+                        'value' => ' </div><div class="tab-pane" id="tab5">
                             <p>' . sprintf(t("class-project-info-setup-phaseinfos"), $this->getNextPhase()) . '</p>',
                     ),
                 ),
             ),
         );
+        // --------------------------------------------------
+        // Tab for phases
+        // --------------------------------------------------
         if ($aSelectForemanGroups) {
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'markup',
@@ -3649,7 +3817,7 @@ class project extends base {
                 // 'required' => 'required',
                 'validate' => 'isastring',
                 'size' => 100,
-                'placeholder' => 'http://' . $sPhase . '.[' . t("project") . '].[...]/',
+                'placeholder' => 'https://' . $sPhase . '.[' . t("project") . '].[...]/',
             );
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'radio',
@@ -3667,7 +3835,8 @@ class project extends base {
                         'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'none\' : \'block\') )',
                     ),
                     'puppet' => array(
-                        'label' => t("deploymethod-puppet"),
+                        // 'label' => t("deploymethod-puppet").' - '.  $this->oRolloutPlugin->getName(),
+                        'label' => t("deploy-rollout-plugin"),
                         'checked' => $sDeploymethod === "puppet",
                         'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'block\' : \'none\') )',
                     ),
@@ -3681,11 +3850,23 @@ class project extends base {
                  */
                 ),
             );
+            
+
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'markup',
                 'value' => ''
                 . '<div id="' . $sDivId4TargetHosts . '" ' . ($sDeploymethod !== "none" ? '' : ' style="display: none;"') . '">'
             );
+            
+            // rollout plugin: phase specific overrides
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'markup',
+                'value' => ''
+                    // . '<hr>'
+                    .'<label class="col-sm-2">'.t('deploy-rollout-plugin-config') .'</label>'
+                    .'<div class="col-sm-10">'.$aRollout[$sPhase].'</div>'
+            ); 
+            
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'text',
                 'name' => 'phases[' . $sPhase . '][hosts]',
@@ -3758,11 +3939,15 @@ class project extends base {
                 'value' => '</td></tr></tbody></table>',
             );
         } // END: loop over phases
+
+        // --------------------------------------------------
+        // Tab for raw data
+        // --------------------------------------------------
         $aForms["setup"]["form"]['input' . $i++] = array(
             'type' => 'markup',
             'value' => '</div>'
             
-                . '<div class="tab-pane" id="tab5">'
+                . '<div class="tab-pane" id="tab6">'
                 . '<br><pre>'.print_r($this->_aPrjConfig, 1).'</pre>'
                 . '</div>'
             
diff --git a/public_html/deployment/classes/rollout.interface.php b/public_html/deployment/classes/rollout.interface.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffdb3e482b74b64bd5be16d6c6862a3ed489db50
--- /dev/null
+++ b/public_html/deployment/classes/rollout.interface.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * INTERFACE for rollout plugins
+ * 
+ * @author hahn
+ */
+interface iRolloutplugin {
+
+    // ----------------------------------------------------------------------
+    // VERIFY
+    // ----------------------------------------------------------------------
+    
+    /**
+     * check requirements if the plugin could work
+     */
+    public function checkRequirements();
+    
+    /**
+     * check access to a deploy target
+     */
+    public function checkConnectionToTarget();
+
+    // ----------------------------------------------------------------------
+    // SETTER
+    // ----------------------------------------------------------------------
+    
+    // ----------------------------------------------------------------------
+    // GETTER
+    // ----------------------------------------------------------------------
+
+    /**
+     * get configuration for the project .. or more specifi for a given phase
+     * @param string  $sPhase
+     */
+    public function getConfig($sPhase=false);
+    
+    /**
+     * get name of plugin as string ... language specific
+     */
+    public function getName();
+    
+    /**
+     * get description of plugin as string ... language specific
+     */
+    public function getDescription();
+    
+    /**
+     * get array of data in info.js
+     */
+    public function getPluginInfos();
+    
+    // ----------------------------------------------------------------------
+    // RENDERER
+    // ----------------------------------------------------------------------
+
+    // ----------------------------------------------------------------------
+    // ACTIONS
+    // ----------------------------------------------------------------------
+
+    
+}
+
diff --git a/public_html/deployment/classes/rollout_base.class.php b/public_html/deployment/classes/rollout_base.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..c27d702b4a73547bbad24ca8dbe509394937d3d1
--- /dev/null
+++ b/public_html/deployment/classes/rollout_base.class.php
@@ -0,0 +1,417 @@
+<?php
+require_once 'rollout.interface.php';
+
+/**
+ * rollout_base class that will beextended in a rollout plugin
+ *
+ * 
+ * @author axel
+ */
+class rollout_base implements iRolloutplugin{
+    
+    // ---------------------------------------------------------------
+    // VARIABLES
+    // ---------------------------------------------------------------
+    /**
+     * identifier for current plugin; it us used to find the current plugin
+     * settings in the config structore for global and project based config
+     * @var string
+     */
+    protected $_sPluginId='UNSET';
+    /**
+     * data with plugin infos (read from info.json)
+     * @var array
+     */
+    protected $_aPlugininfos=false;
+    
+    /**
+     * array with translation texts
+     * @var type
+     */
+    protected $_aLang=false;
+    
+    /**
+     * set language; 2 letter code, i.e. "de"; default language is "en" ; a 
+     * file "lang_en.json" is required in the plugin dir
+     * @var string
+     */
+    protected $_sLang = 'en';
+    
+    /**
+     * string with phase of project; one of preview|stage|live
+     * @var type
+     */
+    protected $_sPhase = false;
+    
+    /**
+     * global configuration of the rollout plugin
+     * @var array
+     */
+    protected $_aCfgGlobal = false;
+    /**
+     * configuration of the project
+     * @var array
+     */
+    protected $_aCfgProject = false;
+    
+    protected $_sNamePrefix4Project=false; // set in constructor
+    protected $_sNamePrefix4Phase=false; // set in constructor
+    
+    // ---------------------------------------------------------------
+    // CONSTRUCTOR
+    // ---------------------------------------------------------------
+
+    /**
+     * initialize rollout plugin
+     * @param array $aParams  hash with those possible keys
+     *                  lang         string   language, i.e. 'de'
+     *                  phase        string   name of phase in a project
+     *                  globalcfg    array    given global config $aConfig
+     *                  projectcfg   array    project config to generate config 
+     *                                        for project and all phases
+     * @return boolean
+     */
+    public function __construct($aParams) {
+        
+        // set current plugin id - taken from plugin directory name above
+        $oReflection=new ReflectionClass($this);
+        $this->_sPluginId=basename(dirname($oReflection->getFileName()));
+   
+        // ----- init language
+        if (isset($aParams['lang'])){
+            $this->setLang($aParams['lang']);
+        } else {
+            $this->setLang();
+        }
+        
+        // ----- init phase
+        if (isset($aParams['phase'])){
+            $this->setPhase($aParams['phase']);
+        }
+
+        // ----- init global config
+        if (isset($aParams['globalcfg'])){
+            $this->setGlobalConfig($aParams['globalcfg']);
+        }
+        // ----- init project config
+        if (isset($aParams['projectcfg'])){
+            $this->setProjectConfig($aParams['projectcfg']);
+        }
+        return true;
+    }
+    
+    // ---------------------------------------------------------------
+    // FORM HELPER
+    // ---------------------------------------------------------------
+
+    /**
+     * get a string for a prefix for name attribute in form vars. 
+     * It is important to store the value in the wanted structure.
+     * 
+     * @param type $sPhase
+     * @return type
+     */
+    protected function _getNamePrefix($sPhase=false){
+        return ($sPhase
+            ? 'phases['.$sPhase.'][plugins][rollout]['.$this->getId().']'
+            : 'plugins[rollout]['.$this->getId().']'
+        );
+    }
+    /**
+     * render a form by given form elementes 
+     * @param  array   $aFormdata  array of form elements
+     * @param  string  $sKey       part of the identifier used in id of the input field
+     * @return string
+     */
+    protected function _renderForm($aFormdata, $sKey){
+        static $i;
+        if (!isset($i)){
+            $i=0;
+        }
+
+        $sReturn='';
+        $sKeyPrefix=$this->getId().'_'.$sKey;
+        
+        $oForm = new formgen($aForms);
+        foreach ($aFormdata as $elementData) {
+            $elementKey=$sKeyPrefix.'_'.$i++;
+            $sReturn.=$oForm->renderHtmlElement($elementKey, $elementData);
+        }
+        return $sReturn;
+    }
+    
+    /**
+     * render form fields for global plugin variables
+     * @param string  $sScope       scope of vars ... one of global|project|phase
+     * @param string  $sPhase       optional: render global vars in a phase; if no phase was set it renders form fields for project based settings
+     * @return string
+     */
+    protected function _renderForm4Vars($sScope, $sPhase=false){
+        $sReturn='';
+        
+        // test vars from info.json file
+        $aInfos=$this->getPluginInfos();
+        if(!isset($aInfos['vars'][$sScope]) || !count($aInfos['vars'][$sScope])){
+            return '---<br>';
+        }
+        
+        $sKey=($sPhase ? 'phase_'.$sPhase : 'project');
+        $sPrefixName=$this->_getNamePrefix($sPhase);
+        
+        // set defaults - to be used in placeholder attribute
+        // no phase = project level - take global defaults of ci config
+        // on a phase - fetch merged cofig data of project
+        $aDefaultValues=($sPhase ? $this->getConfig() : $this->_aCfgGlobal);
+        $aDefaultSource=($sPhase ? 'project' : 'global');
+                
+        // set defaults - to be used in value attribute
+        $aValues=($sPhase 
+                ? $this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()] 
+                : $this->_aCfgProject['plugins']['rollout'][$this->getId()]
+        );
+        
+        $aFormdata=array();
+ 
+        // create form fields
+        // $aFormdata[]=array('type' => 'markup','value' => '<br>'.$this->_t('section-override-'.$sScope.'-vars').':');
+        $aFormdata[]=array('type' => 'markup','value' => '<div style="style: clear: left;"></div><h4>'.$this->getId() .' :: '. $sScope.'</h4>');
+        
+        $sMiss='';
+        foreach ($aInfos['vars'][$sScope] as $sVarname=>$aVarinfos){
+            if ($sScope==='global' && !isset($this->_aCfgGlobal[$sVarname])){
+                $sMiss.='- plugin var was not set in global CI config: plugins -> rollout -> '.$this->getId().' -> "'.$sVarname.'".<br>';
+            }
+            if($aVarinfos['type']=="text"){
+                $aFormdata[]=array(
+                    'type' => 'text',
+                    'name' => $sPrefixName.'['.$sVarname.']',
+                    'label' => $this->_t($sVarname.'-label'),
+                    'title' => $this->_t($sVarname.'-hint'),
+                    'value' => (isset($aValues[$sVarname]) ? $aValues[$sVarname] : ''),
+                    // 'required' => 'required',
+                    'validate' => 'isastring',
+                    // 'size' => 25,
+                    // 'placeholder' => (isset($this->_aCfgGlobal[$sVarname]) ? $this->_aCfgGlobal[$sVarname] : '') . ' | '.$aDefaultValues[$sVarname],
+                    'placeholder' => (isset($aDefaultValues[$sVarname]) 
+                            ? $aDefaultSource.': '.$aDefaultValues[$sVarname] 
+                            : (isset($aVarinfos['default']) ? $aVarinfos['default'] : 'N.A.')
+                        ),
+                );
+               } else {
+                   $sMiss.='- plugin var "'.$sVarname.'" does not have type text. It cannot be rendered so far.<br>';
+               }
+        }
+        // $aFormdata[]=array('type' => 'markup','value' => '<div style="style: clear: left;"></div><br><br>');
+        return $this->_renderForm($aFormdata, $sKey)
+            . ($sMiss ? '<pre>WARNINGS:<br>'.$sMiss.'</pre>' : '')
+            ;
+    }
+    
+    /**
+     * get a translated text from lang_XX.json in plugin dir;
+     * If the key is missed it returns "[KEY :: LANG]"
+     * 
+     * @see setLang()
+     * @param string $sKey  key to find in lang file
+     * @return string
+     */
+    protected function _t($sKey){
+        return (isset($this->_aLang[$sKey]) && $this->_aLang[$sKey])
+                ? $this->_aLang[$sKey]
+                : "[ $sKey :: $this->_sLang ]"
+        ;
+    }
+
+    // ---------------------------------------------------------------
+    // PUBLIC METHODS
+    // ---------------------------------------------------------------
+
+    /**
+     * set language for output of formdata and other texts.
+     * This method loads the language file into a hash. The output of 
+     * translated texts can be done with $this->_t("your_key")
+     * 
+     * @see _t()
+     * @param string   $sLang  language code, i.e. "de"
+     * @return boolean
+     */
+    public function setLang($sLang=false){
+        $this->_sLang=$sLang ? $sLang : $this->_sLang;
+        
+        $oReflection=new ReflectionClass($this);
+        $sFile=dirname($oReflection->getFileName()) . '/lang_'.$this->_sLang.'.json';
+        $this->_aLang=(file_exists($sFile)) ? json_decode(file_get_contents($sFile), 1) : $this->_aLang;
+        return true;
+    }
+    
+    /**
+     * set a phase for automatic use GETTER methods
+     */
+    public function setPhase($sPhase){
+        $this->_sPhase=$sPhase;
+        return true;
+    }
+    
+
+    // ----------------------------------------------------------------------
+    // INTERFACE :: CHECKS
+    // ----------------------------------------------------------------------
+
+    /**
+     * check requirements if the plugin could work
+     */
+    public function checkRequirements(){
+        // no specific checks needed ... always true
+        return true;
+    }
+
+    /**
+     * check access to a deploy target
+     */
+    public function checkConnectionToTarget(){
+        // do nothing ... always true
+        return true;
+    }
+
+    // ----------------------------------------------------------------------
+    // INTERFACE :: SETTER
+    // ----------------------------------------------------------------------
+
+
+    /**
+     * set Config ... by given global config of the current plugin
+     * @param array $aConfigArray 
+     */
+    public function setGlobalConfig($aConfigArray){
+        return $this->_aCfgGlobal=$aConfigArray;
+    }
+
+
+
+    /**
+     * set Config ... by given project config
+     */
+    public function setProjectConfig($aProjectConfigArray){
+        $this->_aCfgProject=$aProjectConfigArray;
+        // echo '<pre>'.print_r($aProjectConfigArray, 1).'</pre>';
+        // ----- ensure that the config structure exists 
+        // (it is easier fo handling in getConfig())
+        if (!isset($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId])){
+            /*
+            if (!isset($this->_aCfgProject['plugins']['rollout'])){
+                if (!isset($this->_aCfgProject['plugins'])){
+                    $this->_aCfgProject['plugins']=array();
+                }
+                $this->_aCfgProject['plugins']['rollout']=array();
+            }
+             * 
+             */
+            $this->_aCfgProject['plugins']['rollout'][$this->_sPluginId]=array('INFO'=>'created');
+        }
+        
+        // unset empty project values to get global values
+
+        foreach ($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId] as $sVarname=>$value){
+            if ($value===''){
+                unset($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId][$sVarname]);
+            }
+        }
+        foreach (array_keys($this->_aCfgProject['phases']) as $sMyPhase){
+            if (isset($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()])){
+                foreach($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()] as $sVarname=>$value){
+                    if ($value===''){
+                        unset($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()][$sVarname]);
+                    }
+                }
+            }
+        }
+        // TODO: 
+        return $this->_aCfgProject;
+    }
+    
+    // ----------------------------------------------------------------------
+    // INTERFACE :: GETTER
+    // ----------------------------------------------------------------------
+
+    /**
+     * get a hash with the merged config for project or mo specific: of a given 
+     * phase
+     * @param  string  $sPhase
+     * @return array
+     */
+    public function getConfig($sPhase=false){
+
+        return ($sPhase && isset($this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()]))
+            ? array_merge($this->_aCfgGlobal, $this->_aCfgProject['plugins']['rollout'][$this->getId()], $this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()])
+            : array_merge($this->_aCfgGlobal, $this->_aCfgProject['plugins']['rollout'][$this->getId()])
+        ;
+    }
+    
+    /**
+     * get string with current ID
+     * @return string
+     */
+    public function getId(){
+        return $this->_sPluginId;
+    }
+    
+    /**
+     * get string with plugin name (taken from plugin language file)
+     * @return string
+     */
+    public function getName(){
+        return $this->_t('plugin_name');
+    }
+    
+    /**
+     * get string with plugin description (taken from plugin language file)
+     * @return string
+     */
+    public function getDescription(){
+        return $this->_t('description');
+    }
+    /**
+     * get array read from info.json
+     * @return type
+     */
+    public function getPluginInfos(){
+
+        if ($this->_aPlugininfos){
+            return $this->_aPlugininfos;
+        }
+        
+        $oReflection=new ReflectionClass($this);
+        $sFile=dirname($oReflection->getFileName()) . '/info.json';
+        $this->_aPlugininfos= (file_exists($sFile))
+            ? json_decode(file_get_contents($sFile), 1)
+            : array('error'=> 'unable to read info file ['.$sFile.'].')
+        ;
+        return $this->_aPlugininfos;
+    }
+
+    // ----------------------------------------------------------------------
+    // INTERFACE :: RENDERER
+    // ----------------------------------------------------------------------
+    public function renderFormdata4Project() {
+        return ''
+                . $this->_renderForm4Vars('global', false)
+                . $this->_renderForm4Vars('project', false)
+                // . $this->_renderFormProjectVars($this->_sNamePrefix4Project, false)
+                // . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
+                // . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
+        // .'<pre>DEBUG: $this->_aCfgProject ... plugin = '.print_r($this->_aCfgProject, 1).'</pre>'
+        ;
+    }
+    public function renderFormdata4Phase($sMyPhase){
+        return ''
+            . $this->_renderForm4Vars('global', $sMyPhase)
+            . $this->_renderForm4Vars('project', $sMyPhase)
+            . $this->_renderForm4Vars('phase', $sMyPhase)
+            // . $this->_renderForm($aFormdata, 'project')
+            // .$sReturn
+            // . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
+            // . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
+            // . '<pre>DEBUG: PHASE settings - $this->getConfig("'.$sMyPhase.'") = ' . print_r($this->getConfig($sMyPhase), 1) . '</pre>'
+            ;
+    }
+}
diff --git a/public_html/deployment/pages/act_setup.php b/public_html/deployment/pages/act_setup.php
index 701ee29ab20649dbc6207b100b296681d25ccf1b..8a762aa199d9d5b1334bf7992c96fcddb786543c 100644
--- a/public_html/deployment/pages/act_setup.php
+++ b/public_html/deployment/pages/act_setup.php
@@ -55,6 +55,35 @@ function maskEntries($aMask, $aConfig){
     return $aConfig;
 }
 
+/**
+ * recursive replace of values in a hash
+ * source: https://www.w3schools.in/php-script/recursive-array-replace-by-Key-or-Value/
+ * FIX: 3x "=" in if($Key === $Find) 
+ * 
+ * @param array   $Array    Array
+ * @param string  $Find     key to scan for
+ * @param strin   $Replace  new value
+ * @return array
+ */
+function ArrayReplace($Array, $Find, $Replace) {
+    if (is_array($Array)) {
+        foreach ($Array as $Key => $Val) {
+            if (is_array($Array[$Key])) {
+                $Array[$Key] = ArrayReplace($Array[$Key], $Find, $Replace);
+            } else {
+                if ($Key === $Find) {
+                    $Array[$Key] = $Replace;
+                }
+            }
+        }
+    }
+    return $Array;
+}
+
+// ---------------------------------------------------------------------
+// MAIN
+// ---------------------------------------------------------------------
+
 if ($aParams["prj"] == "all") {
     
     // ------------------------------------------------------------
@@ -62,7 +91,10 @@ if ($aParams["prj"] == "all") {
     // ------------------------------------------------------------
     if (!array_key_exists("par3", $aParams)) {
         $oPrj = new project();
-        $aTmp=maskEntries($aMask, $aConfig);
+        // $aTmp=maskEntries($aMask, $aConfig);
+        $aTmp=$aConfig;
+        $aTmp=ArrayReplace($aTmp, "password",   $sFakePassword);
+        $aTmp=ArrayReplace($aTmp, "PwLdapUser", $sFakePassword);
         $sOut.= '<pre>'.print_r($aTmp, 1).'</pre>';
         
         // print_r($aConfig);
@@ -304,8 +336,14 @@ if ($aParams["prj"] == "all") {
     // setup page of a an existing project
     // ------------------------------------------------------------
     $oPrj = new project($aParams["prj"]);
-    // $sOut.='<div style="float: right">aParams:<pre>'.print_r($aParams, true).'</pre></div>';
-
+    /*
+    $sOut.='<div style="float: right">'
+                // . 'aParams:<pre>'.print_r($aParams, true).'</pre>'
+                . 'configured rollout plugins:<pre>'.print_r($oPrj->getConfiguredPlugins('rollout'), true).'</pre>'
+                // . 'rollout plugin infos:<pre>'.print_r($oPrj->oRolloutPlugin->getPluginInfos(), true).'</pre>'
+                . 'prj config of rollout plugin:<pre>'.print_r($oPrj->oRolloutPlugin->getConfig(), true).'</pre>'
+            . '</div>';
+    */
     if (array_key_exists("setupaction", $aParams) && $aParams["setupaction"] == "save") {
         if ($oPrj->saveConfig()) {
             $sOut.=$oHtml->getBox("success", t("page-setup-info-settings-were-saved"));
diff --git a/public_html/deployment/plugins/rollout/default/info.json b/public_html/deployment/plugins/rollout/default/info.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6f00239d4aeca148685ba6910b998995915184e
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/default/info.json
@@ -0,0 +1,16 @@
+{
+    "name": "default",
+    "description": "Default rollout plugin - doing no action",
+    "author": "Axel Hahn; University odf Bern; Institute for Medical education",
+    
+    "version": "1.0",
+    "url": "[included]",
+    "license": "GNU GPL 3.0",
+    
+    "vars": {
+        "global": {},
+        "project": {},
+        "phase": {}
+    }
+}
+
diff --git a/public_html/deployment/plugins/rollout/default/lang_de.json b/public_html/deployment/plugins/rollout/default/lang_de.json
new file mode 100644
index 0000000000000000000000000000000000000000..23e15d2fc58a258794cc17eb38c4e66b10a8c382
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/default/lang_de.json
@@ -0,0 +1,6 @@
+{
+    "plugin_name": "Default: keine Aktion",
+    "description": "Es wird keine Aktion zum Rollout gestartet.",
+    
+    "no-cfg": "Es wird keine Einstellung ben&ouml;tigt."
+}
\ No newline at end of file
diff --git a/public_html/deployment/plugins/rollout/default/lang_en.json b/public_html/deployment/plugins/rollout/default/lang_en.json
new file mode 100644
index 0000000000000000000000000000000000000000..cfad0986ad295413adab4094cdff633f3a68a9ec
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/default/lang_en.json
@@ -0,0 +1,6 @@
+{
+    "plugin_name": "Default: no action",
+    "description": "Doing nothing for rollout of a package.",
+    
+    "no-cfg": "No further settings needed,"
+}
\ No newline at end of file
diff --git a/public_html/deployment/plugins/rollout/default/rollout_default.php b/public_html/deployment/plugins/rollout/default/rollout_default.php
new file mode 100644
index 0000000000000000000000000000000000000000..b0e1df57041a0e303b0cd6b448cb7fa59353ba49
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/default/rollout_default.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * 
+ * Rollout plugin - default
+ * 
+ * no action
+ *
+ * @author axel
+ */
+class rollout_default extends rollout_base {
+
+    /**
+     * check requirements if the plugin could work
+     */
+    public function checkRequirements(){
+        // no specific checks needed ... always true
+        return true;
+    }
+
+    /**
+     * check access to a deploy target
+     */
+    public function checkConnectionToTarget(){
+        // do nothing ... always true
+        return true;
+    }
+
+    public function renderFormdata4Project() {
+        return $this->_t('no-cfg');
+    }
+
+    public function renderFormdata4Phase() {
+        return $this->_t('no-cfg');
+    }    
+}
diff --git a/public_html/deployment/plugins/rollout/ssh/info.json b/public_html/deployment/plugins/rollout/ssh/info.json
new file mode 100644
index 0000000000000000000000000000000000000000..e148fb8c8dfaf33b63cd528bf76a55ae3a412c63
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/ssh/info.json
@@ -0,0 +1,47 @@
+{
+    "name": "SSH",
+    "description": "Run a SSH command on remote targets.",
+    "author": "Axel Hahn; University odf Bern; Institute for Medical education",
+    
+    "version": "1.0",
+    "url": "[included]",
+    "license": "GNU GPL 3.0",
+    
+    "vars": {
+        "global": {
+            "user": {
+                "type": "text",
+                "default": "example: remoteuser"
+            },
+            "command": {
+                "type": "text",
+                "default": "example: /opt/somewhere/install.sh"
+            },
+            "privatekey": {
+                "type": "text",
+                "default": ""
+            },
+            "addkeycommand": {
+                "type": "text",
+                "default": ""
+            },
+            "testcommand": {
+                "type": "text",
+                "default": "example: sudo echo OK"
+            }
+        },
+        "project": {
+            "projectfile": {
+                "type": "text",
+                "default": ""
+            }     
+        },
+        "phase": {
+            "hosts": {
+                "type": "text",
+                "default": "example: myserver.example.com"
+            }     
+        }
+    }
+}
+
diff --git a/public_html/deployment/plugins/rollout/ssh/lang_de.json b/public_html/deployment/plugins/rollout/ssh/lang_de.json
new file mode 100644
index 0000000000000000000000000000000000000000..8c73bfed5bb0077fc11ef21dbde4028e38f819aa
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/ssh/lang_de.json
@@ -0,0 +1,22 @@
+{
+    "plugin_name": "SSH",
+    "description": "Ein SSH Kommando auf einem Zielsystem starten.",
+    
+
+    "section-required": "Erforderliche Angaben",
+    "section-optional": "optionale Angaben",
+    "section-commands": "Kommandos",
+
+    "user-label": "SSH User",
+    "user-hint": "Remote Account zu dem sich auf dem Zielsystem verbunden wird.",
+    "command-label": "Kommando",
+    "command-hint": "Auf dem Zielsystem zu startendes Kommando",
+    "privatekey-label": "Dateiname des Privatekeys",
+    "privatekey-hint": "Kompletter Pfad zur Datei des Privatekeys, wenn vom Default (z.B. ~/.ssh/id_rsa) abweichend.",
+    "addkeycommand-label": "known_hosts Kommando",
+    "addkeycommand-hint": "Kommando zum Einsetzen oder Erneuern eines SSH Hostkeys in der known_hosts.",
+    "testcommand-label": "Test-Remote-Kommando",
+    "testcommand-hint": "Auf dem Zielsystem zu startendes Testkommando, ob ein Rollout bereit w&auml;re.",
+
+    "endoffile": ""
+}
\ No newline at end of file
diff --git a/public_html/deployment/plugins/rollout/ssh/lang_en.json b/public_html/deployment/plugins/rollout/ssh/lang_en.json
new file mode 100644
index 0000000000000000000000000000000000000000..1a21a39a114fb69e6574f5638f9131d4281b0564
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/ssh/lang_en.json
@@ -0,0 +1,4 @@
+{
+    "plugin_name": "SSH",
+    "description": "Run a command on a remote system via SSH ."
+}
\ No newline at end of file
diff --git a/public_html/deployment/plugins/rollout/ssh/rollout_ssh.php b/public_html/deployment/plugins/rollout/ssh/rollout_ssh.php
new file mode 100644
index 0000000000000000000000000000000000000000..df5fa0c90b7af41fc58064450bc10281cdb7be85
--- /dev/null
+++ b/public_html/deployment/plugins/rollout/ssh/rollout_ssh.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * 
+ * Rollout plugin - default
+ * 
+ * no action
+ *
+ * @author axel
+ */
+class rollout_ssh extends rollout_base {
+
+    /**
+     * check requirements if the plugin could work
+     */
+    public function checkRequirements() {
+        // no specific checks needed ... always true
+        return true;
+    }
+
+    /**
+     * check access to a deploy target
+     */
+    public function checkConnectionToTarget() {
+        // do nothing ... always true
+        return true;
+    }
+
+    /**
+     * form fields for project settings
+     * @return type
+     */
+    public function __DISABLED__renderFormdata4Project() {
+        /*
+          $sReturn='';
+          $i=0;
+
+          $sNamePrefix='plugins[rollout]['.$this->getId().']';
+          $aValues=$this->getConfig();
+
+          $aFormdata=array(
+          array('type' => 'markup','value' => '<br>'.$this->_t('section-required').':'),
+          array(
+          'type' => 'text',
+          'name' => $sNamePrefix.'[user]',
+          'label' => $this->_t('user'),
+          'value' => $aValues['user'],
+          // 'required' => 'required',
+          'validate' => 'isastring',
+          'size' => 25,
+          'placeholder' => $this->_aCfgGlobal['user'],
+          ),
+          array(
+          'type' => 'text',
+          'name' => $sNamePrefix.'[command]',
+          'label' => $this->_t('command'),
+          'value' => $aValues['command'],
+          // 'required' => 'required',
+          'validate' => 'isastring',
+          // 'size' => 100,
+          'placeholder' => $this->_aCfgGlobal['command'],
+          ),
+          array('type' => 'markup','value' => '<br><br>'.$this->_t('section-optional').':'),
+          array(
+          'type' => 'text',
+          'name' => $sNamePrefix.'[privatekey]',
+          'label' => $this->_t('privatekey'),
+          'value' => $aValues['privatekey'],
+          // 'required' => 'required',
+          'validate' => 'isastring',
+          // 'size' => 100,
+          'placeholder' => $this->_aCfgGlobal['privatekey'],
+          ),
+          array('type' => 'markup','value' => '<br><br>'.$this->_t('section-commands').':'),
+          array(
+          'type' => 'text',
+          'name' => $sNamePrefix.'[addkeycommand]',
+          'label' => $this->_t('addkeycommand'),
+          'value' => $aValues['addkeycommand'],
+          // 'required' => 'required',
+          'validate' => 'isastring',
+          // 'size' => 100,
+          'placeholder' => $this->_aCfgGlobal['addkeycommand'],
+          ),
+          array(
+          'type' => 'text',
+          'name' => $sNamePrefix.'[testcommand]',
+          'label' => $this->_t('testcommand'),
+          'value' => $aValues['testcommand'],
+          // 'required' => 'required',
+          'validate' => 'isastring',
+          // 'size' => 100,
+          'placeholder' => $this->_aCfgGlobal['testcommand'],
+          ),
+          );
+         * 
+         */
+
+        return 'WIP: project based setup for plugin [' . $this->getId() . ']<br>'
+                . $this->_renderForm4Vars('global', false)
+                // . $this->_renderForm($aFormdata, 'project')
+                // .$sReturn
+                . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
+                . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
+        // .'<pre>DEBUG: $this->_aCfgProject ... plugin = '.print_r($this->_aCfgProject, 1).'</pre>'
+        ;
+    }
+
+    /**
+     * form fields for project settings
+     * @return type
+     */
+    public function __DISABLED__renderFormdata4Phase($sMyPhase) {
+        return 'WIP: pase based setup for plugin [' . $this->getId() . '] in phase '.$sMyPhase.'<br>'
+            . $this->_renderForm4Vars('global', $sMyPhase)
+            // . $this->_renderForm($aFormdata, 'project')
+            // .$sReturn
+            . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
+            . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
+            . '<pre>DEBUG: PHASE settings - $this->getConfig("'.$sMyPhase.'") = ' . print_r($this->getConfig($sMyPhase), 1) . '</pre>'
+            ;
+    }
+
+}