diff --git a/emhttp/auth-request.php b/emhttp/auth-request.php index b1c5ebea2d..dc21578877 100644 --- a/emhttp/auth-request.php +++ b/emhttp/auth-request.php @@ -14,8 +14,52 @@ session_write_close(); } -// Include JS caching functions -require_once '/usr/local/emhttp/webGui/include/JSCache.php'; +function isPathInDocroot(string $realPath, string $docroot): bool { + return $realPath === $docroot || str_starts_with($realPath, $docroot . '/'); +} + +function getCanonicalRequestUri(string $docroot): string { + $requestUri = getRequestUriPath(); + + $realRequestPath = realpath($docroot . '/' . ltrim($requestUri, '/')); + if (!is_string($realRequestPath) || !isPathInDocroot($realRequestPath, $docroot)) { + return ''; + } + + $canonicalRequestUri = substr($realRequestPath, strlen($docroot)); + return $canonicalRequestUri === '' ? '/' : $canonicalRequestUri; +} + +function isWebComponentsRequest(string $requestUri): bool { + $webComponentsDirectory = '/plugins/dynamix.my.servers/unraid-components'; + return $requestUri === $webComponentsDirectory || str_starts_with($requestUri, $webComponentsDirectory . '/'); +} + +function getRequestUriPath(): string { + $requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); + return is_string($requestUri) ? $requestUri : '/'; +} + +function getAllowedExternalPublicAssetTargets(): array { + return [ + '/webGui/images/case-model.png' => '/boot/config/plugins/dynamix/case-model.png', + ]; +} + +function isAllowedPublicAssetRequest(string $requestUri, string $docroot, array $arrWhitelist): bool { + if (!in_array($requestUri, $arrWhitelist, true)) { + return false; + } + + $realRequestPath = realpath($docroot . '/' . ltrim($requestUri, '/')); + if (is_string($realRequestPath) && isPathInDocroot($realRequestPath, $docroot)) { + return true; + } + + $allowedExternalTargets = getAllowedExternalPublicAssetTargets(); + return isset($allowedExternalTargets[$requestUri]) && + $realRequestPath === $allowedExternalTargets[$requestUri]; +} // Base whitelist of files $arrWhitelist = [ @@ -54,12 +98,22 @@ '/manifest.json' ]; -// Whitelist ALL files from the unraid-components directory -$webComponentsDirectory = '/plugins/dynamix.my.servers/unraid-components/'; -$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?? '/'; +// Use canonical filesystem path checks against the trusted docroot. +$docroot = '/usr/local/emhttp'; +$requestUri = getRequestUriPath(); +$canonicalRequestUri = getCanonicalRequestUri($docroot); + +// Allow explicit public assets with strict target checks. +if (isAllowedPublicAssetRequest($requestUri, $docroot, $arrWhitelist)) { + http_response_code(200); + exit; +} -// Check if the request is for any file in the unraid-components directory -if (str_starts_with($requestUri, $webComponentsDirectory) || in_array($requestUri, $arrWhitelist)) { +// Allow canonical requests under unraid-components. +if ( + $canonicalRequestUri !== '' && + isWebComponentsRequest($canonicalRequestUri) +) { // authorized http_response_code(200); } else { diff --git a/emhttp/plugins/dynamix.docker.manager/javascript/docker.js b/emhttp/plugins/dynamix.docker.manager/javascript/docker.js index ad46fa3850..fd8599c337 100644 --- a/emhttp/plugins/dynamix.docker.manager/javascript/docker.js +++ b/emhttp/plugins/dynamix.docker.manager/javascript/docker.js @@ -51,6 +51,8 @@ function addDockerContainerContext(container, image, template, started, paused, } context.destroy('#'+id); context.attach('#'+id, opts); + $('#dropdown-'+id).css('z-index', 10001) + .append(''); } function addDockerImageContext(image, imageTag) { var opts = []; diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/language b/emhttp/plugins/dynamix.plugin.manager/scripts/language index 0515669096..a448ff0366 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/language +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/language @@ -86,6 +86,26 @@ function run($command) { return pclose($run); } +function remove_tree(string $dir): bool { + if (!is_dir($dir)) return false; + $items = scandir($dir); + if ($items === false) return false; + foreach ($items as $item) { + if ($item === '.' || $item === '..') continue; + $entry = "$dir/$item"; + if (is_dir($entry) && !is_link($entry)) { + if (!remove_tree($entry)) return false; + } elseif (!@unlink($entry)) { + return false; + } + } + return @rmdir($dir); +} + +function valid_language_pack_name($name): bool { + return is_string($name) && $name !== '' && preg_match('/^[A-Za-z0-9._$-]+$/', $name); +} + // Run hooked scripts before correct execution of "method" // method = install, update, remove, check // hook programs receives three parameters: type=language and method and language-name @@ -159,7 +179,11 @@ function language($method, $xml_file, &$error) { switch ($method) { case 'install': $url = $xml->LanguageURL; - $name = $xml->LanguagePack; + $name = (string)$xml->LanguagePack; + if (!valid_language_pack_name($name)) { + $error = "invalid language pack"; + return false; + } $save = "$boot/dynamix/lang-$name.zip"; if (!file_exists($save)) { if ($url) { @@ -173,22 +197,54 @@ function language($method, $xml_file, &$error) { } } $path = "$docroot/languages/$name"; - exec("mkdir -p $path"); + if (!is_dir($path) && !@mkdir($path, 0777, true)) { + $error = "failed to create language directory"; + return false; + } @unlink("$docroot/webGui/javascript/translate.$name.js"); - foreach (glob("$path/*.dot",GLOB_NOSORT) as $dot_file) unlink($dot_file); - exec("unzip -qqLjo -d $path $save", $dummy, $err); + foreach (['*.dot', '*.txt', '*.json'] as $pattern) { + foreach (glob("$path/$pattern", GLOB_NOSORT) as $lang_file) @unlink($lang_file); + } + $err = 0; + $zip = new ZipArchive(); + if ($zip->open($save) === true) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $entry = $zip->getNameIndex($i); + if ($entry === false) continue; + if (substr($entry, -1) === '/') continue; + $content = $zip->getFromIndex($i); + if ($content === false) { + $err = 2; + break; + } + $entryName = strtolower(basename($entry)); + if ($entryName === '') continue; + if (!preg_match('/\.(dot|txt|json)$/', $entryName)) continue; + if (file_put_contents("$path/$entryName", $content) === false) { + $err = 2; + break; + } + } + $zip->close(); + } else { + $err = 2; + } if ($err > 1) { @unlink($save); - exec("rm -rf $path"); + remove_tree($path); $error = "unzip failed. Error code $err"; return false; } return true; case 'remove': - $name = $xml->LanguagePack; + $name = (string)$xml->LanguagePack; if ($name) { + if (!valid_language_pack_name($name)) { + $error = "invalid language pack"; + return false; + } $path = "$docroot/languages/$name"; - exec("rm -rf $path"); + if (is_dir($path)) remove_tree($path); @unlink("$docroot/webGui/javascript/translate.$name.js"); @unlink("$boot/lang-$name.xml"); @unlink("$plugins/lang-$name.xml"); diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 69e450aa72..d937d6d4d5 100755 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -503,7 +503,12 @@ function tableHeaderResize() { $(function() { - $('#countdown').html(""); + $('#countdown').empty().append( + $('', { + class: '', + text: + }) + ); $('#btnAddVM').click(function AddVMEvent(){$('.tab>input#tab2').click();}); $.removeCookie('lockbutton'); diff --git a/emhttp/plugins/dynamix/include/FileUpload.php b/emhttp/plugins/dynamix/include/FileUpload.php index 27aca0503a..80ff432075 100644 --- a/emhttp/plugins/dynamix/include/FileUpload.php +++ b/emhttp/plugins/dynamix/include/FileUpload.php @@ -21,6 +21,28 @@ $safeexts = ['.png']; $result = false; +function in_safe_path(string $path, string $base): bool { + $path = rtrim($path, '/').'/'; + $base = rtrim($base, '/').'/'; + return strpos($path, $base) === 0; +} + +function remove_tree(string $dir): bool { + if (!is_dir($dir)) return false; + $items = scandir($dir); + if ($items === false) return false; + foreach ($items as $item) { + if ($item === '.' || $item === '..') continue; + $entry = "$dir/$item"; + if (is_dir($entry) && !is_link($entry)) { + if (!remove_tree($entry)) return false; + } elseif (!@unlink($entry)) { + return false; + } + } + return @rmdir($dir); +} + require_once "$docroot/webGui/include/Helpers.php"; switch ($_POST['cmd'] ?? 'load') { @@ -47,20 +69,37 @@ case 'save': // move uploaded file ($verifiedPNG) to final destination $verifiedPNG = "$temp/".basename($file); - $path = $_POST['path']; + $path = $_POST['path'] ?? ''; + $outputRaw = $_POST['output'] ?? ''; + $output = basename($outputRaw); + $outputExt = strtolower(substr($output, -4)); + $isValidFilename = $output !== '' && $output === $outputRaw && preg_match('/^[A-Za-z0-9._$-]+$/', $output); foreach ($safepaths as $safepath) { - if (strpos(dirname("$path/{$_POST['output']}"),$safepath)===0 && in_array(substr(basename($_POST['output']),-4),$safeexts)) { - exec("mkdir -p ".escapeshellarg(realpath($path))); - $result = @rename($verifiedPNG, "$path/{$_POST['output']}"); + $safeBase = realpath($safepath); + $targetDir = realpath($path); + if (!$targetDir && $safeBase) { + $parentDir = realpath(dirname($path)); + if ($parentDir && in_safe_path($parentDir, $safeBase) && @mkdir($path, 0777, true)) { + $targetDir = realpath($path); + } + } + if ($targetDir && $safeBase && in_safe_path($targetDir, $safeBase) && $isValidFilename && in_array($outputExt, $safeexts, true)) { + if (is_dir($targetDir)) { + $result = @rename($verifiedPNG, "$targetDir/$output"); + } break; } } break; case 'delete': - $path = $_POST['path']; + $path = $_POST['path'] ?? ''; + $file = basename($file); + $targetFile = realpath("$path/$file"); + $targetExt = $targetFile ? strtolower(substr($targetFile, -4)) : ''; foreach ($safepaths as $safepath) { - if (strpos(realpath("$path/$file"), $safepath) === 0 && in_array(substr(realpath("$path/$file"), -4), $safeexts)) { - exec("rm -f ".escapeshellarg(realpath("$path/$file"))); + $safeBase = realpath($safepath); + if ($targetFile && $safeBase && in_safe_path($targetFile, $safeBase) && in_array($targetExt, $safeexts, true)) { + @unlink($targetFile); $result = true; break; } @@ -70,14 +109,37 @@ $file = basename($file); $path = "$docroot/languages/$file"; $save = "/tmp/lang-$file.zip"; - exec("mkdir -p $path"); + if (!is_dir($path) && !@mkdir($path, 0777, true)) break; if ($result = file_put_contents($save,base64_decode(preg_replace('/^data:.*;base64,/','',$_POST['filedata'])))) { @unlink("$docroot/webGui/javascript/translate.$file.js"); foreach (glob("$path/*.dot",GLOB_NOSORT) as $dot_file) unlink($dot_file); - exec("unzip -qqjLo -d $path $save", $dummy, $err); + $err = 0; + $zip = new ZipArchive(); + if ($zip->open($save) === true) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $entry = $zip->getNameIndex($i); + if ($entry === false) continue; + if (substr($entry, -1) === '/') continue; + $content = $zip->getFromIndex($i); + if ($content === false) { + $err = 2; + break; + } + $name = strtolower(basename($entry)); + if ($name === '') continue; + if (!preg_match('/\.(dot|txt|json)$/', $name)) continue; + if (file_put_contents("$path/$name", $content) === false) { + $err = 2; + break; + } + } + $zip->close(); + } else { + $err = 2; + } @unlink($save); if ($err > 1) { - exec("rm -rf $path"); + remove_tree($path); $result = false; break; } @@ -101,7 +163,7 @@ $file = basename($file); $path = "$docroot/languages/$file"; if ($result = is_dir($path)) { - exec("rm -rf $path"); + $result = remove_tree($path); @unlink("$docroot/webGui/javascript/translate.$file.js"); @unlink("$boot/lang-$file.xml"); @unlink("$plugins/lang-$file.xml"); diff --git a/emhttp/plugins/dynamix/include/ToggleState.php b/emhttp/plugins/dynamix/include/ToggleState.php index 63e4e87dcd..d940e75169 100644 --- a/emhttp/plugins/dynamix/include/ToggleState.php +++ b/emhttp/plugins/dynamix/include/ToggleState.php @@ -19,7 +19,7 @@ $action = $_POST['action']??''; function emcmd($cmd) { - exec("emcmd '$cmd'"); + exec("emcmd ".escapeshellarg($cmd)); } switch ($device) { case 'New': diff --git a/emhttp/plugins/dynamix/scripts/monitor b/emhttp/plugins/dynamix/scripts/monitor index 01c25eb86a..1e56ca7634 100755 --- a/emhttp/plugins/dynamix/scripts/monitor +++ b/emhttp/plugins/dynamix/scripts/monitor @@ -42,6 +42,184 @@ $pools = pools_filter($disks); $errors = []; $top = 120; +function panelcontrol_monitor_has_show_flag() { + return true; +} + +function panelcontrol_monitor_state_rank($state) { + switch ((string)$state) { + case 'alert': return 5; + case 'warning': return 4; + case 'normal-blink': return 3; + case 'normal': return 2; + case 'off': return 1; + default: return 0; + } +} + +function panelcontrol_monitor_push_event(&$events, $event, $scope, $target, $state, $reason, $details=[]) { + $events[] = [ + 'event' => (string)$event, + 'scope' => (string)$scope, + 'target' => (string)$target, + 'state' => (string)$state, + 'reason' => (string)$reason, + 'details' => is_array($details) ? $details : [], + ]; +} + +function panelcontrol_monitor_build_payload($var, $disks, $devs, $saved, $display, $server, $high1, $high2, $top) { + $events = []; + + foreach ((array)$disks as $disk) { + $name = _var($disk,'name'); + if ($name === 'flash' || substr(_var($disk,'status'),-3) === '_NP') { + continue; + } + + $diskName = no_tilde((string)$name); + $diskDevice = strtolower(trim((string)_var($disk,'device',''))); + $diskId = trim((string)_var($disk,'id','')); + $temp = _var($disk,'temp','*'); + $spundownRaw = _var($disk,'spundown',''); + $spundownValue = strtolower(trim((string)$spundownRaw)); + $status = strtolower((string)_var($disk,'status','')); + $isNoDeviceStatus = strpos($status, 'np') !== false; + $isDisabledStatus = strpos($status, 'dsbl') !== false || strpos($status, 'disabled') !== false; + if (($diskDevice === '' && $diskId === '') || $isNoDeviceStatus || $isDisabledStatus) { + continue; + } + $target = $diskDevice !== '' ? $diskDevice : $diskId; + $color = strtolower((string)strtok(_var($disk,'color'),'-')); + $eventsBeforeDisk = count($events); + $isSpunDown = in_array($spundownValue, ['1', 'true', 'yes', 'on'], true) + || strpos($status, 'sby') !== false + || strpos($status, 'standby') !== false; + $identity = [ + 'disk_name' => $diskName, + 'disk_device' => $diskDevice, + 'disk_id' => $diskId, + 'disk_temp' => is_numeric($temp) ? (int)$temp : null, + 'disk_spundown' => $spundownRaw, + ]; + + if ($color === 'red') { + panelcontrol_monitor_push_event($events, 'monitor.storage.offline', 'disk', $target, 'alert', 'disk-error-state', [ + 'status' => $status, + ] + $identity); + } elseif ($color === 'yellow') { + panelcontrol_monitor_push_event($events, 'monitor.storage.activity', 'disk', $target, 'normal-blink', 'disk-rebuild-or-sync', [ + 'status' => $status, + ] + $identity); + } + + if ($isSpunDown) { + panelcontrol_monitor_push_event($events, 'monitor.storage.spindown', 'disk', $target, 'normal', 'disk-standby', [ + 'status' => $status, + ] + $identity); + } + + if (!$isSpunDown) { + if (is_numeric($temp)) { + panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'disk', $target, 'normal', 'temperature-sample', [ + 'temp' => (int)$temp, + ] + $identity); + } + + [$hotNVME,$maxNVME] = _var($disk,'transport')=='nvme' ? get_nvme_info(_var($disk,'device'),'temp') : [-1,-1]; + $hot = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot'])); + $max = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max'])); + $tempState = exceed($temp,$max,$top) ? 'alert' : (exceed($temp,$hot,$top) ? 'warning' : ''); + if ($tempState !== '') { + panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'disk', $target, $tempState, 'temperature-threshold', [ + 'temp' => (int)$temp, + 'warning' => (int)$hot, + 'critical' => (int)$max, + ] + $identity); + } + } + + $numErrors = (int)_var($disk,'numErrors',0); + if ($numErrors > 0) { + panelcontrol_monitor_push_event($events, 'monitor.storage.health', 'disk', $target, 'alert', 'read-errors', [ + 'errors' => $numErrors, + ] + $identity); + } + + if (count($events) === $eventsBeforeDisk) { + panelcontrol_monitor_push_event($events, 'monitor.storage.health', 'disk', $target, 'normal', 'disk-present', [ + 'status' => $status, + ] + $identity); + } + } + + foreach ((array)$devs as $dev) { + $name = _var($dev,'name','no-name'); + $target = 'device:' . no_tilde((string)$name); + $temp = _var($dev,'temp','*'); + if (!is_numeric($temp)) { + continue; + } + + $tempInt = (int)$temp; + $hot = (int)_var($display,'hot',0); + $max = (int)_var($display,'max',0); + $tempState = exceed($tempInt,$max,$top) ? 'alert' : (exceed($tempInt,$hot,$top) ? 'warning' : ''); + if ($tempState !== '') { + panelcontrol_monitor_push_event($events, 'monitor.system.temperature', 'device', $target, $tempState, 'temperature-threshold', [ + 'temp' => $tempInt, + 'warning' => $hot, + 'critical' => $max, + ]); + } + } + + $targets = []; + $counts = ['off' => 0, 'normal' => 0, 'normal-blink' => 0, 'warning' => 0, 'alert' => 0]; + foreach ($events as $event) { + $target = (string)$event['target']; + $state = (string)$event['state']; + if ($target === '' || $state === '') { + continue; + } + if (!isset($targets[$target]) || panelcontrol_monitor_state_rank($state) > panelcontrol_monitor_state_rank($targets[$target])) { + $targets[$target] = $state; + } + } + + foreach ($targets as $state) { + if (isset($counts[$state])) { + $counts[$state]++; + } + } + + $payloadSavedState = is_array($saved) ? $saved : []; + if (isset($payloadSavedState['used'])) { + unset($payloadSavedState['used']); + } + + return [ + 'schema' => 'panelcontrol.monitor-payload.v1', + 'source' => 'unraid.monitor.copy', + 'generatedAt' => gmdate(DATE_RFC3339), + 'server' => (string)$server, + 'thresholds' => [ + 'docker_critical' => (int)$high1, + 'docker_warning' => (int)$high2, + ], + 'saved_state' => $payloadSavedState, + 'events' => $events, + 'targets' => $targets, + 'summary' => [ + 'event_count' => count($events), + 'targets_count' => count($targets), + 'state_counts' => $counts, + ], + ]; +} + +$panelcontrolShow = panelcontrol_monitor_has_show_flag(); + function check_temp(&$disk,$text,$info) { global $notify,$saved,$server,$display,$top; $name = _var($disk,'name'); @@ -387,5 +565,29 @@ if ($saved) { delete_file($ram,$rom); } } + +if ($panelcontrolShow) { + $payload = panelcontrol_monitor_build_payload($var, $disks, $devs, $saved, $display, $server, $high1, $high2, $top); + $payloadPath = '/usr/local/emhttp/state/panelcontrol-monitor-payload.json'; + $payloadJsonRaw = json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + if ($payloadJsonRaw === false) { + my_logger('Failed to encode panelcontrol monitor payload: '.json_last_error_msg(), 'webgui'); + } else { + $payloadJson = $payloadJsonRaw . "\n"; + $existingPayloadJson = @file_get_contents($payloadPath); + if ($existingPayloadJson !== $payloadJson) { + $tmpPath = $payloadPath . '.tmp'; + if (@file_put_contents($tmpPath, $payloadJson, LOCK_EX) !== false) { + if (!@rename($tmpPath, $payloadPath)) { + @file_put_contents($payloadPath, $payloadJson, LOCK_EX); + @unlink($tmpPath); + } + } else { + @file_put_contents($payloadPath, $payloadJson, LOCK_EX); + } + } + } +} + exit(0); ?> diff --git a/emhttp/plugins/unRAIDServer/EULA.page b/emhttp/plugins/unRAIDServer/EULA.page new file mode 100644 index 0000000000..71668ad473 --- /dev/null +++ b/emhttp/plugins/unRAIDServer/EULA.page @@ -0,0 +1,21 @@ +Menu="About" +Title="EULA" +Icon="icon-eula" +Tag="file-text-o" +--- + + + diff --git a/emhttp/plugins/unRAIDServer/README.md b/emhttp/plugins/unRAIDServer/README.md new file mode 100644 index 0000000000..0692574c3b --- /dev/null +++ b/emhttp/plugins/unRAIDServer/README.md @@ -0,0 +1,3 @@ +**Unraid OS** + +Unraid OS by [Lime Technology, Inc.](https://lime-technology.com). diff --git a/emhttp/plugins/unRAIDServer/icons/eula.png b/emhttp/plugins/unRAIDServer/icons/eula.png new file mode 100644 index 0000000000..09c5757ed6 Binary files /dev/null and b/emhttp/plugins/unRAIDServer/icons/eula.png differ diff --git a/emhttp/plugins/unRAIDServer/icons/license.png b/emhttp/plugins/unRAIDServer/icons/license.png new file mode 100644 index 0000000000..d6e06fae5a Binary files /dev/null and b/emhttp/plugins/unRAIDServer/icons/license.png differ diff --git a/emhttp/plugins/unRAIDServer/images/unRAIDServer.png b/emhttp/plugins/unRAIDServer/images/unRAIDServer.png new file mode 100644 index 0000000000..d22f233133 Binary files /dev/null and b/emhttp/plugins/unRAIDServer/images/unRAIDServer.png differ diff --git a/emhttp/plugins/unRAIDServer/unRAIDServer.plg b/emhttp/plugins/unRAIDServer/unRAIDServer.plg new file mode 100644 index 0000000000..a9ddf690a3 --- /dev/null +++ b/emhttp/plugins/unRAIDServer/unRAIDServer.plg @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + +]> + + + + +# Version &version; &date; + +## Changes + +Refer to [Release Notes](¬es;) + +## Linux kernel + +* version &kernel; + + + + + + +rm /tmp/&name;.sh +# cleanup possibly failed previous download/install attempt +rm -rf /tmp/&name;* +mkdir /tmp/&name; +# check if this is unRAID-5 +source /etc/unraid-version +if [[ "${version:0:2}" == "5." ]]; then + # prevent endless install loop + rm -rf /boot/plugins/&name;.plg + rm -rf /boot/config/plugins/&name;.plg + # check if 64-bit capable CPU + if ! grep -q " lm" /proc/cpuinfo ; then + echo "CPU is not 64-bit capable" + exit 1 + fi + # Wait until network is ready by pinging google - thanks bonienl! + ip=8.8.4.4 + timer=30 + while [[ $timer -gt 0 ]]; do + if [[ -n $(route -n|awk '/^0.0.0.0/{print $2}') && $(ping -qnc1 $ip|awk '/received/{print $4}') -eq 1 ]]; then + break + fi + ((timer--)) + sleep 1 + done + if [[ $timer -eq 0 ]]; then + echo "No network communication !!!" + exit 1 + fi + # unRAID-5 needs infozip + if [ ! -f /boot/extra/&infozip; ]; then + echo "Downloading &infozip; package" + mkdir -p /boot/extra + wget http://slackware.cs.utah.edu/pub/slackware/slackware-13.1/slackware/a/&infozip; -O /boot/extra/&infozip; + upgradepkg --install-new /boot/extra/&infozip; + fi + # download the release + if ! wget --no-check-certificate &zip; -O /tmp/&name;.zip ; then + echo "&zip; download error $?" + exit 1 + fi + if ! wget --no-check-certificate &md5; -O /tmp/&name;.md5 ; then + echo "&md5; download error $?" + exit 1 + fi +fi + + + + + + +version="&version;" +/dev/null) +if [[ -n "$REISER_DEVICES" ]]; then + echo "***" + echo "*** ReiserFS filesystem(s) detected:" + echo "$REISER_DEVICES" + echo "*** Upgrading to this Unraid version is blocked while ReiserFS is in use." + echo "*** Please migrate all ReiserFS disks to a supported filesystem and retry." + echo "***" + exit 1 +fi +exit 0 +]]> + + + + + + +NEW_UNRAID_VERSION="&version;" +NEW_API="&api;" +/dev/null; then + echo "Checking for a newer version of Unraid Connect" + # Get the currently installed version of the Connect plugin + INSTALLED_VERSION=$($PLUGIN version "$INSTALLED_PLUGIN_FILE" 2>/dev/null) + + # Check for latest version (downloads to /tmp/plugins/$PLG and outputs version) + # Extract version from output (version is on its own line, filter out hook messages) + LATEST_VERSION=$($PLUGIN check "$PLG" 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | head -n1) + + # Compare versions using PHP's version_compare (returns -1 if latest < installed, 0 if equal, 1 if latest > installed) + if [[ -n "$INSTALLED_VERSION" && -n "$LATEST_VERSION" ]]; then + if [[ $(php -r "echo version_compare('$LATEST_VERSION', '$INSTALLED_VERSION');") -gt 0 ]]; then + # Latest version of Connect is newer, download files without installing + # The 'download' method expects the 'check' method to have put the updated plugin in /tmp/plugins/ + # The 'download' method downloads files but skips Run commands, and stages the plugin in /boot/config/plugins-nextboot/ + echo "Downloading newer version of Unraid Connect" + if ! $PLUGIN download "$PLG" "$NEW_UNRAID_VERSION" 2>/dev/null; then + echo "Warning: Failed to download newer version of Unraid Connect" + elif [[ -f "$NEXTBOOT_PLUGIN_FILE" ]]; then + API_PLUGIN_FILE="$NEXTBOOT_PLUGIN_FILE" + fi + fi + fi + fi + + # If a newer Connect plugin was already staged by a previous run, prefer that staged file + # for the API check even when this execution path did not just download it. + if [[ "$API_PLUGIN_FILE" == "$INSTALLED_PLUGIN_FILE" && -f "$NEXTBOOT_PLUGIN_FILE" ]]; then + NEXTBOOT_VERSION=$($PLUGIN version "$NEXTBOOT_PLUGIN_FILE" 2>/dev/null) + INSTALLED_VERSION=${INSTALLED_VERSION:-$($PLUGIN version "$INSTALLED_PLUGIN_FILE" 2>/dev/null)} + if [[ -n "$NEXTBOOT_VERSION" && ( -z "$INSTALLED_VERSION" || $(php -r "echo version_compare('$NEXTBOOT_VERSION', '$INSTALLED_VERSION');") -gt 0 ) ]]; then + API_PLUGIN_FILE="$NEXTBOOT_PLUGIN_FILE" + fi + fi + + # Get the API version from the Unraid Connect plugin that will be used after reboot. + INSTALLED_API=$(grep -o 'ENTITY api_version "[^"]*"' "$API_PLUGIN_FILE" | sed 's/ENTITY api_version "//;s/"//') + [[ -z "$INSTALLED_API" ]] && exit 0 + + # Ensure the API version for this new version of Unraid is defined + [[ -z "$NEW_API" ]] && exit 0 + + # Strip build strings (everything after +) from both versions before comparison + INSTALLED_API=${INSTALLED_API%+*} + NEW_API=${NEW_API%+*} + + # Compare API versions - if installed plugin has older API than the new unRAID version, require update + if [[ $(php -r "echo version_compare('$INSTALLED_API', '$NEW_API');") -lt 0 ]]; then + echo "***" + echo "*** Please update the Unraid Connect plugin before upgrading Unraid!" + echo "***" + exit 1 + fi + +fi + +exit 0 +]]> + + + + + +&zip; + + +&md5; + + + + +rm /tmp/&name;.sh +# check download and extract +sum1=$(/usr/bin/md5sum /tmp/&name;.zip) +sum2=$(cat /tmp/&name;.md5) +if [[ "${sum1:0:32}" != "${sum2:0:32}" ]]; then + echo "wrong md5" + exit 1 +fi +# check if enough free space on flash +have=$(df -B1 /boot | awk 'END {print $4}') +need=$(unzip -l /tmp/&name;.zip | awk 'END {print $1}') +source /etc/unraid-version +if [[ "${version:0:2}" == "5." ]]; then + # to permit another upgrade + need=$(($need * 2)) +fi +if [[ $need -gt $have ]]; then + echo "boot device shows $have free but upgrade needs $need" + exit 1 +fi +# detect if Plugin-Update-Helper for third party driver plugins is running and update it +plg_update_helper=$(ps aux | grep -E "inotifywait -q /boot/changes.txt -e move_self,delete_self" | grep -v "grep -E inotifywait" | awk '{print $2}') +if [ ! -z "${plg_update_helper}" ]; then + echo "Plugin-Update-Helper for 3rd party driver plugins found, updating..." + kill ${plg_update_helper} 2>/dev/null + wget -q -T 5 -O /usr/bin/plugin_update_helper "https://raw.githubusercontent.com/ich777/unraid-plugin_update_helper/master/plugin_update_helper" + chmod +x /usr/bin/plugin_update_helper 2>/dev/null + echo "/usr/bin/plugin_update_helper" | at now > /dev/null 2>&1 +fi +# move release files to flash +echo +echo "writing flash device - please wait..." +mkdir -p /boot/&name; +rm -rf /boot/&name;/* +files=&files; +unzip -d /boot/&name; /tmp/&name;.zip ${files//,/ } || exit 1 +# ensure writes to USB flash boot device have completed and bz*.sha256 matches +sync -f /boot ; echo 3 > /proc/sys/vm/drop_caches +for sha in /boot/&name;/*.sha256; do + file=${sha%.sha256} + echo "checking sha256 on ${file}" + sha256expect=$(cat "${sha}") + sha256actual=$(/usr/bin/sha256sum "${file}") + if [[ "${sha256actual:0:64}" != "${sha256expect:0:64}" ]]; then + echo "*** bad sha256 on ${file}" + badsha256="yes" + fi +done +if [[ -v badsha256 ]]; then + echo "***" + echo "*** The upgrade failed, but no changes were made to your configuration." + echo "*** Your USB Flash is likely failing." + echo "***" + exit 1 +fi +# preserve previous version +source /etc/unraid-version +if [[ "${version:0:2}" == "5." ]]; then + mkdir -p /boot/unRAID5 + rm -rf /boot/unRAID5/* + # preserve all files in root of flash except ldlinux.sys needed to boot + find /boot -maxdepth 1 -type f -not -name ldlinux.sys -exec mv {} /boot/unRAID5 \; + # preserve a few directories + mv /boot/extra /boot/unRAID5 &> /dev/null + mv /boot/packages /boot/unRAID5 &> /dev/null + mv /boot/plugins /boot/unRAID5 &> /dev/null + mkdir /boot/unRAID5/config + mv /boot/config/plugins /boot/unRAID5/config &> /dev/null + # grab a fresh 'go' file + mv /boot/config/go /boot/unRAID5/config + unzip -d /boot /tmp/&name;.zip config/go || exit 1 + # ensure key file is in the 'config' directory + cp /boot/unRAID5/*.key /boot/config &> /dev/null +else + mkdir -p /boot/previous + # check for earlier upgrade without reboot + if ! /sbin/mount | grep -q "/boot/previous/bz" ; then + rm -rf /boot/previous/* + mv /boot/{&files;} /boot/previous &> /dev/null + fi +fi +# capture the current hardware list +/sbin/lspci -nnmm > /boot/previous/hardware +# move new version files into place +mv /boot/&name;/{&files;} /boot +rm -r /boot/&name; +# Unraid-6/7 specific +if [[ "${version:0:2}" == "6." || "${version:0:2}" == "7." ]]; then + # replace the readme file + echo "**REBOOT REQUIRED!**" > /usr/local/emhttp/plugins/&name;/README.md + # update pre-6.12.2 wireguard configuration files + if [[ $(php -r "echo version_compare('$version', '6.12.2');") -lt 0 ]]; then + for wg in /boot/config/wireguard/*.conf; do + sed -ri "s/^(Post(Up|Down)=logger -t wireguard .*(started|stopped)).*/\1';\/usr\/local\/emhttp\/webGui\/scripts\/update_services/" $wg + done + fi + # when upgrading any version prior to 6.10 + if [[ $(php -r "echo version_compare('$version', '6.10');") -lt 0 ]]; then + if [[ -f /boot/config/ssl/certs/certificate_bundle.pem ]]; then + if grep -q 'USE_SSL="no"' /boot/config/ident.cfg &> /dev/null ; then + rm /boot/config/ssl/certs/certificate_bundle.pem + else + sed -i 's|USE_SSL="auto"|USE_SSL="yes"|g' /boot/config/ident.cfg + fi + else + sed -i 's|USE_SSL="auto"|USE_SSL="no"|g' /boot/config/ident.cfg + fi + fi +fi +# if Unraid-6.3 ensure GUI Safe Mode syslinux option exists +if [[ "${version:0:3}" == "6.3" ]]; then + if ! grep -q 'initrd=/bzroot,/bzroot-gui unraidsafemode' /boot/syslinux/syslinux.cfg &> /dev/null ; then + sed -i 's|label Memtest86+|label unRAID OS GUI Safe Mode (no plugins)\r\n kernel /bzimage\r\n append initrd=/bzroot,/bzroot-gui unraidsafemode\r\nlabel Memtest86+|g' /boot/syslinux/syslinux.cfg &> /dev/null + fi +fi +# when upgrading any version prior to 6.2 +if [[ "${version:0:3}" < "6.2" ]]; then + if ! grep -q '/bzroot-gui' /boot/syslinux/syslinux.cfg &> /dev/null ; then + sed -i 's|menu title Lime Technology\r|menu title Lime Technology, Inc.\r|g' /boot/syslinux/syslinux.cfg &> /dev/null + sed -i 's|label unRAID OS Safe Mode (no plugins)|label unRAID OS GUI Mode\r\n kernel /bzimage\r\n append initrd=/bzroot,/bzroot-gui\r\nlabel unRAID OS Safe Mode (no plugins, no GUI)|g' /boot/syslinux/syslinux.cfg &> /dev/null + fi +fi +# when upgrading any version prior to 6.1 +if [[ "${version:0:3}" < "6.1" ]]; then + if ! grep -q 'shareDisk' /boot/config/share.cfg &> /dev/null ; then + echo 'shareDisk="yes"' >> /boot/config/share.cfg + fi +fi +# when upgrading from 6.0.x +if [[ "${version:0:3}" == "6.0" ]]; then + sed -i 's|dynamix.docker.manager/dockerupdate.php|dynamix.docker.manager/scripts/dockerupdate.php|g' /boot/config/plugins/dynamix/docker-update.cron &> /dev/null + sed -i 's|sbin/monitor|emhttp/plugins/dynamix/scripts/monitor|g' /boot/config/plugins/dynamix/monitor.cron &> /dev/null + sed -i 's|/root/mdcmd|/usr/local/sbin/mdcmd|g' /boot/config/plugins/dynamix/parity-check.cron &> /dev/null + sed -i 's|sbin/plugincheck|emhttp/plugins/dynamix.plugin.manager/scripts/plugincheck|g' /boot/config/plugins/dynamix/plugin-check.cron &> /dev/null + sed -i 's|sbin/statuscheck|emhttp/plugins/dynamix/scripts/statuscheck|g' /boot/config/plugins/dynamix/status-check.cron &> /dev/null +fi +# if template-repos does not exist +if [[ ! -e /boot/config/plugins/dockerMan/template-repos ]]; then + mkdir -p /boot/config/plugins/dockerMan + echo "https://github.com/limetech/docker-templates" > /boot/config/plugins/dockerMan/template-repos +fi +# if EFI or EFI- directory does not exist on vfat file system +if [[ ! -e /boot/efi && ! -e /boot/efi- ]]; then + unzip -d /boot /tmp/&name;.zip EFI/* || exit 1 + sed -i 's|default /syslinux/menu.c32|default menu.c32|g' /boot/syslinux/syslinux.cfg &> /dev/null +fi +# if metric appended to GATEWAY get rid of it +if [[ -f /boot/config/network.cfg ]]; then + sed -ri 's|^(GATEWAY.+)#[0-9]+|\1|' /boot/config/network.cfg +fi +# if legacy dynamix.plg file exists, delete it +rm -f /boot/config/plugins/dynamix.plg +# download any patches for this version before rebooting +if [[ -x /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php ]]; then + /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php check &version; +fi +# ensure writes to USB flash boot device have completed +sync -f /boot +if [ -z "${plg_update_helper}" ]; then + echo "Update successful - PLEASE REBOOT YOUR SERVER" +else + echo "Third party plugins found - PLEASE CHECK YOUR UNRAID NOTIFICATIONS AND WAIT FOR THE MESSAGE THAT IT IS SAFE TO REBOOT!" +fi + + + + + + +rm /tmp/&name;.sh +# unRAID-5 doesn't support 'remove' method, so we're done +source /etc/unraid-version +if [[ "${version:0:2}" == "5." ]]; then + exit 0 +fi +if [[ -d /boot/previous ]]; then + # restore previous Unraid-6 release + rm -f /boot/previous/hardware + mv /boot/previous/* /boot + rmdir /boot/previous + echo "**REBOOT REQUIRED!**" > /usr/local/emhttp/plugins/&name;/README.md +elif [[ -d /boot/unRAID5 ]]; then + # restore previous unRAID-5 release + rm -rf /boot/extra + mv /boot/unRAID5/extra /boot &> /dev/null + rm -rf /boot/plugins + mv /boot/unRAID5/plugins /boot &> /dev/null + rm -rf /boot/packages + mv /boot/unRAID5/packages /boot &> /dev/null + rm -rf /boot/config/plugins + mv /boot/unRAID5/config/plugins /boot/config &> /dev/null + mv /boot/unRAID5/config/go /boot/config + rmdir /boot/unRAID5/config + mv /boot/unRAID5/* /boot + rmdir /boot/unRAID5 +else + echo "Cannot remove, no previous version" + exit 1 +fi +echo "syncing..." +sync +echo "Remove successful - PLEASE REBOOT YOUR SERVER" + + + + diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker index 576174ca29..6625441ff1 100755 --- a/etc/rc.d/rc.docker +++ b/etc/rc.d/rc.docker @@ -210,6 +210,51 @@ network(){ docker network ls --filter driver="$1" --format='{{.Name}}' 2>/dev/null | grep -P "^[a-z]+$2(\$|\.)" | tr '\n' ' ' } +configured_gateway(){ + local NETWORK=$1 + local KEY=$2 + local CFG=${NETWORK_CFG:-/boot/config/network.cfg} + [[ -s $CFG ]] || return + + ( + declare -A VLANID USE_DHCP IPADDR NETMASK GATEWAY METRIC USE_DHCP6 IPADDR6 NETMASK6 GATEWAY6 METRIC6 PRIVACY6 DESCRIPTION PROTOCOL + local BASE=${NETWORK%%.*} + local VLAN= + local IFACE ETH VALUE + local CANDIDATE + local -a CANDIDATES + local i j + + [[ $NETWORK == *.* ]] && VLAN=${NETWORK#*.} + . <(fromdos <"$CFG") + + for ((i=0; i<${SYSNICS:-1}; i++)); do + IFACE=${IFNAME[$i]:-eth$i} + ETH=${IFACE/#br/eth} + ETH=${ETH/#bond/eth} + CANDIDATES=("$IFACE" "$ETH" "${BRNAME[$i]}" "${BONDNAME[$i]}") + if [[ $i -eq 0 ]]; then + [[ ${BRIDGING:-} == yes ]] && CANDIDATES+=("br0") + [[ ${BONDING:-} == yes ]] && CANDIDATES+=("bond0") + fi + for CANDIDATE in "${CANDIDATES[@]}"; do + [[ -n $CANDIDATE && $CANDIDATE == "$BASE" ]] || continue + if [[ -z $VLAN ]]; then + [[ $KEY == GATEWAY6 ]] && VALUE=${GATEWAY6[$i]} || VALUE=${GATEWAY[$i]} + [[ -n $VALUE ]] && printf '%s\n' "$VALUE" + exit + fi + for ((j=1; j<${VLANS[$i]:-0}; j++)); do + [[ ${VLANID[$i,$j]} == "$VLAN" ]] || continue + [[ $KEY == GATEWAY6 ]] && VALUE=${GATEWAY6[$i,$j]} || VALUE=${GATEWAY[$i,$j]} + [[ -n $VALUE ]] && printf '%s\n' "$VALUE" + exit + done + done + done + ) +} + # Is container running? container_running(){ local CONTAINER @@ -470,6 +515,7 @@ docker_network_start(){ if [[ -n $IPV4 ]]; then SUBNET=$(ip -4 route show dev $NETWORK | sort | awk -v ORS=" " '$1 !~ /^default/ {print $1}' | sed 's/ $//') GATEWAY=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}') + [[ -n $GATEWAY ]] || GATEWAY=$(configured_gateway "$NETWORK" GATEWAY) SERVER=${IPV4%/*} DHCP=${NETWORK/./_} DHCP=DOCKER_DHCP_${DHCP^^} @@ -481,6 +527,7 @@ docker_network_start(){ if [[ -n $IPV6 ]]; then SUBNET6=$(ip -6 route show dev $NETWORK | sort | awk -v ORS=" " '$1 !~ /^(default|fe80)/ {print $1}' | sed 's/ $//') GATEWAY6=$(ip -6 route show to default dev $NETWORK | awk '{print $3;exit}') + [[ -n $GATEWAY6 ]] || GATEWAY6=$(configured_gateway "$NETWORK" GATEWAY6) fi else # add user defined networks