*=$metric?>*
:eth_ipv4_default_gateway_help:
@@ -213,7 +213,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_:
:
" pattern="=$validIP6?>" title="_(IPv6 address nnnn:xxxx::yyyy)_">
-
" class="slim">
*=$metric?>*
+
" class="slim">
*=$metric?>*
:eth_ipv6_default_gateway_help:
@@ -293,7 +293,7 @@ _(IPv4 address)_:
diff --git a/emhttp/plugins/dynamix/LogViewer.page b/emhttp/plugins/dynamix/LogViewer.page
new file mode 100644
index 000000000..fb9eb150c
--- /dev/null
+++ b/emhttp/plugins/dynamix/LogViewer.page
@@ -0,0 +1,8 @@
+Menu="UNRAID-OS"
+Title="Log Viewer (new)"
+Icon="icon-log"
+Tag="list"
+---
+
+
+
\ No newline at end of file
diff --git a/emhttp/plugins/dynamix/include/.login.php b/emhttp/plugins/dynamix/include/.login.php
index 5f003e7a9..ed0c465d9 100644
--- a/emhttp/plugins/dynamix/include/.login.php
+++ b/emhttp/plugins/dynamix/include/.login.php
@@ -3,7 +3,7 @@
// Only start a session to check if they have a cookie that looks like our session
$server_name = strtok($_SERVER['HTTP_HOST'], ":");
-if (!empty($_COOKIE['unraid_'.md5($server_name)])) {
+if (!empty($_COOKIE['unraid_' . md5($server_name)])) {
// Start the session so we can check if $_SESSION has data
if (session_status() == PHP_SESSION_NONE) {
session_start();
@@ -12,7 +12,7 @@ if (!empty($_COOKIE['unraid_'.md5($server_name)])) {
// Check if the user is already logged in
if ($_SESSION && !empty($_SESSION['unraid_user'])) {
// Redirect the user to the start page
- header("Location: /".$start_page);
+ header("Location: /" . $start_page);
exit;
}
}
@@ -81,24 +81,64 @@ function cleanupFails(string $failFile, int $time): int
// Save fails to disk
if ($updateFails) {
- $failText = implode("\n", $fails)."\n";
+ $failText = implode("\n", $fails) . "\n";
writeToFile($failFile, $failText);
}
return count($fails);
}
+
function verifyUsernamePassword(string $username, string $password): bool
{
- if ($username != "root") {
- return false;
- }
+ if ($username != "root") return false;
$output = exec("/usr/bin/getent shadow $username");
- if ($output === false) {
- return false;
- }
+ if ($output === false) return false;
$credentials = explode(":", $output);
- return password_verify($password, $credentials[1]);
+ $valid = password_verify($password, $credentials[1]);
+ if ($valid) {
+ return true;
+ }
+
+ $serverState = new ServerState();
+ // We may have an SSO token - check if SSO is enabled and then validate the token
+ if ($serverState->ssoEnabled && strlen($password) > 500) {
+ if (!preg_match('/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/', $password)) {
+ my_logger("SSO Login Attempt Failed: Invalid token format");
+ return false;
+ }
+ $safePassword = escapeshellarg($password);
+
+ $output = array();
+ exec("/etc/rc.d/rc.unraid-api sso validate-token $safePassword 2>&1", $output, $code);
+ my_logger("SSO Login Attempt Code: $code");
+ my_logger("SSO Login Attempt Response: " . print_r($output, true));
+
+ if ($code !== 0) {
+ return false;
+ }
+
+ if (empty($output)) {
+ return false;
+ }
+
+ try {
+ // Split on first { and take everything after it
+ $jsonParts = explode('{', $output[0], 2);
+ if (count($jsonParts) < 2) {
+ my_logger("SSO Login Attempt Failed: No JSON found in response");
+ return false;
+ }
+ $response = json_decode('{' . $jsonParts[1], true);
+ if (isset($response['valid']) && $response['valid'] === true) {
+ return true;
+ }
+ } catch (Exception $e) {
+ my_logger("SSO Login Attempt Exception: " . $e->getMessage());
+ return false;
+ }
+ }
+ return false;
}
// Load configs into memory
$my_servers = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true);
@@ -146,7 +186,7 @@ if (!empty($username) && !empty($password)) {
my_logger("Successful login user {$username} from {$remote_addr}");
// Redirect the user to the start page
- header("Location: /".$start_page);
+ header("Location: /" . $start_page);
exit;
} catch (Exception $exception) {
// Set error message
@@ -154,7 +194,7 @@ if (!empty($username) && !empty($password)) {
// Log error to syslog
my_logger("Unsuccessful login user {$username} from {$remote_addr}");
- appendToFile($failFile, $time."\n");
+ appendToFile($failFile, $time . "\n");
}
}
@@ -171,6 +211,7 @@ $isDarkTheme = $themeHelper->isDarkTheme();
+
@@ -182,258 +223,366 @@ $isDarkTheme = $themeHelper->isDarkTheme();
-
=$var['NAME']?>/Login
+
= $var['NAME'] ?>/Login
-
">
-
+
">
+
- =htmlspecialchars($var['NAME'])?>
+ = htmlspecialchars($var['NAME']) ?>
- =htmlspecialchars($var['COMMENT'])?>
+ = htmlspecialchars($var['COMMENT']) ?>
-
-
-
-
-
?>)
-
-
+ if ($myCase): ?>
+ if (substr($myCase, -4) != '.png'): ?>
+
+ else: ?>
+
 ?>)
+ endif; ?>
+ endif; ?>
-
=_('Password recovery')?>
+
= _('Password recovery') ?>
+
diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout.php b/emhttp/plugins/dynamix/include/DefaultPageLayout.php
index bb3df8a7e..44d422f4b 100644
--- a/emhttp/plugins/dynamix/include/DefaultPageLayout.php
+++ b/emhttp/plugins/dynamix/include/DefaultPageLayout.php
@@ -28,22 +28,22 @@ $entity = $notify['entity'] & 1 == 1;
$alerts = '/tmp/plugins/my_alerts.txt';
$wlan0 = file_exists('/sys/class/net/wlan0');
-$safemode = _var($var,'safeMode')=='yes';
+$safemode = _var($var, 'safeMode') == 'yes';
$banner = "$config/webGui/banner.png";
$notes = '/var/tmp/unRAIDServer.txt';
if (!file_exists($notes)) {
- file_put_contents($notes, shell_exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin changes $docroot/plugins/unRAIDServer/unRAIDServer.plg"));
+ file_put_contents($notes, shell_exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin changes $docroot/plugins/unRAIDServer/unRAIDServer.plg"));
}
$taskPages = find_pages('Tasks');
$buttonPages = find_pages('Buttons');
$pages = []; // finds subpages
if (!empty($myPage['text'])) $pages[$myPage['name']] = $myPage;
-if (_var($myPage,'Type')=='xmenu') $pages = array_merge($pages, find_pages($myPage['name']));
+if (_var($myPage, 'Type') == 'xmenu') $pages = array_merge($pages, find_pages($myPage['name']));
// nchan related actions
-$nchan = ['webGui/nchan/notify_poller','webGui/nchan/session_check'];
+$nchan = ['webGui/nchan/notify_poller', 'webGui/nchan/session_check'];
if ($wlan0) $nchan[] = 'webGui/nchan/wlan0';
// build nchan scripts from found pages
$allPages = array_merge($taskPages, $buttonPages, $pages);
@@ -52,96 +52,94 @@ foreach ($allPages as $page) {
}
// act on nchan scripts
if (count($pages)) {
- $running = file_exists($nchan_pid) ? file($nchan_pid,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) : [];
+ $running = file_exists($nchan_pid) ? file($nchan_pid, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
$start = array_diff($nchan, $running); // returns any new scripts to be started
$stop = array_diff($running, $nchan); // returns any old scripts to be stopped
$running = array_merge($start, $running); // update list of current running nchan scripts
// start nchan scripts which are new
foreach ($start as $row) {
- $script = explode(':',$row)[0];
+ $script = explode(':', $row)[0];
exec("$docroot/$script &>/dev/null &");
}
// stop nchan scripts with the :stop option
foreach ($stop as $row) {
- [$script,$opt] = my_explode(':',$row);
+ [$script, $opt] = my_explode(':', $row);
if ($opt == 'stop') {
exec("pkill -f $docroot/$script &>/dev/null &");
- array_splice($running,array_search($row,$running),1);
+ array_splice($running, array_search($row, $running), 1);
}
}
- if (count($running)) file_put_contents($nchan_pid,implode("\n",$running)."\n"); else @unlink($nchan_pid);
+ if (count($running)) file_put_contents($nchan_pid, implode("\n", $running) . "\n");
+ else @unlink($nchan_pid);
}
?>
-lang="=strtok($locale, '_') ?: 'en'?>" class="= $themeHelper->getThemeHtmlClass() ?>">
+lang="= strtok($locale, '_') ?: 'en' ?>" class="= $themeHelper->getThemeHtmlClass() ?>">
+
-
=_var($var, 'NAME')?>/=_var($myPage, 'name')?>
-
-
-
-
-
-
-
-
-
">
-
">
-
">
-
">
-
">
-
">
+
= _var($var, 'NAME') ?>/= _var($myPage, 'name') ?>
+
+
+
+
+
+
+
+
+
">
+
">
+
">
+
">
+
">
+
">
-
">
-
">
-
">
-
">
+
">
+
">
+
">
+
">
-
+ isSidebarTheme()) {
+ echo generate_sidebar_icon_css($taskPages, $buttonPages);
+ }
+ ?>
+
-
+
-
-
+
+
- require_once "$docroot/webGui/include/DefaultPageLayout/HeadInlineJS.php"; ?>
+ require_once "$docroot/webGui/include/DefaultPageLayout/HeadInlineJS.php"; ?>
+ ' . parse_text($button['text']));
+ }
-'.parse_text($button['text']));
-}
+ foreach ($pages as $page) {
+ annotate($page['file']);
+ includePageStylesheets($page);
+ }
+ ?>
-foreach ($pages as $page) {
- annotate($page['file']);
- includePageStylesheets($page);
-}
-?>
-
-
+ include "$docroot/plugins/dynamix.my.servers/include/myservers1.php" ?>
+ require_once "$docroot/webGui/include/DefaultPageLayout/GUIModeSessionFix.php"; ?>
+
include "$docroot/webGui/include/DefaultPageLayout/Header.php"; ?>
include "$docroot/webGui/include/DefaultPageLayout/Navigation/Main.php"; ?>
@@ -149,5 +147,7 @@ foreach ($pages as $page) {
include "$docroot/webGui/include/DefaultPageLayout/Footer.php"; ?>
include "$docroot/webGui/include/DefaultPageLayout/MiscElements.php"; ?>
include "$docroot/webGui/include/DefaultPageLayout/BodyInlineJS.php"; ?>
+ include "$docroot/webGui/include/DefaultPageLayout/ToastSetup.php"; ?>
-
+
+
\ No newline at end of file
diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/BodyInlineJS.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/BodyInlineJS.php
index ae5f5eb67..be71c6f72 100644
--- a/emhttp/plugins/dynamix/include/DefaultPageLayout/BodyInlineJS.php
+++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/BodyInlineJS.php
@@ -71,32 +71,7 @@ defaultPage.on('message', function(msg,meta) {
$('#statusbar').html(status);
break;
case 2:
- // notifications
- var bell1 = 0, bell2 = 0, bell3 = 0;
- $.each($.parseJSON(msg), function(i, notify){
- switch (notify.importance) {
- case 'alert' : bell1++; break;
- case 'warning': bell2++; break;
- case 'normal' : bell3++; break;
- }
-
- if (notify.show) {
- $.jGrowl(notify.subject+'
'+notify.description,{
- group: notify.importance,
- header: notify.event+': '+notify.timestamp,
- theme: notify.file,
- beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);},
- afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');},
- click: function(e,m,o){if (notify.link) location.replace(notify.link);},
- close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'hide',file:"=$notify['path'].'/unread/'?>"+notify.file,csrf_token:csrf_token},function(){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});});}
- });
- }
-
- });
- $('#bell').removeClass('red-orb yellow-orb green-orb').prop('title',"=_('Alerts')?> ["+bell1+']\n'+"=_('Warnings')?> ["+bell2+']\n'+"=_('Notices')?> ["+bell3+']');
- if (bell1) $('#bell').addClass('red-orb'); else
- if (bell2) $('#bell').addClass('yellow-orb'); else
- if (bell3) $('#bell').addClass('green-orb');
+ // notifications - moved to the Unraid API
break;
}
});
diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/GUIModeSessionFix.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/GUIModeSessionFix.php
new file mode 100644
index 000000000..516284e1e
--- /dev/null
+++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/GUIModeSessionFix.php
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/HeadInlineJS.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/HeadInlineJS.php
index 798d1fac5..f4f0b117c 100644
--- a/emhttp/plugins/dynamix/include/DefaultPageLayout/HeadInlineJS.php
+++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/HeadInlineJS.php
@@ -477,32 +477,6 @@ function digits(number) {
return 'three';
}
-function openNotifier() {
- $.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) {
- $.each($.parseJSON(msg), function(i, notify){
- $.jGrowl(notify.subject+'
'+notify.description,{
- group: notify.importance,
- header: notify.event+': '+notify.timestamp,
- theme: notify.file,
- sticky: true,
- beforeOpen: function(e,m,o){if ($('div.jGrowl-notification').hasClass(notify.file)) return(false);},
- afterOpen: function(e,m,o){if (notify.link) $(e).css('cursor','pointer');},
- click: function(e,m,o){if (notify.link) location.replace(notify.link);},
- close: function(e,m,o){$.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});}
- });
- });
- });
-}
-
-function closeNotifier() {
- $.post('/webGui/include/Notify.php',{cmd:'get',csrf_token:csrf_token},function(msg) {
- $.each($.parseJSON(msg), function(i, notify){
- $.post('/webGui/include/Notify.php',{cmd:'archive',file:notify.file,csrf_token:csrf_token});
- });
- $('div.jGrowl').find('div.jGrowl-close').trigger('click');
- });
-}
-
function viewHistory() {
location.replace('/Tools/NotificationsArchive');
}
@@ -536,17 +510,7 @@ $(function() {
}
$('#'+tab).attr('checked', true);
updateTime();
- $.jGrowl.defaults.closeTemplate = '
';
- $.jGrowl.defaults.closerTemplate = '=$notify['position'][0]=='b' ? '
':'
'?>[ =_("close all notifications")?> ]
';
- $.jGrowl.defaults.position = '=$notify['position']?>';
- $.jGrowl.defaults.theme = '';
- $.jGrowl.defaults.themeState = '';
- $.jGrowl.defaults.pool = 10;
- 0):?>
- $.jGrowl.defaults.life = =$notify['life']*1000?>;
-
- $.jGrowl.defaults.sticky = true;
-
+
Shadowbox.setup('a.sb-enable', {modal:true});
// add any pre-existing reboot notices
$.post('/webGui/include/Report.php',{cmd:'notice'},function(notices){
diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php
index 4a33aef78..2bd197073 100644
--- a/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php
+++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php
@@ -1,9 +1,5 @@
'),this.interval=setInterval(function(){var t=a(e).data("jGrowl.instance");if(void 0!==t)try{t.update()}catch(e){throw t.shutdown(),e}},parseInt(this.defaults.check,10))},shutdown:function(){try{a(this.element).removeClass("jGrowl").find(".jGrowl-notification").trigger("jGrowl.close").parent().empty()}catch(e){throw e}finally{clearInterval(this.interval)}},close:function(){a(this.element).find(".jGrowl-notification").each(function(){a(this).trigger("jGrowl.beforeClose")})}}),a.jGrowl.defaults=a.fn.jGrowl.prototype.defaults});
-
/* TableSorter - v2.31.3, copyright Rob Garrison LATEST */
!function(A){"use strict";var L=A.tablesorter={version:"2.31.3",parsers:[],widgets:[],defaults:{theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,resort:!0,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",duplicateSpan:!0,textExtraction:"basic",textAttribute:"data-text",textSorter:null,numberSorter:null,initWidgets:!0,widgetClass:"widget-{name}",widgets:[],widgetOptions:{zebra:["even","odd"]},initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssNone:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",cssIconDisabled:"",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[],globalize:0,imgAttr:0},css:{table:"tablesorter",cssHasChild:"tablesorter-hasChildRow",childRow:"tablesorter-childRow",colgroup:"tablesorter-colgroup",header:"tablesorter-header",headerRow:"tablesorter-headerRow",headerIn:"tablesorter-header-inner",icon:"tablesorter-icon",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc",sortNone:"tablesorter-headerUnSorted"},language:{sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",sortDisabled:"sorting is disabled",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},regex:{templateContent:/\{content\}/g,templateIcon:/\{icon\}/g,templateName:/\{name\}/i,spaces:/\s+/g,nonWord:/\W/g,formElements:/(input|select|button|textarea)/i,chunk:/(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i,comma:/,/g,digitNonUS:/[\s|\.]/g,digitNegativeTest:/^\s*\([.\d]+\)/,digitNegativeReplace:/^\s*\(([.\d]+)\)/,digitTest:/^[\-+(]?\d+[)]?$/,digitReplace:/[,.'"\s]/g},string:{max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,null:0,top:!0,bottom:!1},keyCodes:{enter:13},dates:{},instanceMethods:{},setup:function(t,r){var e,o,s,a;t&&t.tHead&&0!==t.tBodies.length&&!0!==t.hasInitialized?(e="",o=A(t),s=A.metadata,t.hasInitialized=!1,t.isProcessing=!0,t.config=r,A.data(t,"tablesorter",r),L.debug(r,"core")&&(console[console.group?"group":"log"]("Initializing tablesorter v"+L.version),A.data(t,"startoveralltimer",new Date)),r.supportsDataObject=((a=A.fn.jquery.split("."))[0]=parseInt(a[0],10),1
':"",i.$headers=A(A.map(i.$table.find(i.selectorHeaders),function(e,t){var r,o,s,a,n=A(e);if(!L.getClosest(n,"tr").hasClass(i.cssIgnoreRow))return/(th|td)/i.test(e.nodeName)||(a=L.getClosest(n,"th, td"),n.attr("data-column",a.attr("data-column"))),r=L.getColumnData(i.table,i.headers,t,!0),i.headerContent[t]=n.html(),""===i.headerTemplate||n.find("."+L.css.headerIn).length||(s=i.headerTemplate.replace(L.regex.templateContent,n.html()).replace(L.regex.templateIcon,n.find("."+L.css.icon).length?"":d),i.onRenderTemplate&&(o=i.onRenderTemplate.apply(n,[t,s]))&&"string"==typeof o&&(s=o),n.html('")),i.onRenderHeader&&i.onRenderHeader.apply(n,[t,i,i.$table]),s=parseInt(n.attr("data-column"),10),e.column=s,a=L.getOrder(L.getData(n,r,"sortInitialOrder")||i.sortInitialOrder),i.sortVars[s]={count:-1,order:a?i.sortReset?[1,0,2]:[1,0]:i.sortReset?[0,1,2]:[0,1],lockedOrder:!1,sortedBy:""},void 0!==(a=L.getData(n,r,"lockedOrder")||!1)&&!1!==a&&(i.sortVars[s].lockedOrder=!0,i.sortVars[s].order=L.getOrder(a)?[1,1]:[0,0]),i.headerList[t]=e,n.addClass(L.css.header+" "+i.cssHeader),L.getClosest(n,"tr").addClass(L.css.headerRow+" "+i.cssHeaderRow).attr("role","row"),i.tabIndex&&n.attr("tabindex",0),e})),i.$headerIndexed=[],r=0;r'),t=e.$table.width(),s=(o=e.$tbodies.find("tr:first").children(":visible")).length,a=0;a").css("width",r));e.$table.prepend(n)}},getData:function(e,t,r){var o,s="",a=A(e);return a.length?(o=!!A.metadata&&a.metadata(),e=" "+(a.attr("class")||""),void 0!==a.data(r)||void 0!==a.data(r.toLowerCase())?s+=a.data(r)||a.data(r.toLowerCase()):o&&void 0!==o[r]?s+=o[r]:t&&void 0!==t[r]?s+=t[r]:" "!==e&&e.match(" "+r+"-")&&(s=e.match(new RegExp("\\s"+r+"-([\\w-]+)"))[1]||""),A.trim(s)):""},getColumnData:function(e,t,r,o,s){if("object"!=typeof t||null===t)return t;var a,e=(e=A(e)[0]).config,s=s||e.$headers,n=e.$headerIndexed&&e.$headerIndexed[r]||s.find('[data-column="'+r+'"]:last');if(void 0!==t[r])return o?t[r]:t[s.index(n)];for(a in t)if("string"==typeof a&&n.filter(a).add(n.find(a)).length)return t[a]},isProcessing:function(e,t,r){var o=(e=A(e))[0].config,s=r||e.find("."+L.css.header);t?(void 0!==r&&0'),A.fn.detach?t.detach():t.remove();r=A(e).find("colgroup.tablesorter-savemyplace");t.insertAfter(r),r.remove(),e.isProcessing=!1},clearTableBody:function(e){A(e)[0].config.$tbodies.children().detach()},characterEquivalents:{a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},replaceAccents:function(e){var t,r="[",o=L.characterEquivalents;if(!L.characterRegex){for(t in L.characterRegexArray={},o)"string"==typeof t&&(r+=o[t],L.characterRegexArray[t]=new RegExp("["+o[t]+"]","g"));L.characterRegex=new RegExp(r+"]")}if(L.characterRegex.test(e))for(t in o)"string"==typeof t&&(e=e.replace(L.characterRegexArray[t],t));return e},validateOptions:function(e){var t,r,o,s,a="headers sortForce sortList sortAppend widgets".split(" "),n=e.originalSettings;if(n){for(t in L.debug(e,"core")&&(s=new Date),n)if("undefined"===(o=typeof L.defaults[t]))console.warn('Tablesorter Warning! "table.config.'+t+'" option not recognized');else if("object"===o)for(r in n[t])o=L.defaults[t]&&typeof L.defaults[t][r],A.inArray(t,a)<0&&"undefined"===o&&console.warn('Tablesorter Warning! "table.config.'+t+"."+r+'" option not recognized');L.debug(e,"core")&&console.log("validate options time:"+L.benchmark(s))}},restoreHeaders:function(e){for(var t,r=A(e)[0].config,o=r.$table.find(r.selectorHeaders),s=o.length,a=0;a tr").children("th, td"),!1===t&&0<=A.inArray("uitheme",s.widgets)&&(o.triggerHandler("applyWidgetId",["uitheme"]),o.triggerHandler("applyWidgetId",["zebra"])),a.find("tr").not(n).remove(),a="sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave "+"keypress sortBegin sortEnd resetToLoadState ".split(" ").join(s.namespace+" "),o.removeData("tablesorter").unbind(a.replace(L.regex.spaces," ")),s.$headers.add(i).removeClass([L.css.header,s.cssHeader,s.cssAsc,s.cssDesc,L.css.sortAsc,L.css.sortDesc,L.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),n.find(s.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(s.namespace+" ").replace(L.regex.spaces," ")),L.restoreHeaders(e),o.toggleClass(L.css.table+" "+s.tableClass+" tablesorter-"+s.theme,!1===t),o.removeClass(s.namespace.slice(1)),e.hasInitialized=!1,delete e.config.cache,"function"==typeof r&&r(e),L.debug(s,"core")&&console.log("tablesorter has been removed"))}};A.fn.tablesorter=function(t){return this.each(function(){var e=A.extend(!0,{},L.defaults,t,L.instanceMethods);e.originalSettings=t,!this.hasInitialized&&L.buildTable&&"TABLE"!==this.nodeName?L.buildTable(this,e):L.setup(this,e)})},window.console&&window.console.log||(L.logs=[],console={},console.log=console.warn=console.error=console.table=function(){var e=1/dev/null
diff --git a/emhttp/plugins/dynamix/scripts/update_services b/emhttp/plugins/dynamix/scripts/update_services
index a89950733..4fbebbb2a 100755
--- a/emhttp/plugins/dynamix/scripts/update_services
+++ b/emhttp/plugins/dynamix/scripts/update_services
@@ -15,6 +15,6 @@ else
log "no queued job present"
fi
-echo "/usr/local/emhttp/webGui/scripts/reload_services ${1:-1}" | at -M now 2>/dev/null
+echo "sleep ${1:-1}; /usr/local/emhttp/webGui/scripts/reload_services" | at -M now 2>/dev/null
log "queue new job $(queue), wait for ${1:-1}s"
exit 0
diff --git a/emhttp/plugins/dynamix/styles/default-dynamix.css b/emhttp/plugins/dynamix/styles/default-dynamix.css
index 5d5a33060..6e149a5d1 100644
--- a/emhttp/plugins/dynamix/styles/default-dynamix.css
+++ b/emhttp/plugins/dynamix/styles/default-dynamix.css
@@ -2,27 +2,6 @@
* This CSS is specifically for overwriting CSS libraries that dynamix.js uses
*/
:root {
- --dynamix-jgrowl-bg-color: var(--gray-500);
- --dynamix-jgrowl-border-color: var(--gray-600);
- --dynamix-jgrowl-shadow-color: var(--gray-500);
- --dynamix-jgrowl-text-color: var(--gray-600);
-
- --dynamix-jgrowl-alert-bg-color: var(--red-300);
- --dynamix-jgrowl-alert-border-color: var(--red-600);
- --dynamix-jgrowl-alert-text-color: var(--red-600);
-
- --dynamix-jgrowl-warning-bg-color: var(--yellow-200);
- --dynamix-jgrowl-warning-border-color: var(--orange-300);
- --dynamix-jgrowl-warning-text-color: var(--orange-300);
-
- --dynamix-jgrowl-normal-bg-color: var(--green-100);
- --dynamix-jgrowl-normal-border-color: var(--green-800);
- --dynamix-jgrowl-normal-text-color: var(--green-800);
-
- --dynamix-jgrowl-close-text-color: var(--gray-500);
- --dynamix-jgrowl-close-bg-color: var(--black);
- --dynamix-jgrowl-close-border: 2px solid var(--gray-300);
-
--dynamix-ui-dropdownchecklist-color: var(--gray-100);
--dynamix-ui-dropdownchecklist-color-alt1: var(--gray-700);
--dynamix-ui-dropdownchecklist-color-alt2: var(--gray-500);
@@ -89,132 +68,6 @@
--dynamix-awesomplete-mark-selected-bg-color: var(--green-900);
}
-.jGrowl {
- position: fixed;
- font-size: 1.3rem;
- z-index: 10001;
-}
-.jGrowl.top-left {
- left: 10px;
- top: 130px;
-}
-.jGrowl.top-right {
- right: 10px;
- top: 130px;
-}
-.jGrowl.bottom-left {
- left: 10px;
- bottom: 24px;
-}
-.jGrowl.bottom-right {
- right: 10px;
- bottom: 24px;
-}
-.jGrowl.center {
- top: 130px;
- left: 40%;
-}
-.jGrowl.center .jGrowl-closer,
-.jGrowl.center .jGrowl-notification {
- margin-left: auto;
- margin-right: auto;
-}
-.jGrowl-notification {
- opacity: 0.96;
- min-height: 1.2rem;
- width: 380px;
- padding: 10px;
- margin: 5px 0;
- text-align: left;
- border-radius: 6px;
-
- &.alert {
- color: var(--dynamix-jgrowl-alert-text-color);
- background-color: var(--dynamix-jgrowl-alert-bg-color);
- border: 1px solid var(--dynamix-jgrowl-alert-border-color);
- box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
- }
-
- &.warning {
- color: var(--dynamix-jgrowl-warning-text-color);
- background-color: var(--dynamix-jgrowl-warning-bg-color);
- border: 1px solid var(--dynamix-jgrowl-warning-border-color);
- box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
- }
-
- &.normal {
- color: var(--dynamix-jgrowl-normal-text-color);
- background-color: var(--dynamix-jgrowl-normal-bg-color);
- border: 1px solid var(--dynamix-jgrowl-normal-border-color);
- box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
- }
-
- &.default {
- color: var(--dynamix-jgrowl-text-color);
- background-color: var(--dynamix-jgrowl-bg-color);
- border: 1px solid var(--dynamix-jgrowl-border-color);
- box-shadow: 2px 2px 1px var(--dynamix-jgrowl-shadow-color);
- }
-}
-.jGrowl-notification .jGrowl-header {
- font-weight: bold;
-}
-.jGrowl-notification .jGrowl-close {
- float: right;
- text-align: right;
- margin: 0;
- padding: 0;
- background: none;
- color: inherit;
- border: none;
- cursor: pointer;
- font-size: 1.8rem;
- min-width: auto;
- margin-left: 2rem;
- line-height: 1;
-}
-.jGrowl-notification .jGrowl-close:hover {
- color: inherit;
- background: none;
-}
-.jGrowl-closer {
- width: 400px;
- color: var(--dynamix-jgrowl-close-text-color);
- background-color: var(--dynamix-jgrowl-close-bg-color);
- opacity: 0.96;
- border: 2px solid var(--dynamix-jgrowl-close-border-color);
- margin: 5px 0;
- padding: 4px 0;
- cursor: pointer;
- font-weight: bold;
- text-align: center;
- border-radius: 6px;
-}
-.jGrowl-closer.top {
- position: fixed;
- top: 130px;
-}
-#jGrowl {
- pointer-events: none !important;
-}
-.jGrowl-notification:first-of-type {
- pointer-events: none !important;
-}
-.jGrowl-notification {
- pointer-events: all !important;
-}
-.jGrowl-closer.top {
- pointer-events: all !important;
-}
-.jGrowl-closer.bottom {
- pointer-events: all !important;
-}
-@media print {
- .jGrowl {
- display: none;
- }
-}
-
#sb-info-inner,
#sb-loading-inner,
div.sb-message {
@@ -1507,18 +1360,6 @@ div.icon-zip {
* @see https://caniuse.com/?search=nesting
*/
.Theme--sidebar {
- .jGrowl.top-left {
- top: 90px;
- }
- .jGrowl.top-right {
- top: 90px;
- }
- .jGrowl.center {
- top: 90px;
- }
- .jGrowl-notification {
- min-height: 4rem;
- }
table.tablesorter thead tr th {
font-size: 1.3rem;
padding: 5px 20px 5px 6px;
diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker
index 6a9719d35..13632d0af 100755
--- a/etc/rc.d/rc.docker
+++ b/etc/rc.d/rc.docker
@@ -10,7 +10,7 @@
# VMs, bare metal, OpenStack clusters, public clouds and more.
#
# LimeTech - modified for Unraid OS
-# Bergware - modified for Unraid OS, January 2025
+# Bergware - modified for Unraid OS, May 2025
DAEMON="Docker daemon"
UNSHARE="/usr/bin/unshare"
@@ -35,6 +35,22 @@ TMP=/var/tmp/network.tmp
# run & log functions
. /etc/rc.d/rc.runlog
+# return interface index
+index(){
+ cat $SYSTEM/$1/ifindex 2>/dev/null
+}
+
+# return active interface
+active(){
+ if [[ -e $SYSTEM/${1/eth/br} ]]; then
+ echo ${1/eth/br}
+ elif [[ -e $SYSTEM/${1/eth/bond} ]]; then
+ echo ${1/eth/bond}
+ else
+ echo $1
+ fi
+}
+
# wait for interface to go up
carrier(){
local n e
@@ -49,20 +65,13 @@ carrier(){
# initialize docker settings
docker_read_options(){
# determine active port name
- [[ -e $SYSTEM/bond0 ]] && PORT=bond0 || PORT=eth0
- [[ -e $SYSTEM/br0 ]] && PORT=br0
+ PORT=$(active eth0)
[[ ! $(carrier $PORT) && $(carrier wlan0 1) ]] && PORT=wlan0
# Set defaults used by the docker daemon
if [[ -f $DOCKER_CFG ]]; then
for NIC in $NICS; do
- if [[ ${NIC:0:3} == eth ]]; then
- if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
- NIC=${NIC/eth/br}
- elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
- NIC=${NIC/eth/bond}
- fi
- fi
+ [[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
CFG=($(grep -Pom2 "_SUBNET_|_${NIC^^}(_[0-9]+)?=" $DOCKER_CFG))
if [[ ${CFG[0]} == _SUBNET_ && -z ${CFG[1]} ]]; then
# interface has changed, update configuration
@@ -103,7 +112,7 @@ docker_read_options(){
[[ -n $MTU && $MTU -ne 1500 ]] && DOCKER_OPTS="--mtu=$MTU $DOCKER_OPTS"
# Enable IPv6 for docker bridge network
- if [[ -n $(ip -6 route show default dev $PORT) ]]; then
+ if [[ -n $(ip -6 route show to default dev $PORT) ]]; then
DOCKER0='fd17::/64'
DOCKER_OPTS="--ipv6 --fixed-cidr-v6=$DOCKER0 $DOCKER_OPTS"
IPV6_FORWARD=${IPV6_FORWARD:=accept}
@@ -165,6 +174,8 @@ driver(){
ATTACH='macvlan'
MODE='bridge'
fi
+ # wlan0 has forced ipvlan
+ [[ $1 == wlan && $2 == forced ]] && ATTACH=ipvlan
}
# Custom networks
@@ -172,6 +183,11 @@ network(){
docker network ls --filter driver="$1" --format='{{.Name}}' 2>/dev/null | grep -P "^[a-z]+$2(\$|\.)" | tr '\n' ' '
}
+# Does the ipv4 address exist?
+ipv4_exist(){
+ ip -4 -br addr show to $2 dev $1 | awk '{print $3;exit}'
+}
+
# Is container running?
container_running(){
local CONTAINER
@@ -213,9 +229,9 @@ container_add_route(){
local NET=${CT[1]#*[}
local LAN=${NET%:*}
if [[ $PID -gt 0 && "eth0 br0 bond0 wlan0" =~ $LAN ]]; then
- local THISIP=$(ip -4 -br addr show dev $LAN scope global | awk '{print $3;exit}')
+ local THISIP=$(ip -4 -br addr show scope global primary dev $LAN | awk '{print $3;exit}')
for CFG in /etc/wireguard/wg*.cfg ; do
- local NETWORK=$(ip -4 show route dev $LAN $THISIP | awk '{print $1;exit}')
+ local NETWORK=$(ip -4 route show to $THISIP dev $LAN | awk '{print $1;exit}')
[[ -n $NETWORK ]] && nsenter -n -t $PID ip -4 route add $NETWORK via ${THISIP%/*} dev $LAN 2>/dev/null
done
fi
@@ -246,8 +262,7 @@ docker_network_start(){
if ! docker_running; then return 1; fi
# get container settings for custom networks to reconnect later
declare -A NETRESTORE CTRESTORE
- CONTAINERS=$(docker container ls -a --format='{{.Names}}' | tr '\n' ' ')
- for CONTAINER in $CONTAINERS; do
+ for CONTAINER in $(docker container ls -a --format='{{.Names}}'); do
# the file case (due to fat32) might be different so use find to match
XMLFILE=$(find /boot/config/plugins/dockerMan/templates-user -maxdepth 1 -iname my-${CONTAINER}.xml)
if [[ -n $XMLFILE ]]; then
@@ -255,13 +270,7 @@ docker_network_start(){
MAIN=
# update custom network reference (if changed)
for NIC in $NICS; do
- if [[ ${NIC:0:3} == eth ]]; then
- if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
- NIC=${NIC/eth/br}
- elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
- NIC=${NIC/eth/bond}
- fi
- fi
+ [[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
X=${NIC//[^0-9]/}
REF=$(grep -Pom1 "\K(br|bond|eth|wlan)$X" $XMLFILE)
[[ $X == 0 && ! $(carrier $NIC 1) ]] && continue
@@ -307,24 +316,18 @@ docker_network_start(){
done
# detach custom networks
for NIC in $NICS; do
- if [[ ${NIC:0:3} == eth ]]; then
- if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
- NIC=${NIC/eth/br}
- elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
- NIC=${NIC/eth/bond}
- fi
- fi
+ [[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
X=${NIC//[^0-9]/}
driver ${NIC//[0-9]/}
for NETWORK in $(network $DETACH $X); do
- [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
+ [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
done
# get existing custom networks
for NETWORK in $(network $ATTACH $X); do
if [[ $STOCK =~ ${NETWORK%%[0-9]*} ]]; then
- [[ $EXCLUDE =~ "$NETWORK " || ! $ACTIVE =~ "$NETWORK " ]] && docker network rm $NETWORK >/dev/null
+ [[ $EXCLUDE =~ "$NETWORK " || ! $ACTIVE =~ "$NETWORK " ]] && docker network rm $NETWORK &>/dev/null
else
- [[ $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
+ [[ $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
fi
done
NETWORKS=$(network $ATTACH $X)
@@ -336,27 +339,27 @@ docker_network_start(){
AUTO=${NETWORK/./_}
AUTO=DOCKER_AUTO_${AUTO^^}
if [[ ${!AUTO} == no ]]; then
- [[ $NETWORKS =~ "$NETWORK " ]] && docker network rm $NETWORK >/dev/null
+ [[ $NETWORKS =~ "$NETWORK " ]] && docker network rm $NETWORK &>/dev/null
continue
fi
# add auto defined networks
SUBNET=; GATEWAY=; SERVER=; RANGE=;
- [[ -z ${!AUTO} || ${!AUTO} =~ "4" ]] && IPV4=$(ip -4 -br addr show $NETWORK scope global | awk '{print $3;exit}') || IPV4=
+ [[ -z ${!AUTO} || ${!AUTO} =~ "4" ]] && IPV4=$(ip -4 -br addr show scope global primary dev $NETWORK | awk '{print $3;exit}') || IPV4=
if [[ -n $IPV4 ]]; then
- SUBNET=$(ip -4 route show $IPV4 dev $NETWORK | awk '{print $1;exit}')
+ SUBNET=$(ip -4 route show to $IPV4 dev $NETWORK | awk '{print $1;exit}')
SERVER=${IPV4%/*}
DHCP=${NETWORK/./_}
DHCP=DOCKER_DHCP_${DHCP^^}
RANGE=${!DHCP}
- GATEWAY=$(ip -4 route show default dev $NETWORK | awk '{print $3;exit}')
+ GATEWAY=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}')
fi
SUBNET6=; GATEWAY6=; SERVER6=;
- [[ -z ${!AUTO} || ${!AUTO} =~ "6" ]] && IPV6=$(ip -6 -br addr show $NETWORK scope global -temporary -deprecated | awk '{print $3;exit}') || IPV6=
+ # get IPv6 address - ignore any /128 networks
+ [[ -z ${!AUTO} || ${!AUTO} =~ "6" ]] && IPV6=$(ip -6 -br addr show scope global primary -deprecated dev $NETWORK | awk -v RS='[[:space:]]+' '(NR>2){print}' | grep -Pvm1 '^.+/128|^$') || IPV6=
if [[ -n $IPV6 ]]; then
- # get IPV6 subnet, preset to /64 if single host address is given
- [[ ${IPV6#*/} == 128 ]] && SUBNET6=$(echo $IPV6 | sed -r 's/^([^:]+):([^:]+):([^:]+):([^:]+).*$/\1:\2:\3:\4::\/64/') || SUBNET6=$(ip -6 route show $IPV6 dev $NETWORK | awk '{print $1;exit}')
+ SUBNET6=$(ip -6 route show to $IPV6 dev $NETWORK | awk '{print $1;exit}')
SERVER6=${IPV6%/*}
- GATEWAY6=$(ip -6 route show default dev $NETWORK | awk '{print $3;exit}')
+ GATEWAY6=$(ip -6 route show to default dev $NETWORK | awk '{print $3;exit}')
# replace link local address for first address in subnet
[[ ${GATEWAY6:0:4} == fe80 ]] && GATEWAY6=${SUBNET6%/*}1
fi
@@ -378,38 +381,7 @@ docker_network_start(){
GATEWAY6=${!GATEWAY6}
SERVER6=;
fi
- # custom network already existing and changed?
- if [[ $NETWORKS =~ "$NETWORK " ]]; then
- UPDATE=;
- SUBNETS=($(docker network inspect --format='{{range .IPAM.Config}}{{.Subnet}} {{end}}' $NETWORK 2>/dev/null))
- RANGES=($(docker network inspect --format='{{range .IPAM.Config}}{{.IPRange}} {{end}}' $NETWORK 2>/dev/null))
- GATEWAYS=($(docker network inspect --format='{{range .IPAM.Config}}{{.Gateway}} {{end}}' $NETWORK 2>/dev/null))
- SERVERS=($(docker network inspect --format='{{range .IPAM.Config}}{{range $IPAddr := .AuxiliaryAddresses}}{{$IPAddr}}{{end}} {{end}}' $NETWORK 2>/dev/null))
- # distribute ipv4 and ipv6 assignments
- [[ ${SUBNETS[0]} =~ '.' ]] && SUBNET0=${SUBNETS[0]} || SUBNET1=${SUBNETS[0]}
- [[ -n ${SUBNETS[1]} && ${SUBNETS[1]} =~ '.' ]] && SUBNET0=${SUBNETS[1]} || SUBNET1=${SUBNETS[1]}
- [[ ${RANGES[0]} =~ '.' ]] && RANGE0=${RANGES[0]} || RANGE1=${RANGES[0]}
- [[ -n ${RANGES[1]} && ${RANGES[1]} =~ '.' ]] && RANGE0=${RANGES[1]} || RANGE1=${RANGES[1]}
- [[ ${GATEWAYS[0]} =~ '.' ]] && GATEWAY0=${GATEWAYS[0]} || GATEWAY1=${GATEWAYS[0]}
- [[ -n ${GATEWAYS[1]} && ${GATEWAYS[1]} =~ '.' ]] && GATEWAY0=${GATEWAYS[1]} || GATEWAY1=${GATEWAYS[1]}
- [[ ${SERVERS[0]} =~ '.' ]] && SERVER0=${SERVERS[0]} || SERVER1=${SERVERS[0]}
- [[ -n ${SERVERS[1]} && ${SERVERS[1]} =~ '.' ]] && SERVER0=${SERVERS[1]} || SERVER1=${SERVERS[1]}
- # check for changes
- [[ $SUBNET != $SUBNET0 || $SUBNET6 != $SUBNET1 ]] && UPDATE=1
- [[ $RANGE != $RANGE0 ]] && UPDATE=1
- [[ (-n $GATEWAY && $GATEWAY != $GATEWAY0) || (-n $GATEWAY6 && $GATEWAY6 != $GATEWAY1) ]] && UPDATE=1
- [[ (-n $SERVER && $SERVER != $SERVER0) || (-n $SERVER6 && $SERVER6 != $SERVER1) ]] && UPDATE=1
- if [[ -z $UPDATE ]]; then
- # no changes, ignore
- SUBNET=; SUBNET6=;
- else
- # changed, remove first
- docker network rm $NETWORK >/dev/null
- fi
- fi
# set parameters for custom network creation
- N4=$SUBNET; R4=$RANGE;
- N6=$SUBNET6;
[[ -n $SUBNET && -n $GATEWAY ]] && GATEWAY="--gateway=$GATEWAY" || GATEWAY=;
[[ -n $SUBNET && -n $SERVER ]] && SERVER="--aux-address=server=$SERVER" || SERVER=;
[[ -n $SUBNET && -n $RANGE ]] && RANGE="--ip-range=$RANGE" || RANGE=;
@@ -419,15 +391,15 @@ docker_network_start(){
[[ -n $SUBNET6 ]] && SUBNET6="--ipv6 --subnet=$SUBNET6"
if [[ -n $SUBNET || -n $SUBNET6 ]]; then
TYPE=${NETWORK//[0-9.]/}
- driver $TYPE
- if [[ $TYPE == br ]]; then
+ driver $TYPE forced
+ if [[ $TYPE == br || $TYPE == wlan ]]; then
VHOST=$NETWORK
- elif [[ $TYPE == wlan ]]; then
- VHOST=$NETWORK
- ATTACH=ipvlan
else
- [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]] && VHOST=vhost${NETWORK//[^0-9.]/} || VHOST=$NETWORK
+ [[ -n $IPV4 && $DOCKER_ALLOW_ACCESS == yes ]] && VHOST=vhost${NETWORK//[^0-9.]/} || VHOST=$NETWORK
fi
+ # delete and recreate unconditionally
+ log "Processing... $NETWORK"
+ docker network rm $NETWORK &>/dev/null
docker network create -d $ATTACH $SUBNET $GATEWAY $SERVER $RANGE $SUBNET6 $GATEWAY6 $SERVER6 -o parent=$VHOST $NETWORK | xargs docker network inspect -f "created network $ATTACH {{.Name}} with subnets: {{range .IPAM.Config}}{{.Subnet}}; {{end}}" 2>/dev/null | log
# connect containers to this new network
for CONNECT in ${NETRESTORE[$NETWORK]}; do
@@ -448,57 +420,50 @@ docker_network_start(){
# hack to let containers talk to host
if [[ $TYPE == br ]]; then
LINK=shim-$NETWORK
- GW=($(ip -4 route show default dev $NETWORK | awk '{print $3,$5;exit}'))
if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then
- # create shim interface and copy parent IPv4 address to shim interface
+ IPV4="$IPV4 metric $((1000 - 1 + $(index $NETWORK)))"
+ # create shim interface
[[ -e $SYSTEM/$LINK ]] || run ip link add link $NETWORK name $LINK type $ATTACH mode $MODE
- run ip addr flush dev $LINK scope global
- run ip -4 addr add $IPV4 dev $LINK metric 0
# disable IPv6 on shim interface
echo 1 >$CONF6/$LINK/disable_ipv6
+ run ip -6 addr flush dev $LINK
+ # copy parent IPv4 address to shim interface
+ run ip addr add $IPV4 dev $LINK
run ip link set $LINK up
- if [[ -n $GW ]]; then
- if [[ -z ${GW[1]} ]]; then
- METRIC=1
- METRICS=$(ip -4 route show default | grep -Po 'metric \K\d+')
- while [[ " $METRICS " =~ " $METRIC " ]]; do ((METRIC++)); done
- # update existing route to avoid conflict with shim route
- run ip -4 route del default via $GW dev $NETWORK
- run ip -4 route add default via $GW dev $NETWORK metric $METRIC
- fi
- run ip -4 route add default via $GW dev $LINK metric 0
- fi
log "created network $LINK for host access"
elif [[ -e $SYSTEM/$LINK ]]; then
# remove shim interface
- [[ -n $GW ]] && ip -4 route del default via $GW dev $LINK
run ip addr flush dev $LINK
run ip link set $LINK down
run ip link del $LINK
fi
- elif [[ $TYPE != wlan ]]; then
- if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then
- run ip addr flush dev $VHOST scope global
- # copy IPv4 address to vhost interface
- run ip -4 addr add $IPV4 dev $VHOST metric 0
- log "prepared network $VHOST for host access"
+ else
+ if [[ $TYPE == wlan ]]; then
+ VHOST=shim-$NETWORK
+ INDEX=3000
else
VHOST=vhost${NETWORK//[^0-9.]/}
- if [[ -e $SYSTEM/$VHOST ]]; then
- # remove IP addresses of vhost
- run ip addr flush dev $VHOST scope global
- # remove routing of vhost
- run ip -4 route flush dev $VHOST
- run ip -6 route flush dev $VHOST
- fi
+ INDEX=1000
+ fi
+ INDEX=$(($INDEX - 1 + $(index $NETWORK)))
+ if [[ -n $IPV4 && $DOCKER_ALLOW_ACCESS == yes ]]; then
+ # disable IPv6 on vhost interface
+ echo 1 >$CONF6/$VHOST/disable_ipv6
+ run ip -6 addr flush dev $VHOST
+ # copy parent IPv4 address to vhost interface
+ [[ -z $(ipv4_exist $VHOST ${IPV4%/*}) ]] && run ip addr add $IPV4 metric $INDEX dev $VHOST
+ log "created network $VHOST for host access"
+ elif [[ -n $IPV4 && -e $SYSTEM/$VHOST && -n $(ipv4_exist $VHOST ${IPV4%/*}) ]]; then
+ # remove parent IPv4 address from vhost interface
+ run ip addr del $IPV4 metric $INDEX dev $VHOST
fi
fi
fi
done
# create IPv6 forward accept rule
if [[ $IPV6_FORWARD == accept ]]; then
- log "creating forward accept rule for IPv6 network"
ip6tables -P FORWARD ACCEPT
+ log "created forward accept rule for IPv6 network"
fi
log "Network started."
}
@@ -506,33 +471,31 @@ docker_network_start(){
docker_network_stop(){
log "Stopping network..."
if ! docker_running; then return 1; fi
+ # Read docker configuration file
+ [[ -f $DOCKER_CFG ]] && . $DOCKER_CFG
for NIC in $NICS; do
- if [[ ${NIC:0:3} == eth ]]; then
- if [[ -e $SYSTEM/${NIC/eth/br} ]]; then
- NIC=${NIC/eth/br}
- elif [[ -e $SYSTEM/${NIC/eth/bond} ]]; then
- NIC=${NIC/eth/bond}
- fi
- fi
- driver ${NIC//[0-9]/}
+ [[ ${NIC:0:3} == eth ]] && NIC=$(active $NIC)
+ driver ${NIC//[0-9]/} forced
for NETWORK in $(network $ATTACH ${NIC//[^0-9]/}); do
- [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK >/dev/null
+ [[ $STOCK =~ ${NETWORK%%[0-9]*} || $DOCKER_USER_NETWORKS != preserve ]] && docker network rm $NETWORK &>/dev/null
TYPE=${NETWORK//[0-9.]/}
if [[ $TYPE == br ]]; then
LINK=shim-$NETWORK
if [[ -e $SYSTEM/$LINK ]]; then
- GW=$(ip -4 route show default dev $LINK | awk '{print $3;exit}')
- [[ -n $GW ]] && run ip -4 route del default via $GW dev $LINK
run ip addr flush dev $LINK
run ip link set $LINK down
run ip link del $LINK
fi
- else
+ elif [[ $TYPE != wlan ]]; then
VHOST=vhost${NETWORK//[^0-9.]/}
[[ -e $SYSTEM/$VHOST ]] && run ip addr flush dev $VHOST
fi
done
done
+ if [[ -e $SYSTEM/shim-wlan0 ]]; then
+ IPV4=$(ip -4 -br addr show scope global primary dev shim-wlan0 | awk '{print $3,$4,$5;exit}')
+ [[ -n $IPV4 ]] && run ip addr del $IPV4 dev shim-wlan0
+ fi
log "Network stopped."
}
@@ -549,7 +512,7 @@ docker_container_start(){
if [[ $OUT =~ "Error:" ]]; then
log "$CONTAINER: $OUT" &
else
- run container_add_route $CONTAINER
+ container_add_route $CONTAINER
log "$CONTAINER: started successfully!" &
if [[ $WAIT -gt 0 ]]; then
log "$CONTAINER: wait $WAIT seconds" &
@@ -650,7 +613,6 @@ case "$1" in
docker_service_start
docker_network_start
docker_container_start &>/dev/null &
- disown
;;
'stop')
docker_container_stop
@@ -670,7 +632,6 @@ case "$1" in
docker_service_start
docker_network_start
docker_container_start &>/dev/null &
- disown
;;
'status')
docker_status
diff --git a/etc/rc.d/rc.inet1 b/etc/rc.d/rc.inet1
index 6670cbbfc..835b8dd3e 100755
--- a/etc/rc.d/rc.inet1
+++ b/etc/rc.d/rc.inet1
@@ -7,7 +7,7 @@
# @(#)/etc/rc.d/rc.inet1 10.2 Sun Jul 24 12:45:56 PDT 2005 (pjv)
# LimeTech - modified for Unraid OS
-# Bergware - modified for Unraid OS, February 2025
+# Bergware - modified for Unraid OS, May 2025
# Adapted by Bergware for use in Unraid OS - April 2016
# - improved interface configuration
@@ -81,6 +81,9 @@
# - added metric value to interface IP assignment
# - fixed DNS entries get removed when configuring interface other then eth0
+# Adapted by Bergware for use in Unraid OS - May 2025
+# - improved metric value to interface IP assignment
+
###########
# LOGGING #
###########
@@ -122,22 +125,18 @@ done
# LOOPBACK FUNCTIONS #
######################
-# function to bring up loopback interface
+# bring up loopback interface
lo_up(){
if [[ -e $SYSTEM/lo ]]; then
- if ! ip -4 addr show lo | grep -qw 'inet'; then
- run ip -4 addr add 127.0.0.1/8 dev lo
- fi
- if ! ip -6 addr show lo | grep -qw 'inet6'; then
- run ip -6 addr add ::1/128 dev lo
- fi
+ [[ -z $(ip -4 -br addr show lo | awk '{print $3;exit}') ]] && run ip -4 addr add 127.0.0.1/8 dev lo
+ [[ -z $(ip -6 -br addr show lo | awk '{print $3;exit}') ]] && run ip -6 addr add ::1/128 dev lo
run ip link set lo up
else
[[ $DEBUG_ETH_UP == yes ]] && log "interface lo not present, can't bring up"
fi
}
-# function to take down loopback interface
+# take down loopback interface
lo_down(){
if [[ -e $SYSTEM/lo ]]; then
run ip link set lo down
@@ -150,12 +149,17 @@ lo_down(){
# INTERFACE FUNCTIONS #
#######################
-# function to get link mtu size
+# return interface index
+index(){
+ cat $SYSTEM/$1/ifindex 2>/dev/null
+}
+
+# get link mtu size
get_mtu(){
ip link show $1 | grep -Po 'mtu \K\d+'
}
-# function to set/reset link mtu size
+# set/reset link mtu size
set_mtu(){
if [[ -n ${MTU[$i]} ]]; then
# set MTU to specified value
@@ -166,8 +170,8 @@ set_mtu(){
fi
}
-# function to wait for carrier of interface
-carrier_up(){
+# wait for carrier of interface
+carrier(){
local n
for n in {1..10}; do
[[ $(cat $SYSTEM/$1/carrier 2>/dev/null) == 1 ]] && return 0 || sleep 1
@@ -175,7 +179,7 @@ carrier_up(){
return 1
}
-# function to create bond interface
+# create bond interface
bond_up(){
[[ -d /proc/net/bonding ]] || modprobe bonding mode=${BONDING_MODE[$i]} miimon=${BONDING_MIIMON[$i]}
run ip link add name ${BONDNAME[$i]} type bond mode ${BONDING_MODE[$i]} miimon ${BONDING_MIIMON[$i]}
@@ -194,7 +198,7 @@ bond_up(){
[[ -n $PRIMARY ]] && run ip link set name ${BONDNAME[$i]} type bond primary $PRIMARY
}
-# function to delete bond interface
+# delete bond interface
bond_down(){
if [[ -e $SYSTEM/${BONDNAME[$i]} ]]; then
# loop thru attached interfaces in bond
@@ -208,7 +212,7 @@ bond_down(){
fi
}
-# function to create bridge interface
+# create bridge interface
br_up(){
for ((j=0;j<${VLANS[$i]:-1};j++)); do
[[ $j -eq 0 ]] && BRIDGE=${BRNAME[$i]} || BRIDGE=${BRNAME[$i]}.${VLANID[$i,$j]}
@@ -235,7 +239,7 @@ br_up(){
done
}
-# function to delete bridge interface
+# delete bridge interface
br_down(){
for ((j=0;j<${VLANS[$i]:-1};j++)); do
# loop thru main bridge and bridge VLAN interfaces
@@ -253,7 +257,7 @@ br_down(){
done
}
-# function to create VLAN interfaces
+# create VLAN interfaces
vlan_up(){
for PORT in ${BRNICS[$i]:-${IFNAME[$i]}}; do
for ((j=1;j<${VLANS[$i]};j++)); do
@@ -265,7 +269,7 @@ vlan_up(){
done
}
-# function to delete VLAN interfaces
+# delete VLAN interfaces
vlan_down(){
for PORT in ${BRNICS[$i]:-${IFNAME[$i]}}; do
for VLAN in $(ls --indicator-style=none $SYSTEM | grep -Po "$PORT\.\d+"); do
@@ -275,7 +279,7 @@ vlan_down(){
done
}
-# function to create macvtap interfaces
+# create macvtap interfaces
macvtap_up(){
PARENT=${IFNAME[$i]}
[[ -n ${BONDNICS[$i]} ]] && PARENT=${BONDNAME[$i]}
@@ -283,16 +287,18 @@ macvtap_up(){
MAC=$(echo $(hostname)-$VTAP | md5sum | sed -r 's/^(..)(..)(..)(..)(..).*$/02:\1:\2:\3:\4:\5/')
run ip link add link $PARENT name $VTAP address $MAC type macvtap mode bridge
set_mtu $VTAP
+ echo 1 >$CONF6/$VTAP/disable_ipv6
run ip link set $VTAP up
for ((j=1;j<${VLANS[$i]:-0};j++)); do
VLAN=${VLANID[$i,$j]}
run ip link add link $PARENT.$VLAN name $VTAP.$VLAN address $MAC type macvtap mode bridge
set_mtu $VTAP.$VLAN
+ echo 1 >$CONF6/$VTAP.$VLAN/disable_ipv6
run ip link set $VTAP.$VLAN up
done
}
-# function to delete macvtap interfaces
+# delete macvtap interfaces
macvtap_down(){
PARENT=${IFNAME[$i]}
[[ -n ${BONDNICS[$i]} ]] && PARENT=${BONDNAME[$i]}
@@ -308,26 +314,26 @@ macvtap_down(){
run ip link del $VTAP
}
-# function to enable/disable ipv6 protocol per interface
+# enable/disable ipv6 protocol per interface
ipv6_up(){
[[ -d $CONF6/${IFACE/$1/$2} ]] && echo $4 >$CONF6/${IFACE/$1/$2}/disable_ipv6
[[ -d $CONF6/${IFACE/$1/$3} ]] && echo $4 >$CONF6/${IFACE/$1/$3}/disable_ipv6
}
-# function to enable/disable ipv6 assignment per interface
+# enable/disable ipv6 assignment per interface
ipv6_ra(){
echo $2 >$CONF6/$1/accept_ra
echo $2 >$CONF6/$1/accept_ra_defrtr
echo $3 >$CONF6/$1/autoconf
}
-# function to enable/disable ipv6 assignment per interface
+# enable/disable ipv6 assignment per interface
ipv6_conf(){
[[ -d $CONF6/${IFACE/$1/$2} ]] && ipv6_ra ${IFACE/$1/$2} $4 $5
[[ -d $CONF6/${IFACE/$1/$3} ]] && ipv6_ra ${IFACE/$1/$3} $4 $5
}
-# function to enable/disable ipv6 assignment per interface
+# enable/disable ipv6 assignment per interface
ipv6_addr(){
[[ -d $CONF6/$IFACE ]] && ipv6_ra $IFACE $1 $2
[[ -d $CONF6/$VHOST ]] && ipv6_ra $VHOST $1 $2
@@ -342,7 +348,7 @@ ipv6_addr(){
sleep 1
}
-# function to assign IP address
+# assign IP address
ipaddr_up(){
if [[ -z $RENEW ]]; then
# disable IPv6 per interface when IPv4 only
@@ -371,7 +377,7 @@ ipaddr_up(){
[[ $IP == ipv4 ]] && DHCP_OPTIONS="$DHCP_OPTIONS -4"
[[ $IP == ipv6 ]] && DHCP_OPTIONS="$DHCP_OPTIONS -6"
[[ $IP != ipv4 && -n $PRIV6 && -d $CONF6/$IFACE ]] && echo $PRIV6 >$CONF6/$IFACE/use_tempaddr
- if carrier_up $IFACE; then
+ if carrier $IFACE; then
# interface is UP
log "interface $IFACE is UP, polling up to 60 sec for DHCP $IP server"
if ! run timeout 60 dhcpcd -w $DHCP_OPTIONS $IFACE; then
@@ -385,21 +391,23 @@ ipaddr_up(){
fi
elif [[ $DHCP == no ]]; then
# bring up interface using static IP address
- if carrier_up $IFACE; then STATE="UP"; else STATE="DOWN"; fi
+ if carrier $IFACE; then STATE="UP"; else STATE="DOWN"; fi
log "interface $IFACE is $STATE, setting static $IP address"
ipv6_addr 0 1
+ INDEX=$(index $IFACE)
+ INDEX=$((1000 + ${INDEX:-$(($(index * | sort -n | tail -1) + 1))}))
if [[ $IP != ipv6 ]]; then
[[ $j -eq 0 ]] && ADDR=${IPADDR[$i]} || ADDR=${IPADDR[$i,$j]}
if [[ -n $ADDR ]]; then
[[ $j -eq 0 ]] && MASK=${NETMASK[$i]} || MASK=${NETMASK[$i,$j]}
- [[ -n $MASK ]] && run ip -4 addr add $(unzero $ADDR)/$MASK dev $IFACE metric ${DHCP_METRIC:-1}
+ [[ -n $MASK ]] && run ip -4 addr add $(unzero $ADDR)/$MASK metric $INDEX dev $IFACE
fi
fi
if [[ $IP != ipv4 ]]; then
[[ $j -eq 0 ]] && ADDR6=${IPADDR6[$i]} || ADDR6=${IPADDR6[$i,$j]}
if [[ -n $ADDR6 ]]; then
[[ $j -eq 0 ]] && MASK6=${NETMASK6[$i]} || MASK6=${NETMASK6[$i,$j]}
- [[ -n $MASK6 ]] && run ip -6 addr add $(unzero6 $ADDR6)/$MASK6 dev $IFACE metric ${DHCP_METRIC:-1}
+ [[ -n $MASK6 ]] && run ip -6 addr add $(unzero6 $ADDR6)/$MASK6 metric $INDEX dev $IFACE
[[ -n $PRIV6 && -d $CONF6/$IFACE ]] && echo 0 >$CONF6/$IFACE/use_tempaddr
fi
fi
@@ -410,7 +418,7 @@ ipaddr_up(){
fi
}
-# function to release IP addresses and routes
+# release IP addresses and routes
ipaddr_conf(){
if [[ -e $SYSTEM/${IFACE/$1/$2} ]]; then
run ip -$4 addr flush dev ${IFACE/$1/$2}
@@ -422,7 +430,7 @@ ipaddr_conf(){
fi
}
-# function to release IP addresses and routes
+# release IP addresses and routes
ipaddr_flush(){
run ip -$1 addr flush dev $IFACE
run ip -$1 route flush dev $IFACE
@@ -436,7 +444,7 @@ ipaddr_flush(){
fi
}
-# function to release IP addresses and routes
+# release IP addresses and routes
ipaddr_down(){
if [[ $DHCP == yes ]]; then
DHCP_OPTIONS="-q -k"
@@ -453,7 +461,7 @@ ipaddr_down(){
fi
}
-# function to bring up network interface
+# bring up network interface
if_up(){
# set index of INTERFACE in array
i=0
@@ -555,7 +563,7 @@ if_up(){
done
}
-# function to take down network interface
+# take down network interface
if_down(){
# set index of INTERFACE in array
i=0
@@ -612,7 +620,7 @@ if_down(){
# GATEWAY FUNCTIONS #
#####################
-# function to add default gateway per interface
+# add default gateway per interface
gateway_up(){
for GW in ${GATEWAY[@]}; do
[[ -z $GW ]] && continue
@@ -623,7 +631,7 @@ gateway_up(){
IP=${PROTOCOL[$x]:-ipv4}
AD=${METRIC[$x]}
[[ -n $AD ]] && AD="metric $AD"
- EXIST=$(ip -4 route show default via $(unzero $GW) dev $DEV | grep "$AD ")
+ EXIST=$(ip -4 route show to default via $(unzero $GW) dev $DEV | grep "$AD ")
[[ $IP != ipv6 && -z $EXIST ]] && run ip -4 route add default via $(unzero $GW) dev $DEV $AD
done
for GW6 in ${GATEWAY6[@]}; do
@@ -635,12 +643,12 @@ gateway_up(){
IP=${PROTOCOL[$x]:-ipv4}
AD6=${METRIC6[$x]}
[[ -n $AD6 ]] && AD6="metric $AD6"
- EXIST=$(ip -6 route show default via $(unzero6 $GW6) dev $DEV | grep "$AD6 ")
+ EXIST=$(ip -6 route show to default via $(unzero6 $GW6) dev $DEV | grep "$AD6 ")
[[ $IP != ipv4 && -z $EXIST ]] && run ip -6 route add default via $(unzero6 $GW6) dev $DEV $AD6
done
}
-# function to delete default gateway per interface
+# delete default gateway per interface
gateway_down(){
for GW in ${GATEWAY[@]}; do
[[ -z $GW ]] && continue
@@ -649,7 +657,7 @@ gateway_down(){
i=(${x/,/ })
[[ -z ${i[1]} ]] && DEV=${IFNAME[$i]} || DEV=${IFNAME[$i]}.${VLANID[$x]}
IP=${PROTOCOL[$x]:-ipv4}
- EXIST=$(ip -4 route show default dev $DEV)
+ EXIST=$(ip -4 route show to default dev $DEV)
[[ $IP != ipv6 && -n $EXIST ]] && run ip -4 route flush default dev $DEV
done
for GW6 in ${GATEWAY6[@]}; do
@@ -659,12 +667,12 @@ gateway_down(){
i=(${x/,/ })
[[ -z ${i[1]} ]] && DEV=${IFNAME[$i]} || DEV=${IFNAME[$i]}.${VLANID[$x]}
IP=${PROTOCOL[$x]:-ipv4}
- EXIST=$(ip -6 route show default dev $DEV)
+ EXIST=$(ip -6 route show to default dev $DEV)
[[ $IP != ipv4 && -n $EXIST ]] && run ip -6 route flush default dev $DEV
done
}
-# function to start network
+# start network
start(){
lo_up
for INTERFACE in ${IFNAME[@]}; do
@@ -673,7 +681,7 @@ start(){
gateway_up
}
-# function to stop network
+# stop network
stop(){
gateway_down
for INTERFACE in ${IFNAME[@]}; do
@@ -682,7 +690,7 @@ stop(){
lo_down
}
-# function to show network status
+# show network status
status(){
echo "INTERFACE STATE INFORMATION"
echo "========================================================================"
@@ -693,38 +701,38 @@ status(){
# STATIC ROUTE FUNCTIONS #
##########################
-# function to add static route
+# add static route
route_up(){
[[ -n $3 ]] && METRIC="metric $3" || METRIC=
if [[ $2 == default ]]; then
# determine IP protocol & optional device
[[ -n ${1##*:*} ]] && IP=-4 || IP=-6
[[ -z ${1##*-*} ]] && DEV="dev ${1#*-}" || DEV=
- EXIST=$(ip $IP route show default via ${1%-*} $DEV | grep "$METRIC ")
+ EXIST=$(ip $IP route show to default via ${1%-*} $DEV | grep "$METRIC ")
[[ -z $EXIST ]] && run ip $IP route add default via ${1%-*} $DEV $METRIC
elif [[ -n $2 ]]; then
# determine IP protocol & gateway syntax
[[ -n ${2##*:*} ]] && IP=-4 || IP=-6
[[ -e $SYSTEM/$1 ]] && GW="dev $1" || GW="via $1"
- EXIST=$(ip $IP route show $2 $GW | grep "$METRIC ")
+ EXIST=$(ip $IP route show to $2 $GW | grep "$METRIC ")
[[ -z $EXIST ]] && run ip $IP route add $2 $GW $METRIC
fi
}
-# function to delete static route
+# delete static route
route_down(){
[[ -n $3 ]] && METRIC="metric $3" || METRIC=
if [[ $2 == default ]]; then
# determine IP protocol & optional device
[[ -n ${1##*:*} ]] && IP=-4 || IP=-6
[[ -z ${1##*-*} ]] && DEV="dev ${1#*-}" || DEV=
- EXIST=$(ip $IP route show default via ${1%-*} $DEV)
+ EXIST=$(ip $IP route show to default via ${1%-*} $DEV)
[[ -n $EXIST ]] && run ip $IP route del default via ${1%-*} $DEV $METRIC
elif [[ -n $2 ]]; then
# determine IP protocol & gateway syntax
[[ -n ${2##*:*} ]] && IP=-4 || IP=-6
[[ -e $SYSTEM/$1 ]] && GW="dev $1" || GW="via $1"
- EXIST=$(ip $IP route show $2 $GW)
+ EXIST=$(ip $IP route show to $2 $GW)
[[ -n $EXIST ]] && run ip $IP route del $2 $GW $METRIC
fi
}
diff --git a/etc/rc.d/rc.library.source b/etc/rc.d/rc.library.source
index 99861be3b..fb5f5f9d0 100644
--- a/etc/rc.d/rc.library.source
+++ b/etc/rc.d/rc.library.source
@@ -5,7 +5,7 @@
# Library used by nfsd, ntpd, rpc, samba, nginx, sshd, avahidaemon, show_interfaces
#
# Bergware - created for Unraid OS, December 2023
-# Bergware - updated January 2025
+# Bergware - updated May 2025
WIREGUARD="/etc/wireguard"
NETWORK_INI="/var/local/emhttp/network.ini"
@@ -56,8 +56,8 @@ good(){
show(){
case $# in
- 1) ip -br addr show scope global -temporary -deprecated to $1 2>/dev/null | awk '{gsub("@.+","",$1);print $1;exit}' ;;
- 2) ip -br addr show scope global -temporary -deprecated $1 $2 2>/dev/null | awk '{$1=$2="";print;exit}' | sed -r 's/ metric [0-9]+//g' ;;
+ 1) ip -br addr show scope global primary -deprecated to $1 2>/dev/null | awk '{gsub("@.+","",$1);print $1;exit}' ;;
+ 2) ip -br addr show scope global primary -deprecated $1 $2 2>/dev/null | awk '{$1=$2="";print;exit}' | sed -r 's/ metric [0-9]+//g' ;;
esac
}
@@ -160,7 +160,7 @@ check(){
fi
[[ $(ipv $ADDR) == 4 ]] && IPV4=yes || IPV6=yes
done
- done <<< $(ip -br addr show scope global -temporary -deprecated | awk '$1~"^(br|bond|eth|wlan|wg)[0-9]+(.[0-9]+)?" && $3!="" {gsub("@.+","",$1);$2="";print}' | sed -r 's/ metric [0-9]+//g' | sort)
+ done <<< $(ip -br addr show scope global primary -deprecated | awk '$1~"^(br|bond|eth|wlan|wg)[0-9]+(.[0-9]+)?" && $3!="" {gsub("@.+","",$1);$2="";print}' | sed -r 's/ metric [0-9]+//g' | sort)
# add loopback interface
if [[ "smb nfs" =~ "$CALLER" ]]; then
[[ $IPV4 == yes ]] && BIND+=(127.0.0.1)
diff --git a/etc/rc.d/rc.local b/etc/rc.d/rc.local
index af02d607c..25909ceee 100755
--- a/etc/rc.d/rc.local
+++ b/etc/rc.d/rc.local
@@ -80,9 +80,14 @@ fi
/usr/local/emhttp/webGui/scripts/notify smtp-init
/usr/local/emhttp/webGui/scripts/notify cron-init
+# start interface state monitoring
+if [[ -x /usr/local/sbin/monitor_interface ]]; then
+ /usr/local/sbin/monitor_interface &>/dev/null
+fi
+
# start nchan monitoring -> stop all running nchan processes when no subscribers are connected
if [[ -x /usr/local/sbin/monitor_nchan ]]; then
- /usr/local/sbin/monitor_nchan
+ /usr/local/sbin/monitor_nchan &>/dev/null
fi
# First boot following Unraid Server OS update: delete plugin file
@@ -184,7 +189,7 @@ else
if [[ -f "$PRIORITY_PLUGIN_PATH" ]]; then
/usr/local/sbin/plugin install "$PRIORITY_PLUGIN_PATH" | log
fi
- done
+ done
# Install remaining plugins
shopt -s nullglob
for PLUGIN in $CONFIG/plugins/*.plg; do
@@ -238,9 +243,16 @@ if grep -q "$GOTEST2" $CONFIG/go; then
fi
# Invoke the 'go' script
-if [[ -f $CONFIG/go ]]; then
- log "Starting go script"
- fromdos <$CONFIG/go >/var/tmp/go
- chmod +x /var/tmp/go
- /var/tmp/go
+GO=go
+if [[ -f /boot/unraidsafemode ]] || grep -wq unraidsafemode /proc/cmdline; then
+ GO=go.safemode
+fi
+if [[ -f $CONFIG/$GO ]]; then
+ log "Starting $GO script"
+ fromdos <$CONFIG/$GO >/var/tmp/$GO
+ chmod +x /var/tmp/$GO
+ /var/tmp/$GO
+else
+ log "Starting emhttp"
+ /usr/local/sbin/emhttp
fi
diff --git a/etc/rc.d/rc.nginx b/etc/rc.d/rc.nginx
index 0f903a569..c9298385e 100755
--- a/etc/rc.d/rc.nginx
+++ b/etc/rc.d/rc.nginx
@@ -6,7 +6,7 @@
# Written for Slackware Linux by Cherife Li .
# LimeTech - modified for Unraid OS
-# Bergware - modified for Unraid OS, October 2023
+# Bergware - modified for Unraid OS, May 2025
# reference:
# LANNAME 'tower'
@@ -537,12 +537,12 @@ build_ssl(){
# fetch LAN IP address (read management interface eth0)
[[ -e $SYSTEM/bond0 ]] && DEV=bond0 || DEV=eth0
[[ -e $SYSTEM/br0 ]] && DEV=br0
- LANIP=$(ip -4 -br addr show $DEV scope global | sed -r 's/\/[0-9]+//g' | awk '{print $3;exit}')
- LANIP6=$(ip -6 -br addr show $DEV scope global -temporary -deprecated | sed -r 's/\/[0-9]+//g' | awk '{print $3;exit}')
+ LANIP=$(ip -4 -br addr show scope global primary dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ LANIP6=$(ip -6 -br addr show scope global primary -deprecated dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
# try wireless connection if no IP address on interface eth0
- [[ -z $LANIP && -e $SYSTEM/wlan0 ]] && LANIP=$(ip -4 -br addr show wlan0 scope global | sed -r 's/\/[0-9]+//g' | awk '{print $3;exit}')
- [[ -z $LANIP6 && -e $SYSTEM/wlan0 ]] && LANIP6=$(ip -6 -br addr show wlan0 scope global -temporary -deprecated | sed -r 's/\/[0-9]+//g' | awk '{print $3;exit}')
+ [[ -z $LANIP && -e $SYSTEM/wlan0 ]] && LANIP=$(ip -4 -br addr show scope global primary dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ [[ -z $LANIP6 && -e $SYSTEM/wlan0 ]] && LANIP6=$(ip -6 -br addr show scope global primary -deprecated dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
# regenerate self-signed cert if local TLD changes */
SELFCERTPATH=$SSL/certs/${LANNAME}_unraid_bundle.pem
@@ -716,7 +716,7 @@ nginx_start(){
# side-load unraid-api
unraid_api_control start
# resume nchan publishers
- /usr/local/sbin/monitor_nchan start
+ /usr/local/sbin/monitor_nchan start
rm -f /tmp/publishPaused
if nginx_running; then REPLY="Started"; else REPLY="Failed"; fi
diff --git a/etc/rc.d/rc.wireless b/etc/rc.d/rc.wireless
index c2f419784..8e16249ce 100755
--- a/etc/rc.d/rc.wireless
+++ b/etc/rc.d/rc.wireless
@@ -10,8 +10,10 @@ DAEMON="WiFi network"
CALLER="wifi"
INI="/var/local/emhttp/wireless.ini"
CFG="/boot/config/wireless.cfg"
+DOCKER="/boot/config/docker.cfg"
OPENSSL="/usr/local/emhttp/webGui/scripts/open_ssl"
STARTWIFI="/usr/local/emhttp/webGui/scripts/wireless"
+SERVICES="/usr/local/emhttp/webGui/scripts/update_services"
WPA="/etc/wpa_supplicant.conf"
# system network references
@@ -28,6 +30,16 @@ CONF6="/proc/sys/net/ipv6/conf"
[[ -r $INI ]] && . $INI
PORT=${PORT:-wlan0}
+# return variable value from file
+var(){
+ [[ -r "$2" ]] && grep -Pom1 "^$1=\"\K[^\"]+" "$2"
+}
+
+# return interface index
+index(){
+ cat $SYSTEM/$1/ifindex 2>/dev/null
+}
+
# translate security to informational text
trans(){
case "$1" in
@@ -126,13 +138,21 @@ ipaddr_up(){
if carrier $PORT; then STATE="UP"; else STATE="DOWN"; fi
log "interface $PORT is $STATE, setting static $IP address"
ipv6_addr $PORT 0 1
+ INDEX=$(index $PORT)
+ INDEX=$((3000 + ${INDEX:-$(($(index * | sort -n | tail -1) + 1))}))
if [[ $IP == ipv4 ]]; then
- [[ -n $IP4 && -n $MASK4 ]] && run ip -4 addr add $(unzero $IP4)/$MASK4 dev $PORT metric 3004
- [[ -n $GATEWAY4 ]] && run ip -4 route add default via $GATEWAY4 dev $PORT metric 3004
+ if [[ -n $IP4 && -n $MASK4 ]]; then
+ run ip -4 addr add $(unzero $IP4)/$MASK4 metric $INDEX dev $PORT
+ # re-add IPv4 address of parent (if docker is running)
+ if [[ $(var DOCKER_ALLOW_ACCESS $DOCKER) == yes && -S /var/run/docker.sock ]]; then
+ ip addr add $(unzero $IP4)/$MASK4 metric $(($INDEX - 1)) dev shim-$PORT
+ fi
+ fi
+ [[ -n $GATEWAY4 ]] && run ip -4 route add default via $GATEWAY4 metric $INDEX dev $PORT
fi
if [[ $IP == ipv6 ]]; then
- [[ -n $IP6 && -n $MASK6 ]] && run ip -6 addr add $(unzero6 $IP6)/$MASK6 dev $PORT metric 3004
- [[ -n $GATEWAY6 ]] && run ip -6 route add default via $GATEWAY6 dev $PORT metric 3004
+ [[ -n $IP6 && -n $MASK6 ]] && run ip -6 addr add $(unzero6 $IP6)/$MASK6 metric $INDEX dev $PORT
+ [[ -n $GATEWAY6 ]] && run ip -6 route add default via $GATEWAY6 metric $INDEX dev $PORT
fi
fi
if [[ $DNS == yes ]]; then
@@ -148,17 +168,13 @@ ipaddr_up(){
ipaddr_down(){
if [[ $DHCP == yes ]]; then
# release DHCP assigned address and default route
- OPTIONS="-q -k"
+ OPTIONS="-q -k -$1"
[[ $DNS == yes ]] && OPTIONS="$OPTIONS -C resolv.conf"
- [[ $IP == ipv4 ]] && OPTIONS="$OPTIONS -4"
- [[ $IP == ipv6 ]] && OPTIONS="$OPTIONS -6"
run dhcpcd $OPTIONS $PORT
elif [[ $DHCP == no ]]; then
# release static assigned address and default route
- [[ $IP == ipv4 ]] && run ip -4 addr flush dev $PORT
- [[ $IP == ipv4 ]] && run ip -4 route flush default dev $PORT
- [[ $IP == ipv6 ]] && run ip -6 addr flush dev $PORT
- [[ $IP == ipv6 ]] && run ip -6 route flush default dev $PORT
+ run ip -$1 addr flush dev $PORT
+ run ip -$1 route flush default dev $PORT
fi
}
@@ -253,19 +269,25 @@ wifi_stop(){
log "$DAEMON... No Wifi present."
return
fi
- IP=ipv4
DHCP=$DHCP4
DNS=$DNS4
- ipaddr_down
+ SRV4=$DNS
+ SRV6=
+ ipaddr_down 4
if [[ -n $DHCP6 ]]; then
- IP=ipv6
DHCP=$DHCP6
DNS=$DNS6
- ipaddr_down
+ SRV6=$DNS
+ ipaddr_down 6
fi
+ IPV4=$(ip -4 -br addr show scope global primary dev shim-$PORT | awk '{print $3,$4,$5;exit}')
+ [[ -n $IPV4 ]] && run ip addr del $IPV4 dev shim-$PORT
+ run ip addr flush dev $PORT
run pkill wpa_supplicant
run iw dev $PORT disconnect
run rm -f $INI
+ # restart services when static assignments
+ [[ $SRV4 == no && (-z $SRV6 || $SRV6 == no) ]] && $SERVICES 5
if ! wifi_running; then REPLY="Stopped"; else REPLY="Failed"; fi
log "$DAEMON... $REPLY."
}
@@ -309,6 +331,8 @@ wifi_join(){
IP=ipv4
DHCP=$DHCP4
DNS=$DNS4
+ SRV4=$DNS
+ SRV6=
ipaddr_up
# IPv6 address assignment (if enabled)
if [[ -n $DHCP6 ]]; then
@@ -316,10 +340,13 @@ wifi_join(){
IP=ipv6
DHCP=$DHCP6
DNS=$DNS6
+ SRV6=$DNS
ipaddr_up
else
echo 1 >$CONF6/$PORT/disable_ipv6
fi
+ # restart services when static assignments
+ [[ $SRV4 == no && (-z $SRV6 || $SRV6 == no) ]] && $SERVICES 5
if wifi_running; then
if [[ -z $CC ]]; then
CC=($(iw reg get | grep -Po '^country \K..'))
diff --git a/sbin/create_network_ini b/sbin/create_network_ini
index afe6c39e1..7fee9fe62 100755
--- a/sbin/create_network_ini
+++ b/sbin/create_network_ini
@@ -13,23 +13,42 @@
[[ (-z $reason && -z $1) || (-n $reason && ! "BOUND6 IPV4LL EXPIRE" =~ $reason) ]] && exit 0
-INI=/var/local/emhttp/network.ini.new
-CFG=/boot/config/network.cfg
-SYS=/sys/class/net
+INI="/var/local/emhttp/network.ini.new"
+STA="/var/local/emhttp/statics.ini.new"
+CFG="/boot/config/network.cfg"
+DOCKER="/boot/config/docker.cfg"
+SYSTEM="/sys/class/net"
declare -A VLANID USE_DHCP IPADDR NETMASK GATEWAY METRIC USE_DHCP6 IPADDR6 NETMASK6 GATEWAY6 PRIVACY6 METRIC6 DESCRIPTION PROTOCOL
# run & log functions
. /etc/rc.d/rc.runlog
-mask(){
- [[ -z $1 ]] && return
- # convert prefix to netmask
- set -- $((5-($1/8))) 255 255 255 255 $(((255<<(8-($1%8)))&255)) 0 0 0
- [[ $1 -gt 1 ]] && shift $1 || shift
- echo $1.$2.$3.$4
+# return variable value from file
+var(){
+ [[ -r "$2" ]] && grep -Pom1 "^$1=\"\K[^\"]+" "$2"
}
+# return interface index
+index(){
+ cat $SYSTEM/$1/ifindex 2>/dev/null
+}
+
+# convert netmask to prefix
+mask2cidr(){
+ [[ -z $1 ]] && return
+ local MASK=$(eval eval echo "'\$((('{"${1//./,}"}'>>'{7..0}')%2))'")
+ eval echo '$(('"${MASK// /+}"'))'
+}
+
+# convert prefix to netmask
+cidr2mask(){
+ [[ -z $1 ]] && return
+ local MASK=$(eval echo '$(((1<<32)-1<<32-$1>>'{3..0}'*8&255))')
+ echo "${MASK// /.}"
+}
+
+# return dns nameserver entry
dns() {
[[ $1 == 4 ]] && ADDR='(\d{1,3}\.){3}\d+' || ADDR='([0-9a-fA-F]{1,4}::?){1,7}[0-9a-fA-F]*'
grep -Po "^nameserver \K$ADDR" /etc/resolv.conf
@@ -48,8 +67,10 @@ else
BONDING=yes
BRIDGING=yes
fi
+
# prepare empty file
echo -n >$INI
+echo -n >$STA
# loop thru all defined interfaces (=1 in case of legacy)
for ((i=0; i<${SYSNICS:-1}; i++)); do
@@ -57,7 +78,7 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
ETH=${IFACE/#bond/eth}
ETH=${ETH/#br/eth}
# don't store when non-existing
- [[ -e $SYS/$ETH ]] || continue
+ [[ -e $SYSTEM/$ETH ]] || continue
echo "[$ETH]" >>$INI
if [[ $i -eq 0 ]]; then
# process legacy settings
@@ -127,10 +148,10 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "USE_GW4:0=\"${USE_GW4[$i]}\"" >>$INI
if [[ ${USE_DHCP[$i]} == yes ]]; then
# get dhcp assigned ipv4 address & mask
- NET=($(ip -4 -br addr show $IFACE | awk '{sub("/"," ",$3);print $3;exit}'))
- GW=$(ip -4 route show default dev $IFACE | awk '{print $3;exit}')
+ NET=($(ip -4 -br addr show scope global primary dev $IFACE | awk '{sub("/"," ",$3);print $3;exit}'))
+ GW=$(ip -4 route show to default dev $IFACE | awk '{print $3;exit}')
echo "IPADDR:0=\"${NET[0]}\"" >>$INI
- echo "NETMASK:0=\"$(mask ${NET[1]})\"" >>$INI
+ echo "NETMASK:0=\"$(cidr2mask ${NET[1]})\"" >>$INI
echo "GATEWAY:0=\"$GW\"" >>$INI
echo "METRIC:0=\"${METRIC[$i]}\"" >>$INI
else
@@ -139,13 +160,16 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "NETMASK:0=\"${NETMASK[$i]}\"" >>$INI
echo "GATEWAY:0=\"${GATEWAY[$i]}\"" >>$INI
echo "METRIC:0=\"${METRIC[$i]}\"" >>$INI
+ # store static ipv4 assignment
+ IPV4="$(ip -4 -br addr show scope global primary dev $IFACE | awk '{$2="";print;exit}')"
+ [[ -n $IPV4 ]] && echo "$IPV4" >>$STA
fi
echo "USE_DHCP6:0=\"${USE_DHCP6[$i]}\"" >>$INI
echo "USE_GW6:0=\"${USE_GW6[$i]}\"" >>$INI
if [[ ${USE_DHCP6[$i]} == yes ]]; then
# get auto assigned ipv6 address & prefix
- NET6=($(ip -6 -br addr show $IFACE scope global -temporary -deprecated | awk '{sub("/"," ",$3);print $3;exit}'))
- GW6=$(ip -6 route show default dev $IFACE | awk '{print $3;exit}')
+ NET6=($(ip -6 -br addr show scope global primary -deprecated dev $IFACE | awk '{sub("/"," ",$3);print $3;exit}'))
+ GW6=$(ip -6 route show to default dev $IFACE | awk '{print $3;exit}')
echo "IPADDR6:0=\"${NET6[0]}\"" >>$INI
echo "NETMASK6:0=\"${NET6[1]}\"" >>$INI
echo "GATEWAY6:0=\"$GW6\"" >>$INI
@@ -158,6 +182,9 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "GATEWAY6:0=\"${GATEWAY6[$i]}\"" >>$INI
echo "METRIC6:0=\"${METRIC6[$i]}\"" >>$INI
echo "PRIVACY6:0=\"\"" >>$INI
+ # store static ipv6 assignment
+ IPV6="$(ip -6 -br addr show scope global primary -deprecated dev $IFACE | awk '{$2="";print;exit}')"
+ [[ -n $IPV6 ]] && echo "$IPV6" >>$STA
fi
echo "USE_MTU=\"${USE_MTU[$i]}\"" >>$INI
echo "MTU=\"${MTU[$i]}\"" >>$INI
@@ -172,11 +199,11 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "USE_GW4:$j=\"${USE_GW4[$i,$j]}\"" >>$INI
if [[ ${USE_DHCP[$i,$j]} == yes ]]; then
DEV=$IFACE.${VLANID[$i,$j]}
- # get dhcp assigned ipv4 address & mask
- NET=($(ip -4 -br addr show $DEV | awk '{sub("/"," ",$3);print $3;exit}'))
- GW=$(ip -4 route show default dev $DEV | awk '{print $3;exit}')
+ # get dhcp assigned ipv4 address & cidr2mask
+ NET=($(ip -4 -br addr show scope global primary dev $DEV | awk '{sub("/"," ",$3);print $3;exit}'))
+ GW=$(ip -4 route show to default dev $DEV | awk '{print $3;exit}')
echo "IPADDR:$j=\"${NET[0]}\"" >>$INI
- echo "NETMASK:$j=\"$(mask ${NET[1]})\"" >>$INI
+ echo "NETMASK:$j=\"$(cidr2mask ${NET[1]})\"" >>$INI
echo "GATEWAY:$j=\"$GW\"" >>$INI
echo "METRIC:$j=\"${METRIC[$i,$j]}\"" >>$INI
else
@@ -185,14 +212,17 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "NETMASK:$j=\"${NETMASK[$i,$j]}\"" >>$INI
echo "GATEWAY:$j=\"${GATEWAY[$i,$j]}\"" >>$INI
echo "METRIC:$j=\"${METRIC[$i,$j]}\"" >>$INI
+ # store static ipv4 assignment
+ IPV4="$(ip -4 -br addr show scope global primary dev $DEV | awk '{$2="";print;exit}')"
+ [[ -n $IPV4 ]] && echo "$IPV4" >>$STA
fi
echo "USE_DHCP6:$j=\"${USE_DHCP6[$i,$j]}\"" >>$INI
echo "USE_GW6:$j=\"${USE_GW6[$i,$j]}\"" >>$INI
if [[ ${USE_DHCP6[$i,$j]} == yes ]]; then
DEV=$IFACE.${VLANID[$i,$j]}
# get auto assigned ipv6 address & prefix
- NET6=($(ip -6 -br addr show $DEV scope global -temporary -deprecated | awk '{sub("/"," ",$3);print $3;exit}'))
- GW6=$(ip -6 route show default dev $DEV | awk '{print $3;exit}')
+ NET6=($(ip -6 -br addr show scope global primary -deprecated dev $DEV | awk '{sub("/"," ",$3);print $3;exit}'))
+ GW6=$(ip -6 route show to default dev $DEV | awk '{print $3;exit}')
echo "IPADDR6:$j=\"${NET6[0]}\"" >>$INI
echo "NETMASK6:$j=\"${NET6[1]}\"" >>$INI
echo "GATEWAY6:$j=\"$GW6\"" >>$INI
@@ -205,6 +235,9 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
echo "GATEWAY6:$j=\"${GATEWAY6[$i,$j]}\"" >>$INI
echo "METRIC6:$j=\"${METRIC6[$i,$j]}\"" >>$INI
echo "PRIVACY6:$j=\"\"" >>$INI
+ # store static ipv6 assignment
+ IPV6="$(ip -6 -br addr show scope global primary -deprecated dev $DEV | awk '{$2="";print;exit}')"
+ [[ -n $IPV6 ]] && echo "$IPV6" >>$STA
fi
done
else
@@ -214,6 +247,25 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do
done
# atomically update file
mv $INI ${INI%.*}
+mv $STA ${STA%.*}
+
+# add or remove IPv4 assignment from attached interface when Docker custom network access is allowed
+[[ ${interface:0:2} == br || $interface == wlan0 ]] && LINK=shim-$interface || LINK=vhost${interface//[^0-9.]/}
+if [[ -e $SYSTEM/$LINK && $(var DOCKER_ALLOW_ACCESS $DOCKER) == yes ]]; then
+ IPV4=$(ip -4 -br addr show scope global primary dev $interface | awk '{print $3;exit}')
+ [[ $interface == wlan0 ]] && INDEX=3000 || INDEX=1000
+ INDEX=$(($INDEX - 1 + $(index $interface)))
+ case $reason in
+ 'BOUND' | 'BOUND6')
+ # re-add IPv4 address of parent (if docker is running)
+ [[ -S /var/run/docker.sock ]] && ip addr add $IPV4 metric $INDEX dev $LINK
+ ;;
+ 'EXPIRE')
+ # remove IPv4 address of parent
+ ip addr del $IPV4 metric $INDEX dev $LINK
+ ;;
+ esac
+fi
log "interface=${interface:-$1}, reason=$reason, protocol=$protocol"
# delayed execution
@@ -224,19 +276,19 @@ if [[ -z $interface || "eth0 br0 bond0 wlan0" =~ $interface ]]; then
. /etc/unraid-version
echo -e "Unraid Server OS version: $version" >/etc/issue
# find management interface
- [[ -e $SYS/bond0 ]] && dev=bond0 || dev=eth0
- [[ -e $SYS/br0 ]] && dev=br0
- IPv4=$(ip -4 -br addr show $dev scope global | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
- IPv6=$(ip -6 -br addr show $dev scope global -temporary -deprecated | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ [[ -e $SYSTEM/bond0 ]] && DEV=bond0 || DEV=eth0
+ [[ -e $SYSTEM/br0 ]] && DEV=br0
+ IPV4=$(ip -4 -br addr show scope global primary dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ IPV6=$(ip -6 -br addr show scope global primary -deprecated dev $DEV | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
# show current IP assignment
- [[ -n $IPv4 ]] && echo " IPv4 address: $IPv4" >>/etc/issue || echo " IPv4 address: not set" >>/etc/issue
- [[ -n $IPv6 ]] && echo " IPv6 address: $IPv6" >>/etc/issue || echo " IPv6 address: not set" >>/etc/issue
- if [[ -e $SYS/wlan0 ]]; then
+ [[ -n $IPV4 ]] && echo " IPv4 address: $IPV4" >>/etc/issue || echo " IPv4 address: not set" >>/etc/issue
+ [[ -n $IPV6 ]] && echo " IPv6 address: $IPV6" >>/etc/issue || echo " IPv6 address: not set" >>/etc/issue
+ if [[ -e $SYSTEM/wlan0 ]]; then
echo "Wireless network:" >>/etc/issue
- IPv4=$(ip -4 -br addr show wlan0 scope global | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
- IPv6=$(ip -6 -br addr show wlan0 scope global -temporary -deprecated | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
- [[ -n $IPv4 ]] && echo " IPv4 address: $IPv4" >>/etc/issue || echo " IPv4 address: not set" >>/etc/issue
- [[ -n $IPv6 ]] && echo " IPv6 address: $IPv6" >>/etc/issue || echo " IPv6 address: not set" >>/etc/issue
+ IPV4=$(ip -4 -br addr show scope global primary dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ IPV6=$(ip -6 -br addr show scope global primary -deprecated dev wlan0 | awk '{print $3;exit}' | sed -r 's/\/[0-9]+//')
+ [[ -n $IPV4 ]] && echo " IPv4 address: $IPV4" >>/etc/issue || echo " IPv4 address: not set" >>/etc/issue
+ [[ -n $IPV6 ]] && echo " IPv6 address: $IPV6" >>/etc/issue || echo " IPv6 address: not set" >>/etc/issue
fi
echo >>/etc/issue
fi
diff --git a/sbin/monitor_interface b/sbin/monitor_interface
new file mode 100755
index 000000000..331a021fb
--- /dev/null
+++ b/sbin/monitor_interface
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# script: monitor_interface
+#
+# Monitors a given list of interfaces and add or remove static IP addresses to these interfaces.
+# The list of interfaces is provided in the file '/var/local/emhttp/statics.ini'
+# This file is maintained by the script 'create_network_ini' which keep track of all IP assignments.
+#
+# By removing static IP addresses from inactive interfaces, these interfaces do not longer interfere with wireless.
+# In other words the wired connection can be pulled without consequences.
+#
+# Bergware - modified for Unraid OS, May 2025
+
+FILE=/var/local/emhttp/statics.ini
+SYSTEM=/sys/class/net
+
+state(){
+ cat $SYSTEM/$1/operstate 2>/dev/null
+}
+
+md5(){
+ [[ -r $FILE ]] && md5sum $FILE | awk '{print $1;exit}'
+}
+
+switch(){
+ local n status
+ [[ -z $1 ]] && return 1
+ status=3
+ # state should stay different for at least 3 seconds
+ for n in {1..3}; do
+ [[ $(state $1) != $2 ]] && ((status--))
+ sleep 1
+ done
+ [[ $status -eq 0 ]]
+}
+
+init(){
+ PORT=(); STATE=();
+ if [[ -r $FILE ]]; then
+ # initialize values from file, maintained by 'create_network_ini'
+ while IFS=$'\n' read -r ROW; do
+ PORT+=("$ROW")
+ STATE+=($(state ${ROW%% *}))
+ done <$FILE
+ fi
+ MD5=$(md5)
+}
+
+while :; do
+ # monitor file content changes
+ [[ $MD5 != $(md5) ]] && init
+ LAST=
+ for i in ${!PORT[@]}; do
+ INT=${PORT[$i]%% *}
+ # did interface state change?
+ if switch $INT ${STATE[$i]}; then
+ NEW=$(state $INT)
+ STATE[$i]=$NEW
+ if [[ $NEW == up ]]; then
+ ip addr add dev ${PORT[$i]}
+ elif [[ $NEW == down && $INT != $LAST ]]; then
+ ip addr flush scope global dev $INT
+ fi
+ fi
+ LAST=$INT
+ done
+ # check every 3 seconds
+ sleep 3
+done &
diff --git a/sbin/monitor_nchan b/sbin/monitor_nchan
index 76d50e769..dc61e7deb 100755
--- a/sbin/monitor_nchan
+++ b/sbin/monitor_nchan
@@ -7,6 +7,21 @@ status=http://localhost/pub/session?buffer_length=1 # nchan information about G
nchan_list=/tmp/nchan_list.tmp
nchan_id=$(basename "$0")
+nchan_subs(){
+ curl -m2 --unix-socket $nginx $status 2>/dev/null | grep -Pom1 'subscribers: \K\d+'
+}
+
+nchan_idle(){
+ local n idle subs
+ idle=3
+ for n in {1..3}; do
+ subs=$(nchan_subs)
+ [[ -z $subs || $subs =~ ^[0-9]+$ && $subs -eq 0 ]] && ((idle--))
+ sleep 3
+ done
+ [[ $idle -eq 0 ]]
+}
+
nchan_stop() {
echo -n >$nchan_list
while IFS=$'\n' read -r nchan; do
@@ -49,26 +64,18 @@ start=$(date +%s)
while :; do
# only act when GUI registered nchan processes are running
if [[ -s $nchan_pid ]]; then
- # get number of GUI nchan subscribers
- subs=$(curl --unix-socket $nginx $status 2>/dev/null | grep -Pom1 'subscribers: \K\d+')
- if [[ -z $subs || $subs -eq 0 ]]; then
- sleep 5
- # steady state?
- subs=$(curl --unix-socket $nginx $status 2>/dev/null | grep -Pom1 'subscribers: \K\d+')
- if [[ -z $subs || $subs -eq 0 ]]; then
- now=$(date +%s)
- # log at 1 hour interval
- if [[ $((now-start)) -ge 3600 ]]; then
- logger -t $nchan_id -- "Stop running nchan processes"
- start=$now
- fi
- nchan_stop
- # empty GUI registered list & statistics
- rm -f $nchan_pid $disk_load
+ if nchan_idle; then
+ now=$(date +%s)
+ # log at 1 hour interval
+ if [[ $((now-start)) -ge 3600 ]]; then
+ logger -t $nchan_id -- "Stop running nchan processes"
+ start=$now
fi
+ nchan_stop
+ # empty GUI registered list & statistics
+ rm -f $nchan_pid $disk_load
fi
fi
# check every 30 seconds
sleep 30
done &
-disown %%