Files
webgui/emhttp/plugins/dynamix.docker.manager/scripts/update_container

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>&nbsp;&nbsp;-",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_','');
?>