diff --git a/plugins/dynamix.docker.manager/include/CreateDocker.php b/plugins/dynamix.docker.manager/include/CreateDocker.php
index bb9ae8157..5ca38354d 100644
--- a/plugins/dynamix.docker.manager/include/CreateDocker.php
+++ b/plugins/dynamix.docker.manager/include/CreateDocker.php
@@ -14,6 +14,7 @@
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
+require_once "$docroot/plugins/dynamix.docker.manager/include/Helpers.php";
$var = parse_ini_file('state/var.ini');
ignore_user_abort(true);
@@ -34,486 +35,7 @@ $subnet = ['bridge'=>'', 'host'=>'', 'none'=>''];
foreach ($custom as $network) $subnet[$network] = substr(DockerUtil::docker("network inspect --format='{{range .IPAM.Config}}{{.Subnet}}, {{end}}' $network"),0,-1);
-function stopContainer($name) {
- global $DockerClient;
- $waitID = mt_rand();
- echo "
";
- echo "\n";
- @flush();
- $retval = $DockerClient->stopContainer($name);
- $out = ($retval === true) ? "Successfully stopped container '$name'" : "Error: ".$retval;
- echo "\n";
- @flush();
-}
-function removeContainer($name, $cache=false) {
- global $DockerClient;
- $waitID = mt_rand();
- echo "";
- echo "\n";
- @flush();
- $retval = $DockerClient->removeContainer($name, false, $cache);
- $out = ($retval === true) ? "Successfully removed container '$name'" : "Error: ".$retval;
- echo "\n";
- @flush();
-}
-
-function removeImage($image) {
- global $DockerClient;
- $waitID = mt_rand();
- echo "";
- echo "\n";
- @flush();
- $retval = $DockerClient->removeImage($image);
- $out = ($retval === true) ? "Successfully removed image '$image'" : "Error: ".$retval;
- echo "\n";
- @flush();
-}
-
-function pullImage($name, $image) {
- global $DockerClient, $DockerTemplates, $DockerUpdate;
- $waitID = mt_rand();
- if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
- echo "";
- echo "\n";
- @flush();
- $alltotals = [];
- $laststatus = [];
- $strError = '';
- $DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) {
- $cnt = json_decode($line, true);
- $id = (isset($cnt['id'])) ? trim($cnt['id']) : '';
- $status = (isset($cnt['status'])) ? trim($cnt['status']) : '';
- if (isset($cnt['error'])) {
- $strError = $cnt['error'];
- }
- if ($waitID !== false) {
- echo "\n";
- @flush();
- $waitID = false;
- }
- if (empty($status)) return;
- if (!empty($id)) {
- if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
- $alltotals[$id] = $cnt['progressDetail']['total'];
- }
- if (empty($laststatus[$id])) {
- $laststatus[$id] = '';
- }
- switch ($status) {
- case 'Waiting':
- // Omit
- break;
- case 'Downloading':
- if ($laststatus[$id] != $status) {
- echo "\n";
- }
- $total = $cnt['progressDetail']['total'];
- $current = $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
- $alltotals[$id] = $current;
- echo "\n";
- }
- break;
- default:
- if ($laststatus[$id] == "Downloading") {
- echo "\n";
- }
- if ($laststatus[$id] != $status) {
- echo "\n";
- }
- break;
- }
- $laststatus[$id] = $status;
- } else {
- if (strpos($status, 'Status: ') === 0) {
- echo "\n";
- }
- if (strpos($status, 'Digest: ') === 0) {
- $DockerUpdate->setUpdateStatus($image, substr($status, 8));
- }
- }
- @flush();
- });
- echo "\n";
- @flush();
- if (!empty($strError)) {
- echo "\n";
- @flush();
- return false;
- }
- return true;
-}
-
-function xml_encode($string) {
- return htmlspecialchars($string, ENT_XML1, 'UTF-8');
-}
-
-function xml_decode($string) {
- return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
-}
-
-function postToXML($post, $setOwnership=false) {
- $dom = new domDocument;
- $dom->appendChild($dom->createElement("Container"));
- $xml = simplexml_import_dom($dom);
- $xml['version'] = 2;
- $xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
- $xml->Repository = xml_encode(trim($post['contRepository']));
- $xml->Registry = xml_encode(trim($post['contRegistry']));
- $xml->Network = xml_encode($post['contNetwork']);
- $xml->MyIP = xml_encode($post['contMyIP']);
- $xml->Shell = xml_encode($post['contShell']);
- $xml->Privileged = strtolower($post['contPrivileged'])=='on' ? 'true' : 'false';
- $xml->Support = xml_encode($post['contSupport']);
- $xml->Project = xml_encode($post['contProject']);
- $xml->Overview = xml_encode($post['contOverview']);
- $xml->Category = xml_encode($post['contCategory']);
- $xml->WebUI = xml_encode(trim($post['contWebUI']));
- $xml->TemplateURL = xml_encode($post['contTemplateURL']);
- $xml->Icon = xml_encode(trim($post['contIcon']));
- $xml->ExtraParams = xml_encode($post['contExtraParams']);
- $xml->PostArgs = xml_encode($post['contPostArgs']);
- $xml->DateInstalled = xml_encode(time());
- $xml->DonateText = xml_encode($post['contDonateText']);
- $xml->DonateLink = xml_encode($post['contDonateLink']);
-
- // 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");
- $xml->addChild("Labels");
-
- $size = is_array($post['confName']) ? count($post['confName']) : 0;
- for ($i = 0; $i < $size; $i++) {
- $Type = $post['confType'][$i];
- $config = $xml->addChild('Config', 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];
- } elseif ($Type == 'Path') {
- $path = $xml->Data->addChild("Volume");
- $path->HostDir = $post['confValue'][$i];
- $path->ContainerDir = $post['confTarget'][$i];
- $path->Mode = $post['confMode'][$i];
- } elseif ($Type == 'Variable') {
- $variable = $xml->Environment->addChild("Variable");
- $variable->Value = $post['confValue'][$i];
- $variable->Name = $post['confTarget'][$i];
- $variable->Mode = $post['confMode'][$i];
- } elseif ($Type == 'Label') {
- $label = $xml->Labels->addChild("Label");
- $label->Value = $post['confValue'][$i];
- $label->Name = $post['confTarget'][$i];
- $label->Mode = $post['confMode'][$i];
- }
- }
- $dom = new DOMDocument('1.0');
- $dom->preserveWhiteSpace = false;
- $dom->formatOutput = true;
- $dom->loadXML($xml->asXML());
- return $dom->saveXML();
-}
-
-function xmlToVar($xml) {
- global $subnet;
- $xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
- $out = [];
- $out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
- $out['Repository'] = xml_decode($xml->Repository);
- $out['Registry'] = xml_decode($xml->Registry);
- $out['Network'] = xml_decode($xml->Network);
- $out['MyIP'] = xml_decode($xml->MyIP ?? '');
- $out['Shell'] = xml_decode($xml->Shell ?? 'sh');
- $out['Privileged'] = xml_decode($xml->Privileged);
- $out['Support'] = xml_decode($xml->Support);
- $out['Project'] = xml_decode($xml->Project);
- $out['Overview'] = stripslashes(xml_decode($xml->Overview));
- $out['Category'] = xml_decode($xml->Category);
- $out['WebUI'] = xml_decode($xml->WebUI);
- $out['TemplateURL'] = xml_decode($xml->TemplateURL);
- $out['Icon'] = xml_decode($xml->Icon);
- $out['ExtraParams'] = xml_decode($xml->ExtraParams);
- $out['PostArgs'] = xml_decode($xml->PostArgs);
- $out['DonateText'] = xml_decode($xml->DonateText);
- $out['DonateLink'] = xml_decode($xml->DonateLink);
- $out['Config'] = [];
- if (isset($xml->Config)) {
- foreach ($xml->Config as $config) {
- $c = [];
- $c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']);
- foreach ($config->attributes() as $key => $value) {
- $value = xml_decode($value);
- $val = strtolower($value);
- if ($key == 'Mode') {
- switch (xml_decode($config['Type'])) {
- case 'Path':
- $value = ($val=='rw'||$val=='rw,slave'||$val=='rw,shared'||$val=='ro'||$val=='ro,slave'||$val=='ro,shared') ? $value : "rw";
- break;
- case 'Port':
- $value = ($val=='tcp'||$val=='udp') ? $value : "tcp";
- break;
- }
- }
- $c[$key] = $value;
- }
- $out['Config'][] = $c;
- }
- }
- // some xml templates advertise as V2 but omit the new element
- // check for and use the V1 element when this occurs
- if (empty($out['Network']) && isset($xml->Networking->Mode)) {
- $out['Network'] = xml_decode($xml->Networking->Mode);
- }
- // check if network exists
- if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
- // V1 compatibility
- if ($xml['version'] != '2') {
- 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) {
- if (empty(xml_decode($port->ContainerPort))) continue;
- $portNum += 1;
- $out['Config'][] = [
- 'Name' => "Host Port ${portNum}",
- 'Target' => xml_decode($port->ContainerPort),
- 'Default' => xml_decode($port->HostPort),
- 'Value' => xml_decode($port->HostPort),
- 'Mode' => xml_decode($port->Protocol) ? xml_decode($port->Protocol) : "tcp",
- 'Description' => ($out['Network'] == 'bridge') ? 'Container Port: '.xml_decode($port->ContainerPort) : 'n/a',
- 'Type' => 'Port',
- 'Display' => 'always',
- 'Required' => 'true',
- 'Mask' => 'false'
- ];
- }
- }
- if (isset($xml->Data->Volume)) {
- $volNum = 0;
- foreach ($xml->Data->Volume as $vol) {
- if (empty(xml_decode($vol->ContainerDir))) continue;
- $volNum += 1;
- $out['Config'][] = [
- 'Name' => "Host Path ${volNum}",
- 'Target' => xml_decode($vol->ContainerDir),
- 'Default' => xml_decode($vol->HostDir),
- 'Value' => xml_decode($vol->HostDir),
- 'Mode' => xml_decode($vol->Mode) ? xml_decode($vol->Mode) : "rw",
- 'Description' => 'Container Path: '.xml_decode($vol->ContainerDir),
- 'Type' => 'Path',
- 'Display' => 'always',
- 'Required' => 'true',
- 'Mask' => 'false'
- ];
- }
- }
- if (isset($xml->Environment->Variable)) {
- $varNum = 0;
- foreach ($xml->Environment->Variable as $varitem) {
- if (empty(xml_decode($varitem->Name))) continue;
- $varNum += 1;
- $out['Config'][] = [
- 'Name' => "Key ${varNum}",
- 'Target' => xml_decode($varitem->Name),
- 'Default' => xml_decode($varitem->Value),
- 'Value' => xml_decode($varitem->Value),
- 'Mode' => '',
- 'Description' => 'Container Variable: '.xml_decode($varitem->Name),
- 'Type' => 'Variable',
- 'Display' => 'always',
- 'Required' => 'false',
- 'Mask' => 'false'
- ];
- }
- }
- if (isset($xml->Labels->Variable)) {
- $varNum = 0;
- foreach ($xml->Labels->Variable as $varitem) {
- if (empty(xml_decode($varitem->Name))) continue;
- $varNum += 1;
- $out['Config'][] = [
- 'Name' => "Label ${varNum}",
- 'Target' => xml_decode($varitem->Name),
- 'Default' => xml_decode($varitem->Value),
- 'Value' => xml_decode($varitem->Value),
- 'Mode' => '',
- 'Description' => 'Container Label: '.xml_decode($varitem->Name),
- 'Type' => 'Label',
- 'Display' => 'always',
- 'Required' => 'false',
- 'Mask' => 'false'
- ];
- }
- }
- }
- xmlSecurity($out);
- return $out;
-}
-
-function xmlSecurity(&$template) {
- foreach ($template as &$element) {
- if ( is_array($element) ) {
- xmlSecurity($element);
- } else {
- if ( is_string($element) ) {
- $tempElement = htmlspecialchars_decode($element);
- $tempElement = str_replace("[","<",$tempElement);
- $tempElement = str_replace("]",">",$tempElement);
- if ( preg_match('##is',$tempElement) || preg_match('##is',$tempElement) ) {
- $element = "REMOVED";
- }
- }
- }
- }
-}
-
-function xmlToCommand($xml, $create_paths=false) {
- global $docroot, $var, $driver;
- $xml = xmlToVar($xml);
- $cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : '';
- $cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : '';
- $cmdNetwork = '--net='.escapeshellarg(strtolower($xml['Network']));
- $cmdMyIP = $xml['MyIP'] ? '--ip='.escapeshellarg($xml['MyIP']) : '';
- $Volumes = [''];
- $Ports = [''];
- $Variables = [''];
- $Labels = [''];
- $Devices = [''];
- // 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 ($confType != "device" && !strlen($containerConfig)) continue;
- if ($confType == "path") {
- $Volumes[] = escapeshellarg($hostConfig).':'.escapeshellarg($containerConfig).':'.escapeshellarg($Mode);
- if ( ! file_exists($hostConfig) && $create_paths ) {
- @mkdir($hostConfig, 0777, true);
- @chown($hostConfig, 99);
- @chgrp($hostConfig, 100);
- }
- } elseif ($confType == 'port') {
- switch ($driver[$xml['Network']]) {
- case 'host':
- case 'macvlan':
- // Export ports as variable if network is set to host or macvlan
- $Variables[] = strtoupper(escapeshellarg($Mode.'_PORT_'.$containerConfig).'='.escapeshellarg($hostConfig));
- break;
- case 'bridge':
- // Export ports as port if network is set to (custom) bridge
- $Ports[] = escapeshellarg($hostConfig.':'.$containerConfig.'/'.$Mode);
- break;
- case 'none':
- // No export of ports if network is set to none
- }
- } elseif ($confType == "label") {
- $Labels[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
- } elseif ($confType == "variable") {
- $Variables[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
- } elseif ($confType == "device") {
- $Devices[] = escapeshellarg($hostConfig);
- }
- }
- $postArgs = explode(";",$xml['PostArgs']);
- $cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s',
- $cmdName, $cmdNetwork, $cmdMyIP, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $postArgs[0]);
- return [preg_replace('/\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
-}
-
-function execCommand($command) {
- // $command should have all its args already properly run through 'escapeshellarg'
- $descriptorspec = [
- 0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
- 1 => ['pipe', 'w'], // stdout is a pipe that the child will write to
- 2 => ['pipe', 'w'] // stderr is a pipe that the child will write to
- ];
- $id = mt_rand();
- echo '';
- echo '';
- @flush();
- $proc = proc_open($command." 2>&1", $descriptorspec, $pipes, '/', []);
- while ($out = fgets( $pipes[1] )) {
- $out = preg_replace("%[\t\n\x0B\f\r]+%", '', $out);
- echo '';
- @flush();
- }
- $retval = proc_close($proc);
- echo '';
- $out = $retval ? 'The command failed.' : 'The command finished successfully!';
- echo '';
- return $retval===0;
-}
-
-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) {
- $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();
-}
-
-function getAllocations() {
- global $DockerClient, $host;
- foreach ($DockerClient->getDockerContainers() as $ct) {
- $list = $port = [];
- $nat = $ip = false;
- $list['Name'] = $ct['Name'];
- foreach ($ct['Ports'] as $tmp) {
- $nat = $tmp['NAT'];
- $ip = $tmp['IP'];
- $port[] = $tmp['PublicPort'];
- }
- sort($port);
- $ip = $ct['NetworkMode']=='host'||$nat ? $host : ($ip ?: DockerUtil::myIP($ct['Name']) ?: '0.0.0.0');
- $list['Port'] = "$ip : ".(implode(' ',array_unique($port)) ?: '???')." -- {$ct['NetworkMode']}";
- $ports[] = $list;
- }
- return $ports;
-}
# ██████╗ ██████╗ ██████╗ ███████╗
# ██╔════╝██╔═══██╗██╔══██╗██╔════╝
@@ -1441,7 +963,7 @@ optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-t
For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
- For more information, see this link: https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration
+ For more information, see this link: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
diff --git a/plugins/dynamix.docker.manager/include/Helpers.php b/plugins/dynamix.docker.manager/include/Helpers.php
new file mode 100644
index 000000000..2ee81e6e5
--- /dev/null
+++ b/plugins/dynamix.docker.manager/include/Helpers.php
@@ -0,0 +1,500 @@
+
+
+function xml_encode($string) {
+ return htmlspecialchars($string, ENT_XML1, 'UTF-8');
+}
+
+function xml_decode($string) {
+ return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
+}
+
+function postToXML($post, $setOwnership=false) {
+ $dom = new domDocument;
+ $dom->appendChild($dom->createElement("Container"));
+ $xml = simplexml_import_dom($dom);
+ $xml['version'] = 2;
+ $xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
+ $xml->Repository = xml_encode(trim($post['contRepository']));
+ $xml->Registry = xml_encode(trim($post['contRegistry']));
+ $xml->Network = xml_encode($post['contNetwork']);
+ $xml->MyIP = xml_encode($post['contMyIP']);
+ $xml->Shell = xml_encode($post['contShell']);
+ $xml->Privileged = strtolower($post['contPrivileged'])=='on' ? 'true' : 'false';
+ $xml->Support = xml_encode($post['contSupport']);
+ $xml->Project = xml_encode($post['contProject']);
+ $xml->Overview = xml_encode($post['contOverview']);
+ $xml->Category = xml_encode($post['contCategory']);
+ $xml->WebUI = xml_encode(trim($post['contWebUI']));
+ $xml->TemplateURL = xml_encode($post['contTemplateURL']);
+ $xml->Icon = xml_encode(trim($post['contIcon']));
+ $xml->ExtraParams = xml_encode($post['contExtraParams']);
+ $xml->PostArgs = xml_encode($post['contPostArgs']);
+ $xml->DateInstalled = xml_encode(time());
+ $xml->DonateText = xml_encode($post['contDonateText']);
+ $xml->DonateLink = xml_encode($post['contDonateLink']);
+
+ // 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");
+ $xml->addChild("Labels");
+
+ $size = is_array($post['confName']) ? count($post['confName']) : 0;
+ for ($i = 0; $i < $size; $i++) {
+ $Type = $post['confType'][$i];
+ $config = $xml->addChild('Config', 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];
+ } elseif ($Type == 'Path') {
+ $path = $xml->Data->addChild("Volume");
+ $path->HostDir = $post['confValue'][$i];
+ $path->ContainerDir = $post['confTarget'][$i];
+ $path->Mode = $post['confMode'][$i];
+ } elseif ($Type == 'Variable') {
+ $variable = $xml->Environment->addChild("Variable");
+ $variable->Value = $post['confValue'][$i];
+ $variable->Name = $post['confTarget'][$i];
+ $variable->Mode = $post['confMode'][$i];
+ } elseif ($Type == 'Label') {
+ $label = $xml->Labels->addChild("Label");
+ $label->Value = $post['confValue'][$i];
+ $label->Name = $post['confTarget'][$i];
+ $label->Mode = $post['confMode'][$i];
+ }
+ }
+ $dom = new DOMDocument('1.0');
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ $dom->loadXML($xml->asXML());
+ return $dom->saveXML();
+}
+
+function xmlToVar($xml) {
+ global $subnet;
+ $xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
+ $out = [];
+ $out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
+ $out['Repository'] = xml_decode($xml->Repository);
+ $out['Registry'] = xml_decode($xml->Registry);
+ $out['Network'] = xml_decode($xml->Network);
+ $out['MyIP'] = xml_decode($xml->MyIP ?? '');
+ $out['Shell'] = xml_decode($xml->Shell ?? 'sh');
+ $out['Privileged'] = xml_decode($xml->Privileged);
+ $out['Support'] = xml_decode($xml->Support);
+ $out['Project'] = xml_decode($xml->Project);
+ $out['Overview'] = stripslashes(xml_decode($xml->Overview));
+ $out['Category'] = xml_decode($xml->Category);
+ $out['WebUI'] = xml_decode($xml->WebUI);
+ $out['TemplateURL'] = xml_decode($xml->TemplateURL);
+ $out['Icon'] = xml_decode($xml->Icon);
+ $out['ExtraParams'] = xml_decode($xml->ExtraParams);
+ $out['PostArgs'] = xml_decode($xml->PostArgs);
+ $out['DonateText'] = xml_decode($xml->DonateText);
+ $out['DonateLink'] = xml_decode($xml->DonateLink);
+ $out['Config'] = [];
+ if (isset($xml->Config)) {
+ foreach ($xml->Config as $config) {
+ $c = [];
+ $c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']);
+ foreach ($config->attributes() as $key => $value) {
+ $value = xml_decode($value);
+ $val = strtolower($value);
+ if ($key == 'Mode') {
+ switch (xml_decode($config['Type'])) {
+ case 'Path':
+ $value = ($val=='rw'||$val=='rw,slave'||$val=='rw,shared'||$val=='ro'||$val=='ro,slave'||$val=='ro,shared') ? $value : "rw";
+ break;
+ case 'Port':
+ $value = ($val=='tcp'||$val=='udp') ? $value : "tcp";
+ break;
+ }
+ }
+ $c[$key] = $value;
+ }
+ $out['Config'][] = $c;
+ }
+ }
+ // some xml templates advertise as V2 but omit the new element
+ // check for and use the V1 element when this occurs
+ if (empty($out['Network']) && isset($xml->Networking->Mode)) {
+ $out['Network'] = xml_decode($xml->Networking->Mode);
+ }
+ // check if network exists
+ if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
+ // V1 compatibility
+ if ($xml['version'] != '2') {
+ 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) {
+ if (empty(xml_decode($port->ContainerPort))) continue;
+ $portNum += 1;
+ $out['Config'][] = [
+ 'Name' => "Host Port ${portNum}",
+ 'Target' => xml_decode($port->ContainerPort),
+ 'Default' => xml_decode($port->HostPort),
+ 'Value' => xml_decode($port->HostPort),
+ 'Mode' => xml_decode($port->Protocol) ? xml_decode($port->Protocol) : "tcp",
+ 'Description' => ($out['Network'] == 'bridge') ? 'Container Port: '.xml_decode($port->ContainerPort) : 'n/a',
+ 'Type' => 'Port',
+ 'Display' => 'always',
+ 'Required' => 'true',
+ 'Mask' => 'false'
+ ];
+ }
+ }
+ if (isset($xml->Data->Volume)) {
+ $volNum = 0;
+ foreach ($xml->Data->Volume as $vol) {
+ if (empty(xml_decode($vol->ContainerDir))) continue;
+ $volNum += 1;
+ $out['Config'][] = [
+ 'Name' => "Host Path ${volNum}",
+ 'Target' => xml_decode($vol->ContainerDir),
+ 'Default' => xml_decode($vol->HostDir),
+ 'Value' => xml_decode($vol->HostDir),
+ 'Mode' => xml_decode($vol->Mode) ? xml_decode($vol->Mode) : "rw",
+ 'Description' => 'Container Path: '.xml_decode($vol->ContainerDir),
+ 'Type' => 'Path',
+ 'Display' => 'always',
+ 'Required' => 'true',
+ 'Mask' => 'false'
+ ];
+ }
+ }
+ if (isset($xml->Environment->Variable)) {
+ $varNum = 0;
+ foreach ($xml->Environment->Variable as $varitem) {
+ if (empty(xml_decode($varitem->Name))) continue;
+ $varNum += 1;
+ $out['Config'][] = [
+ 'Name' => "Key ${varNum}",
+ 'Target' => xml_decode($varitem->Name),
+ 'Default' => xml_decode($varitem->Value),
+ 'Value' => xml_decode($varitem->Value),
+ 'Mode' => '',
+ 'Description' => 'Container Variable: '.xml_decode($varitem->Name),
+ 'Type' => 'Variable',
+ 'Display' => 'always',
+ 'Required' => 'false',
+ 'Mask' => 'false'
+ ];
+ }
+ }
+ if (isset($xml->Labels->Variable)) {
+ $varNum = 0;
+ foreach ($xml->Labels->Variable as $varitem) {
+ if (empty(xml_decode($varitem->Name))) continue;
+ $varNum += 1;
+ $out['Config'][] = [
+ 'Name' => "Label ${varNum}",
+ 'Target' => xml_decode($varitem->Name),
+ 'Default' => xml_decode($varitem->Value),
+ 'Value' => xml_decode($varitem->Value),
+ 'Mode' => '',
+ 'Description' => 'Container Label: '.xml_decode($varitem->Name),
+ 'Type' => 'Label',
+ 'Display' => 'always',
+ 'Required' => 'false',
+ 'Mask' => 'false'
+ ];
+ }
+ }
+ }
+ xmlSecurity($out);
+ return $out;
+}
+
+function xmlSecurity(&$template) {
+ foreach ($template as &$element) {
+ if ( is_array($element) ) {
+ xmlSecurity($element);
+ } else {
+ if ( is_string($element) ) {
+ $tempElement = htmlspecialchars_decode($element);
+ $tempElement = str_replace("[","<",$tempElement);
+ $tempElement = str_replace("]",">",$tempElement);
+ if ( preg_match('##is',$tempElement) || preg_match('##is',$tempElement) ) {
+ $element = "REMOVED";
+ }
+ }
+ }
+ }
+}
+
+function xmlToCommand($xml, $create_paths=false) {
+ global $docroot, $var, $driver;
+ $xml = xmlToVar($xml);
+ $cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : '';
+ $cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : '';
+ $cmdNetwork = '--net='.escapeshellarg(strtolower($xml['Network']));
+ $cmdMyIP = $xml['MyIP'] ? '--ip='.escapeshellarg($xml['MyIP']) : '';
+ $Volumes = [''];
+ $Ports = [''];
+ $Variables = [''];
+ $Labels = [''];
+ $Devices = [''];
+ // 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 ($confType != "device" && !strlen($containerConfig)) continue;
+ if ($confType == "path") {
+ $Volumes[] = escapeshellarg($hostConfig).':'.escapeshellarg($containerConfig).':'.escapeshellarg($Mode);
+ if ( ! file_exists($hostConfig) && $create_paths ) {
+ @mkdir($hostConfig, 0777, true);
+ @chown($hostConfig, 99);
+ @chgrp($hostConfig, 100);
+ }
+ } elseif ($confType == 'port') {
+ switch ($driver[$xml['Network']]) {
+ case 'host':
+ case 'macvlan':
+ // Export ports as variable if network is set to host or macvlan
+ $Variables[] = strtoupper(escapeshellarg($Mode.'_PORT_'.$containerConfig).'='.escapeshellarg($hostConfig));
+ break;
+ case 'bridge':
+ // Export ports as port if network is set to (custom) bridge
+ $Ports[] = escapeshellarg($hostConfig.':'.$containerConfig.'/'.$Mode);
+ break;
+ case 'none':
+ // No export of ports if network is set to none
+ }
+ } elseif ($confType == "label") {
+ $Labels[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
+ } elseif ($confType == "variable") {
+ $Variables[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
+ } elseif ($confType == "device") {
+ $Devices[] = escapeshellarg($hostConfig);
+ }
+ }
+
+ $cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s',
+ $cmdName, $cmdNetwork, $cmdMyIP, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
+ return [preg_replace('/\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
+}
+function stopContainer($name) {
+ global $DockerClient;
+ $waitID = mt_rand();
+ echo "";
+ echo "\n";
+ @flush();
+ $retval = $DockerClient->stopContainer($name);
+ $out = ($retval === true) ? "Successfully stopped container '$name'" : "Error: ".$retval;
+ echo "\n";
+ @flush();
+}
+
+function removeContainer($name, $cache=false) {
+ global $DockerClient;
+ $waitID = mt_rand();
+ echo "";
+ echo "\n";
+ @flush();
+ $retval = $DockerClient->removeContainer($name, false, $cache);
+ $out = ($retval === true) ? "Successfully removed container '$name'" : "Error: ".$retval;
+ echo "\n";
+ @flush();
+}
+
+function removeImage($image) {
+ global $DockerClient;
+ $waitID = mt_rand();
+ echo "";
+ echo "\n";
+ @flush();
+ $retval = $DockerClient->removeImage($image);
+ $out = ($retval === true) ? "Successfully removed image '$image'" : "Error: ".$retval;
+ echo "\n";
+ @flush();
+}
+
+function pullImage($name, $image) {
+ global $DockerClient, $DockerTemplates, $DockerUpdate;
+ $waitID = mt_rand();
+ if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
+ echo "";
+ echo "\n";
+ @flush();
+ $alltotals = [];
+ $laststatus = [];
+ $strError = '';
+ $DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) {
+ $cnt = json_decode($line, true);
+ $id = (isset($cnt['id'])) ? trim($cnt['id']) : '';
+ $status = (isset($cnt['status'])) ? trim($cnt['status']) : '';
+ if (isset($cnt['error'])) {
+ $strError = $cnt['error'];
+ }
+ if ($waitID !== false) {
+ echo "\n";
+ @flush();
+ $waitID = false;
+ }
+ if (empty($status)) return;
+ if (!empty($id)) {
+ if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
+ $alltotals[$id] = $cnt['progressDetail']['total'];
+ }
+ if (empty($laststatus[$id])) {
+ $laststatus[$id] = '';
+ }
+ switch ($status) {
+ case 'Waiting':
+ // Omit
+ break;
+ case 'Downloading':
+ if ($laststatus[$id] != $status) {
+ echo "\n";
+ }
+ $total = $cnt['progressDetail']['total'];
+ $current = $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
+ $alltotals[$id] = $current;
+ echo "\n";
+ }
+ break;
+ default:
+ if ($laststatus[$id] == "Downloading") {
+ echo "\n";
+ }
+ if ($laststatus[$id] != $status) {
+ echo "\n";
+ }
+ break;
+ }
+ $laststatus[$id] = $status;
+ } else {
+ if (strpos($status, 'Status: ') === 0) {
+ echo "\n";
+ }
+ if (strpos($status, 'Digest: ') === 0) {
+ $DockerUpdate->setUpdateStatus($image, substr($status, 8));
+ }
+ }
+ @flush();
+ });
+ echo "\n";
+ @flush();
+ if (!empty($strError)) {
+ echo "\n";
+ @flush();
+ return false;
+ }
+ return true;
+}
+
+function execCommand($command) {
+ if ( dockerRunSecurity($command) ) {
+ $command = "logger 'docker command execution halted due to security violation (Bash command execution or redirection)'";
+ }
+ // $command should have all its args already properly run through 'escapeshellarg'
+ $descriptorspec = [
+ 0 => ['pipe', 'r'], // stdin is a pipe that the child will read from
+ 1 => ['pipe', 'w'], // stdout is a pipe that the child will write to
+ 2 => ['pipe', 'w'] // stderr is a pipe that the child will write to
+ ];
+ $id = mt_rand();
+ echo '';
+ echo '';
+ @flush();
+ $proc = proc_open($command." 2>&1", $descriptorspec, $pipes, '/', []);
+ while ($out = fgets( $pipes[1] )) {
+ $out = preg_replace("%[\t\n\x0B\f\r]+%", '', $out);
+ echo '';
+ @flush();
+ }
+ $retval = proc_close($proc);
+ echo '';
+ $out = $retval ? 'The command failed.' : 'The command finished successfully!';
+ echo '';
+ return $retval===0;
+}
+
+function dockerRunSecurity($command) {
+ $testCommand = htmlspecialchars_decode($command);
+ $testCommand = str_replace("\'","",$testCommand);
+ $cmdSplit = explode("'",$testCommand);
+ for ($i=0; $i","&&"] as $invalidChars ) {
+ if ( strpos($tstCommand,$invalidChars) ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+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) {
+ $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();
+}
+
+function getAllocations() {
+ global $DockerClient, $host;
+ foreach ($DockerClient->getDockerContainers() as $ct) {
+ $list = $port = [];
+ $nat = $ip = false;
+ $list['Name'] = $ct['Name'];
+ foreach ($ct['Ports'] as $tmp) {
+ $nat = $tmp['NAT'];
+ $ip = $tmp['IP'];
+ $port[] = $tmp['PublicPort'];
+ }
+ sort($port);
+ $ip = $ct['NetworkMode']=='host'||$nat ? $host : ($ip ?: DockerUtil::myIP($ct['Name']) ?: '0.0.0.0');
+ $list['Port'] = "$ip : ".(implode(' ',array_unique($port)) ?: '???')." -- {$ct['NetworkMode']}";
+ $ports[] = $list;
+ }
+ return $ports;
+}
+?>
\ No newline at end of file
diff --git a/plugins/dynamix.plugin.manager/Plugins.page b/plugins/dynamix.plugin.manager/Plugins.page
index b8e28e5c2..0e8d70641 100644
--- a/plugins/dynamix.plugin.manager/Plugins.page
+++ b/plugins/dynamix.plugin.manager/Plugins.page
@@ -73,9 +73,9 @@ function loadlist(id) {
$(function() {
loadlist();
$('#plugin_tree').fileTree({root:'/boot/',filter:'plg'}, function(file) {$('#plugin_file').val(file);});
- $('.tabs').append("");
+ $('.tabs').append("");
- $('.tabs').append("");
+ $('.tabs').append("");
});
diff --git a/plugins/dynamix.plugin.manager/scripts/plugin b/plugins/dynamix.plugin.manager/scripts/plugin
index b1ecbfb32..d07c4c1a3 100755
--- a/plugins/dynamix.plugin.manager/scripts/plugin
+++ b/plugins/dynamix.plugin.manager/scripts/plugin
@@ -439,6 +439,7 @@ if ($argc < 3) {
//
$unraid = parse_ini_file('/etc/unraid-version');
if ($method == "install") {
+ $argv[2] = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $argv[2]);
echo "plugin: installing: $argv[2]\n";
// check for URL
if ((strpos($argv[2], "http://") === 0) || (strpos($argv[2], "https://") === 0)) {
diff --git a/plugins/dynamix/include/CertUpload.php b/plugins/dynamix/include/CertUpload.php
index fb00ea483..148b1d451 100644
--- a/plugins/dynamix/include/CertUpload.php
+++ b/plugins/dynamix/include/CertUpload.php
@@ -16,7 +16,7 @@ $text = $_POST['text'] ?? '';
file_put_contents('/boot/config/ssl/certs/certificate_bundle.pem.new', $text);
//validate certificate_bundle.pem.new is for *.unraid.net before moving it over to certificate_bundle.pem
-if (preg_match('/CN=[0-9a-f]{40}\.unraid\.net$/', exec('openssl x509 -in /boot/config/ssl/certs/certificate_bundle.pem.new -subject -noout 2>&1'))) {
+if (preg_match('/[0-9a-f]{40}\.unraid\.net$/', exec('openssl x509 -in /boot/config/ssl/certs/certificate_bundle.pem.new -subject -noout 2>&1'))) {
rename('/boot/config/ssl/certs/certificate_bundle.pem.new', '/boot/config/ssl/certs/certificate_bundle.pem');
} else {
unlink('/boot/config/ssl/certs/certificate_bundle.pem.new');