mirror of
https://github.com/unraid/webgui.git
synced 2026-01-01 15:10:19 -06:00
206 lines
9.5 KiB
PHP
Executable File
206 lines
9.5 KiB
PHP
Executable File
#!/usr/bin/php -q
|
|
<?PHP
|
|
/* Copyright 2005-2023, Lime Technology
|
|
* Copyright 2012-2023, Bergware International.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version 2,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*/
|
|
?>
|
|
<?
|
|
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
|
require_once "$docroot/webGui/include/Wrappers.php";
|
|
extract(parse_plugin_cfg('dynamix',true));
|
|
|
|
// add translations
|
|
$_SERVER['REQUEST_URI'] = '';
|
|
$login_locale = _var($display,'locale');
|
|
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
|
require_once "$docroot/webGui/include/publish.php";
|
|
|
|
$var = parse_ini_file('/var/local/emhttp/var.ini');
|
|
$DockerClient = new DockerClient();
|
|
$DockerUpdate = new DockerUpdate();
|
|
$DockerTemplates = new DockerTemplates();
|
|
|
|
$custom = DockerUtil::custom();
|
|
$subnet = DockerUtil::network($custom);
|
|
$cpus = DockerUtil::cpus();
|
|
|
|
function write(...$messages){
|
|
foreach ($messages as $message) {
|
|
publish('docker', $message,1,false);
|
|
}
|
|
}
|
|
function stopContainer_nchan($name) {
|
|
global $DockerClient;
|
|
$waitID = mt_rand();
|
|
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Stopping container').": ".htmlspecialchars($name)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
|
$retval = $DockerClient->stopContainer($name);
|
|
$out = ($retval === true) ? _('Successfully stopped container').": $name" : _('Error').": ".$retval;
|
|
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
|
}
|
|
function removeContainer_nchan($name) {
|
|
global $DockerClient;
|
|
$waitID = mt_rand();
|
|
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Removing container').": ".htmlspecialchars($name)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
|
$retval = $DockerClient->removeContainer($name);
|
|
$out = ($retval === true) ? _('Successfully removed container').": $name" : _('Error').": ".$retval;
|
|
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
|
}
|
|
function removeImage_nchan($image) {
|
|
global $DockerClient;
|
|
$waitID = mt_rand();
|
|
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Removing orphan image').": ".htmlspecialchars($image)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
|
$retval = $DockerClient->removeImage($image);
|
|
$out = ($retval === true) ? _('Successfully removed orphan image').": $image" : _('Error').": ".$retval;
|
|
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
|
}
|
|
function pullImage_nchan($name, $image) {
|
|
global $DockerClient, $DockerTemplates, $DockerUpdate;
|
|
$waitID = mt_rand();
|
|
if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
|
|
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Pulling image').": ".htmlspecialchars($image)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
|
$alltotals = [];
|
|
$laststatus = [];
|
|
$strError = '';
|
|
$DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) {
|
|
$cnt = json_decode($line, true);
|
|
$id = $cnt['id'] ?? '';
|
|
$status = $cnt['status'] ?? '';
|
|
if (isset($cnt['error'])) $strError = $cnt['error'];
|
|
if ($waitID !== false) {
|
|
write("stop_Wait\0$waitID");
|
|
$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) {
|
|
write("addToID\0$id\0".htmlspecialchars($status));
|
|
}
|
|
$total = $cnt['progressDetail']['total'];
|
|
$current = $cnt['progressDetail']['current'];
|
|
if ($total > 0) {
|
|
$percentage = round(($current / $total) * 100);
|
|
write("progress\0$id\0 ".$percentage."% "._('of')." ".$DockerClient->formatBytes($total));
|
|
} 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;
|
|
write("progress\0$id\0".$DockerClient->formatBytes($current));
|
|
}
|
|
break;
|
|
default:
|
|
if ($laststatus[$id] == "Downloading") {
|
|
write("progress\0$id\0 100% "._('of')." ".$DockerClient->formatBytes($alltotals[$id]));
|
|
}
|
|
if ($laststatus[$id] != $status) {
|
|
write("addToID\0".($id=='latest'?mt_rand():$id)."\0".htmlspecialchars($status));
|
|
}
|
|
break;
|
|
}
|
|
$laststatus[$id] = $status;
|
|
} else {
|
|
if (strpos($status, 'Status: ') === 0) {
|
|
write("addLog\0".htmlspecialchars($status));
|
|
}
|
|
if (strpos($status, 'Digest: ') === 0) {
|
|
$DockerUpdate->setUpdateStatus($image, substr($status,8));
|
|
}
|
|
}
|
|
});
|
|
write("addLog\0<br><b>"._('TOTAL DATA PULLED').":</b> ".$DockerClient->formatBytes(array_sum($alltotals)));
|
|
if (!empty($strError)) {
|
|
write("addLog\0<br><span class='error'><b>"._('Error').":</b> ".htmlspecialchars($strError)."</span>");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function execCommand_nchan($command) {
|
|
$waitID = mt_rand();
|
|
[$cmd,$args] = explode(' ',$command,2);
|
|
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br> -",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
|
|
$proc = popen("$command 2>&1",'r');
|
|
while ($out = fgets($proc)) {
|
|
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
|
|
write("addLog\0".htmlspecialchars($out));
|
|
}
|
|
$retval = pclose($proc);
|
|
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
|
|
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
|
|
return $retval===0;
|
|
}
|
|
|
|
$style = ["<style>"];
|
|
$style[] = ".logLine{font-family:bitstream!important;font-size:1.2rem!important;margin:0;padding:0}";
|
|
$style[] = "fieldset.docker{border:solid thin;margin-top:8px}";
|
|
$style[] = "legend{font-size:1.1rem!important;font-weight:bold}";
|
|
$style[] = "</style>";
|
|
|
|
write(implode($style)."<p class='logLine'></p>");
|
|
foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
|
$tmpl = $DockerTemplates->getUserTemplate($value);
|
|
if (!$tmpl) {
|
|
write("addLog\0"._('Configuration not found').". "._('Was this container created using this plugin')."?");
|
|
continue;
|
|
}
|
|
$xml = file_get_contents($tmpl);
|
|
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
|
$Registry = getXmlVal($xml, "Registry");
|
|
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
|
$oldImageID = $DockerClient->getImageID($Repository);
|
|
// pull image
|
|
if (!pullImage_nchan($Name, $Repository)) continue;
|
|
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
|
|
// determine if the container is still running
|
|
$startContainer = false;
|
|
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
|
|
// since container was already running, put it back it to a running state after update
|
|
$startContainer = true;
|
|
// attempt graceful stop of container first
|
|
stopContainer_nchan($Name);
|
|
}
|
|
if ( ($argv[2]??null) == "ca_docker_run_override" )
|
|
$startContainer = true;
|
|
if ( $startContainer )
|
|
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
|
// force kill container if still running after 10 seconds
|
|
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
|
|
// Extract real Entrypoint and Cmd from container for Tailscale
|
|
if ($TS_Enabled == 'true') {
|
|
// Create preliminary base container but don't run it
|
|
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
|
// Get Entrypoint and Cmd from docker inspect
|
|
$containerInfo = $DockerClient->getContainerDetails($Name);
|
|
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
|
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
|
// Insert Entrypoint and Cmd to docker command
|
|
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
|
// Remove preliminary container
|
|
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
|
}
|
|
execCommand_nchan($cmd);
|
|
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
|
$DockerClient->flushCaches();
|
|
$newImageID = $DockerClient->getImageID($Repository);
|
|
// remove old orphan image since it's no longer used by this container
|
|
if ($oldImageID && $oldImageID != $newImageID) removeImage_nchan($oldImageID);
|
|
}
|
|
write('_DONE_','');
|
|
?>
|