diff --git a/plugins/dynamix.docker.manager/DockerContainers.page b/plugins/dynamix.docker.manager/DockerContainers.page index 604f7970c..9d8712a2e 100644 --- a/plugins/dynamix.docker.manager/DockerContainers.page +++ b/plugins/dynamix.docker.manager/DockerContainers.page @@ -84,12 +84,12 @@ img.stopped{opacity:0.3;} $updateStatus = ($updateStatus == "true" or $updateStatus == "undef" ) ? 'true' : 'false'; $running = ($ct['Running']) ? 'true' : 'false'; $webGuiUrl = $info[$name]['url']; - $contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info[$name]['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl)); + $contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s', '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info[$name]['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl), $ct["Id"]); $shape = ($ct["Running"]) ? "play" : "square"; $status = ($ct["Running"]) ? "started" : "stopped"; $Icon = $info[$name]['icon']; - if ( $Icon == "#" ){ + if ( ! $Icon ){ $Icon = "/plugins/dynamix.docker.manager/images/question.png"; } @@ -144,6 +144,7 @@ img.stopped{opacity:0.3;} echo htmlspecialchars($ct['Image']); } ?> + if($ct['BaseImage']) echo "
++ +You can specify a folder to automatically generate and store subfolders containing configuration files for each Docker app (via the /config mapped volume).
+Only used when adding new Docker apps. Editing existing Docker apps will not be affected by this setting.
+
++You can expose all of your user shares (/mnt/user) to a folder named /unraid within Docker containers.
+Only used when adding new Docker apps. Editing existing Docker apps will not be affected by this setting.
+
".shell_exec("btrfs filesystem show /var/lib/docker").""?>".print_r($m,true)."";}; + function prepareDir($dir){ if (strlen($dir)){ if ( ! is_dir($dir) && ! is_file($dir)){ @@ -30,7 +39,6 @@ function prepareDir($dir){ function ContainerExist($container){ global $DockerClient; - $all_containers = $DockerClient->getDockerContainers(); if ( ! $all_containers) { return FALSE; } foreach ($all_containers as $ct) { @@ -42,60 +50,67 @@ function ContainerExist($container){ return False; } +function ImageExist($image){ + global $DockerClient; + $all_images = $DockerClient->getDockerImages(); + if ( ! $all_images) { return FALSE; } + foreach ($all_images as $img) { + if ( ! is_bool(strpos($img['Tags'][0], $image)) ){ + return True; + break; + } + } + return False; +} + function trimLine($text){ return preg_replace("/([\n^])[\s]+/", '$1', $text); } +$pullecho = function($line) { + global $alltotals; + $cnt = json_decode( $line, TRUE ); + $id = ( isset( $cnt['id'] )) ? $cnt['id'] : ""; + $status = ( isset( $cnt['status'] )) ? $cnt['status'] : ""; + if (strlen(trim($status)) && strlen(trim($id))) { + if ( isset($cnt['progressDetail']['total']) && $cnt['progressDetail']['total'] > 0 ) { + $alltotals[$cnt['id']] = $cnt['progressDetail']['total']; + } + echo "\n"; + @flush(); + } + if ($status == "Downloading") { + $total = $cnt['progressDetail']['total']; + $current = $cnt['progressDetail']['current']; + $alltotals[$cnt['id']] = $cnt['progressDetail']['current']; + if ($total > 0) { + $percentage = round(($current/$total) * 100); + echo "\n"; + } else { + // Docker must not know the total download size (http-chunked or something?) + // just show the current download progress without the percentage + echo "\n"; + } + @flush(); + } +}; + + function pullImage($image) { + global $DockerClient, $pullecho, $alltotals; + $alltotals = array(); if (! preg_match("/:[\w]*$/i", $image)) $image .= ":latest"; readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm"); - echo ''; - echo ""; + echo ""; @flush(); - $fp = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr); - if ($fp === false) { - echo "Couldn't create socket: [$errno] $errstr"; - return NULL; - } - $out="POST /images/create?fromImage=$image HTTP/1.1\r\nConnection: Close\r\n\r\n"; - fwrite($fp, $out); - $cid = ""; - $cstatus=""; - $alltotals = []; - while (!feof($fp)) { - $cnt = json_decode( fgets($fp, 5000), TRUE ); - $id = ( isset( $cnt['id'] )) ? $cnt['id'] : ""; - if ($id != $cid && strlen($id)) { - $cid = $id; - $cstatus = ""; - echo ""; - @flush(); - } - $status = ( isset( $cnt['status'] )) ? $cnt['status'] : ""; - if ($status != $cstatus && strlen($status)) { - if ( isset($cnt['progressDetail']['total']) && $cnt['progressDetail']['total'] > 0 ) { - $alltotals[$cnt['id']] = $cnt['progressDetail']['total']; - } - $cstatus = $status; - echo ""; - @flush(); - } - if ($status == "Downloading") { - $total = $cnt['progressDetail']['total']; - $current = $cnt['progressDetail']['current']; - $alltotals[$cnt['id']] = $cnt['progressDetail']['current']; - if ($total > 0) { - $percentage = round(($current/$total) * 100); - echo "\n"; - } else { - // Docker must not know the total download size (http-chunked or something?) - // just show the current download progress without the percentage - echo "\n"; - } - @flush(); - } - } + $DockerClient->pullImage($image, $pullecho); echo "\n"; } @@ -109,175 +124,291 @@ function sizeToHuman($size) { return ceil($size) . " " . $units[$unitsIndex]; } -function xmlToCommand($xmlFile){ - global $var; - $doc = new DOMDocument(); - $doc->loadXML($xmlFile); - - $Name = $doc->getElementsByTagName( "Name" )->item(0)->nodeValue; - $cmdName = (strlen($Name)) ? '--name="' . $Name . '"' : ""; - $Privileged = $doc->getElementsByTagName( "Privileged" )->item(0)->nodeValue; - $cmdPrivileged = (strtolower($Privileged) == 'true') ? '--privileged="true"' : ""; - $Repository = $doc->getElementsByTagName( "Repository" )->item(0)->nodeValue; - $Mode = $doc->getElementsByTagName( "Mode" )->item(0)->nodeValue; - $cmdMode = '--net="'.strtolower($Mode).'"'; - $BindTime = $doc->getElementsByTagName( "BindTime" )->item(0)->nodeValue; - // $cmdBindTime = (strtolower($BindTime) == "true") ? '"/etc/localtime":"/etc/localtime":ro' : ''; - $cmdBindTime = (strtolower($BindTime) == "true") ? 'TZ="' . $var['timeZone'] . '"' : ''; - - $Ports = array(''); - foreach($doc->getElementsByTagName('Port') as $port){ - $ContainerPort = $port->getElementsByTagName( "ContainerPort" )->item(0)->nodeValue; - if (! strlen($ContainerPort)){ continue; } - $HostPort = $port->getElementsByTagName( "HostPort" )->item(0)->nodeValue; - $Protocol = $port->getElementsByTagName( "Protocol" )->item(0)->nodeValue; - $Ports[] = sprintf("%s:%s/%s", $HostPort, $ContainerPort, $Protocol); - } - - $Volumes = array(''); - foreach($doc->getElementsByTagName('Volume') as $volume){ - $ContainerDir = $volume->getElementsByTagName( "ContainerDir" )->item(0)->nodeValue; - if (! strlen($ContainerDir)){ continue; } - $HostDir = $volume->getElementsByTagName( "HostDir" )->item(0)->nodeValue; - $DirMode = $volume->getElementsByTagName( "Mode" )->item(0)->nodeValue; - $Volumes[] = sprintf( '"%s":"%s":%s', $HostDir, $ContainerDir, $DirMode); - } - - // if (strlen($cmdBindTime)) { - // $Volumes[] = $cmdBindTime; - // } - - $Variables = array(''); - foreach($doc->getElementsByTagName('Variable') as $variable){ - $VariableName = $variable->getElementsByTagName( "Name" )->item(0)->nodeValue; - if (! strlen($VariableName)){ continue; } - $VariableValue = $variable->getElementsByTagName( "Value" )->item(0)->nodeValue; - $Variables[] = sprintf('%s="%s"', $VariableName, $VariableValue); - } - - if (strlen($cmdBindTime)) { - $Variables[] = $cmdBindTime; - } - - $templateExtraParams = ''; - if ( $doc->getElementsByTagName( "ExtraParams" )->length > 0 ) { - $templateExtraParams = $doc->getElementsByTagName( "ExtraParams" )->item(0)->nodeValue; - } - - $cmd = sprintf('/plugins/dynamix.docker.manager/scripts/docker run -d %s %s %s %s %s %s %s %s', $cmdName, $cmdMode, $cmdPrivileged, implode(' -e ', $Variables), - implode(' -p ', $Ports), implode(' -v ', $Volumes), $templateExtraParams, $Repository); - $cmd = preg_replace('/\s+/', ' ', $cmd); - - return array($cmd, $Name, $Repository); +function xml_encode($string) { + return htmlspecialchars($string, ENT_XML1, 'UTF-8'); } - -function addElement($doc, $el, $elName, $elVal){ - $node = $el->appendChild($doc->createElement($elName)); - $node->appendChild($doc->createTextNode($elVal)); - return $node; +function xml_decode($string) { + return strval(html_entity_decode($string, ENT_XML1, 'UTF-8')); } function postToXML($post, $setOwnership = FALSE){ - global $DockerUpdate; - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->preserveWhiteSpace = false; - $doc->formatOutput = true; - $root = $doc->createElement('Container'); - $root = $doc->appendChild($root); + $dom = new domDocument; + $dom->appendChild($dom->createElement( "Container" )); + $xml = simplexml_import_dom( $dom ); + $xml["version"] = 2; + $xml->Name = xml_encode($post['contName']); + $xml->Repository = xml_encode($post['contRepository']); + $xml->Registry = xml_encode($post['contRegistry']); + $xml->Network = xml_encode($post['contNetwork']); + $xml->Privileged = (strtolower($post["contPrivileged"]) == 'on') ? 'true' : 'false'; + $xml->Support = xml_encode($post['contSupport']); + $xml->Overview = xml_encode($post['contOverview']); + $xml->Category = xml_encode($post['contCategory']); + $xml->WebUI = xml_encode($post['contWebUI']); + $xml->Icon = xml_encode($post['contIcon']); + $xml->ExtraParams = xml_encode($post['contExtraParams']); - $docName = $root->appendChild($doc->createElement('Name')); - if ( isset( $post[ 'Description' ] )) addElement($doc, $root, 'Description', $post[ 'Description' ]); - if ( isset( $post[ 'Registry' ] )) addElement($doc, $root, 'Registry', $post[ 'Registry' ]); - $docRepository = $root->appendChild($doc->createElement('Repository')); - $BindTime = $root->appendChild($doc->createElement('BindTime')); - $Privileged = $root->appendChild($doc->createElement('Privileged')); - $Environment = $root->appendChild($doc->createElement('Environment')); - $docNetworking = $root->appendChild($doc->createElement('Networking')); - $Data = $root->appendChild($doc->createElement('Data')); - $Version = $root->appendChild($doc->createElement('Version')); - $Mode = $docNetworking->appendChild($doc->createElement('Mode')); - $Publish = $docNetworking->appendChild($doc->createElement('Publish')); - $Name = preg_replace('/\s+/', '', $post["containerName"]); + # V1 compatibility + $xml->Description = xml_encode($post['contOverview']); + $xml->Networking->Mode = xml_encode($post['contNetwork']); + $xml->Networking->addChild("Publish"); + $xml->addChild("Data"); + $xml->addChild("Environment"); - // Editor Values - if ( isset( $post[ 'WebUI' ] )) addElement($doc, $root, 'WebUI', $post[ 'WebUI' ]); - if ( isset( $post[ 'Banner' ] )) addElement($doc, $root, 'Banner', $post[ 'Banner' ]); - if ( isset( $post[ 'Icon' ] )) addElement($doc, $root, 'Icon', $post[ 'Icon' ]); - if ( isset( $post[ 'ExtraParams' ] )) addElement($doc, $root, 'ExtraParams', $post[ 'ExtraParams' ]); - - $docName->appendChild($doc->createTextNode($Name)); - $docRepository->appendChild($doc->createTextNode($post["Repository"])); - $BindTime->appendChild($doc->createTextNode((strtolower($post["BindTime"]) == 'on') ? 'true' : 'false')); - $Privileged->appendChild($doc->createTextNode((strtolower($post["Privileged"]) == 'on') ? 'true' : 'false')); - $Mode->appendChild($doc->createTextNode(strtolower($post["NetworkType"]))); - - for ($i = 0; $i < count($post["hostPort"]); $i++){ - if (! strlen($post["containerPort"][$i])) { continue; } - $protocol = $post["portProtocol"][$i]; - $Port = $Publish->appendChild($doc->createElement('Port')); - $HostPort = $Port->appendChild($doc->createElement('HostPort')); - $ContainerPort = $Port->appendChild($doc->createElement('ContainerPort')); - $Protocol = $Port->appendChild($doc->createElement('Protocol')); - $HostPort->appendChild($doc->createTextNode(trim($post["hostPort"][$i]))); - $ContainerPort->appendChild($doc->createTextNode($post["containerPort"][$i])); - $Protocol->appendChild($doc->createTextNode($protocol)); - } - - for ($i = 0; $i < count($post["VariableName"]); $i++){ - if (! strlen($post["VariableName"][$i])) { continue; } - $Variable = $Environment->appendChild($doc->createElement('Variable')); - $VariableName = $Variable->appendChild($doc->createElement('Name')); - $VariableValue = $Variable->appendChild($doc->createElement('Value')); - $VariableName->appendChild($doc->createTextNode(trim($post["VariableName"][$i]))); - $VariableValue->appendChild($doc->createTextNode(trim($post["VariableValue"][$i]))); - } - - for ($i = 0; $i < count($post["hostPath"]); $i++){ - if (! strlen($post["hostPath"][$i])) { continue; } - if (! strlen($post["containerPath"][$i])) { continue; } - $tmpMode = $post["hostWritable"][$i]; - if ($setOwnership){ - prepareDir($post["hostPath"][$i]); + for ($i = 0; $i < count($post["confName"]); $i++) { + $Type = $post['confType'][$i]; + $config = $xml->addChild('Config'); + $config->{0} = xml_encode($post['confValue'][$i]); + $config['Name'] = xml_encode($post['confName'][$i]); + $config['Target'] = xml_encode($post['confTarget'][$i]); + $config['Default'] = xml_encode($post['confDefault'][$i]); + $config['Mode'] = xml_encode($post['confMode'][$i]); + $config['Description'] = xml_encode($post['confDescription'][$i]); + $config['Type'] = xml_encode($post['confType'][$i]); + $config['Display'] = xml_encode($post['confDisplay'][$i]); + $config['Required'] = xml_encode($post['confRequired'][$i]); + $config['Mask'] = xml_encode($post['confMask'][$i]); + # V1 compatibility + if ($Type == "Port"){ + $port = $xml->Networking->Publish->addChild("Port"); + $port->HostPort = $post['confValue'][$i]; + $port->ContainerPort = $post['confTarget'][$i]; + $port->Protocol = $post['confMode'][$i]; + } else if ($Type == "Path"){ + $path = $xml->Data->addChild("Volume"); + $path->HostDir = $post['confValue'][$i]; + $path->ContainerDir = $post['confTarget'][$i]; + $path->Mode = $post['confMode'][$i]; + } else if ($Type == "Variable"){ + $variable = $xml->Environment->addChild("Variable"); + $variable->Value = $post['confValue'][$i]; + $variable->Name = $post['confTarget'][$i]; + $variable->Mode = $post['confMode'][$i]; } - $Volume = $Data->appendChild($doc->createElement('Volume')); - $HostDir = $Volume->appendChild($doc->createElement('HostDir')); - $ContainerDir = $Volume->appendChild($doc->createElement('ContainerDir')); - $DirMode = $Volume->appendChild($doc->createElement('Mode')); - $HostDir->appendChild($doc->createTextNode($post["hostPath"][$i])); - $ContainerDir->appendChild($doc->createTextNode($post["containerPath"][$i])); - $DirMode->appendChild($doc->createTextNode($tmpMode)); } - - $currentVersion = $DockerUpdate->getRemoteVersion($post["Registry"], $post["Repository"]); - $Version->appendChild($doc->createTextNode($currentVersion)); - - return $doc->saveXML(); + $dom = new DOMDocument('1.0'); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + $dom->loadXML($xml->asXML()); + return $dom->saveXML(); } -if ($_POST){ +function xmlToVar($xml) { + global $var; + $out = array(); + $xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml); + + $out['Name'] = xml_decode($xml->Name); + $out['Repository'] = xml_decode($xml->Repository); + $out['Registry'] = xml_decode($xml->Registry); + $out['Network'] = (isset($xml->Network)) ? xml_decode($xml->Network) : xml_decode($xml->Network['Default']); + $out['Privileged'] = xml_decode($xml->Privileged); + $out['Support'] = xml_decode($xml->Support); + $out['Overview'] = stripslashes(xml_decode($xml->Overview)); + $out['Category'] = xml_decode($xml->Category); + $out['WebUI'] = xml_decode($xml->WebUI); + $out['Icon'] = xml_decode($xml->Icon); + $out['ExtraParams'] = xml_decode($xml->ExtraParams); + + $out['Config'] = array(); + if (isset($xml->Config)) { + foreach ($xml->Config as $config) { + $c = array(); + $c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']) ; + foreach ($config->attributes() as $key => $value) $c[$key] = xml_decode(xml_decode($value)); + $out['Config'][] = $c; + } + } + + # V1 compatibility + if ($xml["version"] != "2"){ + if (isset($xml->Networking->Mode)){ + $out['Network'] = xml_decode($xml->Networking->Mode); + } + if (isset($xml->Description)) { + $out['Overview'] = stripslashes(xml_decode($xml->Description)); + } + + if (isset($xml->Networking->Publish->Port)) { + $portNum = 0; + foreach ($xml->Networking->Publish->Port as $port) { + $portNum += 1; + $out['Config'][] = array('Name' => "Port ${portNum}", + 'Target' => xml_decode($port->ContainerPort), + 'Default' => xml_decode($port->HostPort), + 'Value' => xml_decode($port->HostPort), + 'Mode' => xml_decode($port->Protocol), + 'Description' => '', + 'Type' => 'Port', + 'Display' => 'always', + 'Required' => 'true', + 'Mask' => 'false', + ); + } + } + + if (isset($xml->Data->Volume)) { + $volNum = 0; + foreach ($xml->Data->Volume as $vol) { + $volNum += 1; + $out['Config'][] = array('Name' => "Path ${volNum}", + 'Target' => xml_decode($vol->ContainerDir), + 'Default' => xml_decode($vol->HostDir), + 'Value' => xml_decode($vol->HostDir), + 'Mode' => xml_decode($vol->Mode), + 'Description' => '', + 'Type' => 'Path', + 'Display' => 'always', + 'Required' => 'true', + 'Mask' => 'false', + ); + } + } + + if (isset($xml->Environment->Variable)) { + $varNum = 0; + foreach ($xml->Environment->Variable as $var) { + $varNum += 1; + $out['Config'][] = array('Name' => "Variable ${varNum}", + 'Target' => xml_decode($var->Name), + 'Default' => xml_decode($var->Value), + 'Value' => xml_decode($var->Value), + 'Mode' => '', + 'Description' => '', + 'Type' => 'Variable', + 'Display' => 'always', + 'Required' => 'false', + 'Mask' => 'false', + ); + } + } + } + + return $out; +} + +function xmlToCommand($xml) { + global $var; + $xml = xmlToVar($xml); + $cmdName = (strlen($xml['Name'])) ? '--name="' . $xml['Name'] . '"' : ""; + $cmdPrivileged = (strtolower($xml['Privileged']) == 'true') ? '--privileged="true"' : ""; + $cmdNetwork = '--net="'.strtolower($xml['Network']).'"'; + $Volumes = array(''); + $Ports = array(''); + $Variables = array(''); + $Devices = array(''); + # Bind Time + $Variables[] = 'TZ="' . $var['timeZone'] . '"'; + # Add HOST_OS variable + $Variables[] = 'HOST_OS="unRAID"'; + + foreach ($xml['Config'] as $key => $config) { + $confType = strtolower(strval($config['Type'])); + $hostConfig = strlen($config['Value']) ? $config['Value'] : $config['Default']; + $containerConfig = strval($config['Target']); + $Mode = strval($config['Mode']); + if (! strlen($containerConfig)) continue; + if ($confType == "path") { + $Volumes[] = sprintf( '"%s":"%s":%s', $hostConfig, $containerConfig, $Mode); + } elseif ($confType == 'port') { + $Ports[] = sprintf("%s:%s/%s", $hostConfig, $containerConfig, $Mode); + } elseif ($confType == "variable") { + $Variables[] = sprintf('%s="%s"', $containerConfig, $hostConfig); + } elseif ($confType == "device") { + $Devices[] = '"'.$containerConfig.'"'; + } + } + $cmd = sprintf('/plugins/dynamix.docker.manager/scripts/docker run -d %s %s %s %s %s %s %s %s %s', + $cmdName, + $cmdNetwork, + $cmdPrivileged, + implode(' -e ', $Variables), + implode(' -p ', $Ports), + implode(' -v ', $Volumes), + implode(' --device=', $Devices), + $xml['ExtraParams'], + $xml['Repository']); + + $cmd = preg_replace('/\s+/', ' ', $cmd); + return array($cmd, $xml['Name'], $xml['Repository']); +} + +function getXmlVal($xml, $element, $attr=null, $pos=0) { + $xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml); + $element = $xml->xpath("//$element")[$pos]; + return isset($element) ? (isset($element[$attr]) ? strval($element[$attr]) : strval($element)) : ""; +} + +function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) { + global $echo; + $xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml); + $element = $xml->xpath("//$el")[$pos]; + if (! isset($element)) $element = $xml->addChild($el); + if ($attr) { + $element[$attr] = $value; + } else { + $element->{0} = $value; + } + $dom = new DOMDocument('1.0'); + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + $dom->loadXML($xml->asXML()); + $xml = $dom->saveXML(); +} + + +# ██████╗ ██████╗ ██████╗ ███████╗ +# ██╔════╝██╔═══██╗██╔══██╗██╔════╝ +# ██║ ██║ ██║██║ ██║█████╗ +# ██║ ██║ ██║██║ ██║██╔══╝ +# ╚██████╗╚██████╔╝██████╔╝███████╗ +# ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ + +## +## CREATE CONTAINER +## + +if (isset($_POST['contName'])) { $postXML = postToXML($_POST, TRUE); // Get the command line list($cmd, $Name, $Repository) = xmlToCommand($postXML); + // Run dry + if ($_POST['dryRun'] == "true") { + echo "
".htmlentities($postXML).""; + echo "
".htmlentities($cmd).""; + echo '