diff --git a/emhttp/plugins/dynamix.docker.manager/DockerSettings.page b/emhttp/plugins/dynamix.docker.manager/DockerSettings.page index 0203a7f896..b6072ab567 100644 --- a/emhttp/plugins/dynamix.docker.manager/DockerSettings.page +++ b/emhttp/plugins/dynamix.docker.manager/DockerSettings.page @@ -25,23 +25,23 @@ exec("/etc/rc.d/rc.docker status >/dev/null",$dummy,$DockerStopped); function strposX($s, $c, $n=1) { $p = 0; - while ($n && $p=strpos($s,$c,$p)!==false) {$n--; $p+=strlen($c);} + while ($n && $p = strpos($s,$c,$p) !== false) {$n--; $p += strlen($c);} return $p; } unset($custom,$other); -exec("ls --indicator-style=none /sys/class/net|grep -P '^br[0-9]'",$custom); -exec("ls --indicator-style=none /sys/class/net|grep -P '^(bond|eth|wlan)[0-9]'",$other); -$bridge = count($custom)>0; +exec("ls --indicator-style=none /sys/class/net | grep -P '^br[0-9]'",$custom); +exec("ls --indicator-style=none /sys/class/net | grep -P '^(bond|eth|wlan)[0-9]'",$other); +$bridge = count($custom) > 0; $slaves = []; foreach ($other as $network) { - if (substr($network,0,4)=='bond') { + if (substr($network,0,4) == 'bond') { $br = str_replace('bond','br',$network); $bond = "/sys/class/net/$network/bonding/slaves"; if (file_exists($bond)) $slaves = array_merge($slaves,explode(' ',str_replace("\n","",file_get_contents($bond)))); if (!in_array($br,$custom)) $custom[] = $network; - } elseif (substr($network,0,3)=='eth') { + } elseif ($bridge && substr($network,0,3) == 'eth') { $br = str_replace('eth','br',$network); $bond = str_replace('eth','bond',$network); if (!in_array($br,$custom) && !in_array($bond,$custom)) $custom[] = $network; @@ -50,18 +50,18 @@ foreach ($other as $network) { } } -$include = $include6 = $address = $address6 = $gateway = $gateway6 = $unset = $protocol = []; -$wide = false; -$wlan = (array)@parse_ini_file('/var/local/emhttp/wireless.ini'); +$include = $include6 = $address = $address6 = $gateway = $gateway6 = $unset = $protocol = []; +$wide = false; +$wlan = (array)@parse_ini_file('/var/local/emhttp/wireless.ini'); foreach ($custom as $network) { if (in_array($network,$slaves)) continue; - $ip4 = exec("ip -4 -br addr show $network scope global | awk '{print $3;exit}'"); - $ip6 = exec("ip -6 -br addr show $network scope global -temporary -deprecated | awk '{print $3;exit}'"); - $gw4 = $ip4 ? exec("ip -4 route show dev $network default | awk '{print $3;exit}'") : ''; - $gw6 = $ip6 ? exec("ip -6 route show dev $network default | awk '{print $3;exit}'") : ''; - $route4 = $ip4 ? exec("ip -4 route show dev $network $ip4 | awk '{print $1;exit}'") : ''; - $route6 = $ip6 ? exec("ip -6 route show dev $network | awk '/^".substr($ip6,0,strposX($ip6,':',4))."/{print $1;exit}'") : ''; + $ip4 = exec("ip -4 -br addr show scope global primary dev $network | awk '{print $3;exit}'"); + $ip6 = exec("ip -6 -br addr show scope global primary -deprecated dev $network | awk '{print $3;exit}'"); + $gw4 = $ip4 ? exec("ip -4 route show to default dev $network | awk '{print $3;exit}'") : ''; + $gw6 = $ip6 ? exec("ip -6 route show to default dev $network | awk '{print $3;exit}'") : ''; + $route4 = $ip4 ? exec("ip -4 route show dev $network | awk '$1 !~ /^default/ {print $1;exit}'") : ''; + $route6 = $ip6 ? exec("ip -6 route show dev $network | awk '$1 !~ /^(default|f[a-f])/ {print $1;exit}'") : ''; if (substr($network,0,4) != 'wlan') { [$eth,$vlan] = my_explode('.',$network); $eth = str_replace(['bond','br'],'eth',$eth); @@ -89,6 +89,7 @@ foreach ($custom as $network) { } if ($protocol[$network] != 'ipv4') $wide = true; } + $ip4class = $wide ? 'ip6' : 'ip4'; $gw4class = $wide ? 'gw6' : 'gw4'; @@ -114,15 +115,6 @@ function base_net($route) { return substr(explode('/',$route)[0],0,-2); } -function hide_wlan($network) { - return $network=='wlan0' && lan_port(DockerUtil::port(),true)==1; -} - -function hide_eth($network) { - $mgmt_port = ['br0','bond0','eth0']; - return in_array($network,$mgmt_port) && lan_port('wlan0',true)==1; -} - $bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c'; //Check if docker.cfg does exist @@ -275,8 +267,8 @@ _(Template Authoring Mode)_: _(Docker custom network type)_: :  _(Please read the Help carefully)_. _(Misconfiguration can cause problems)_. :docker_custom_network_type_help: @@ -302,14 +294,9 @@ _(Preserve user defined networks)_: $net = normalize($network); $docker_auto = "DOCKER_AUTO_$net"; $docker_dhcp = "DOCKER_DHCP_$net"; -$hide_wlan = hide_wlan($network); ?> - -
- - _(IPv4 custom network on interface)_ (_(optional)_): (_(optional)_): **_(DHCP pool)_:**. "; - for ($n=$net[$b]; $n<=$max[$b]; $n++) echo mk_option($net_user[$b],$n,$n,$n%$step==0?'':'class="hide"'); + for ($n = $net[$b]; $n <= $max[$b]; $n++) echo mk_option($net_user[$b],$n,$n,$n%$step==0?'':'class="hide"'); echo ""; } echo "/ "; echo "($size "._('hosts').")"; echo ""; ?> - -
- @@ -370,12 +354,8 @@ $port = normalize($network); [$range,$size] = my_explode('/',_var($dockercfg,"DOCKER_RANGE_$port")); $disabled = $subnet ? '':'disabled'; $dhcpDisabled = $range ? '':'disabled'; -$hide_eth = hide_eth($network); ?> - - -
- + _(IPv4 custom network on interface)_ (_(optional)_): : > @@ -387,31 +367,22 @@ _(IPv4 custom network on interface)_ (_(optional)_): > **_(DHCP pool)_:** >/ + ( _(hosts)_) - - -
:docker_exclude_interface_vlan_ipv4_help: - - -
$route):?> - -
- + _(IPv6 custom network on interface)_ (_(optional)_): (_(optional)_): **_(Subnet)_:** **_(Gateway)_:** - -
@@ -444,12 +413,8 @@ $port = normalize($network); [$range6,$size6] = my_explode('/',_var($dockercfg,"DOCKER_RANGE6_$port")); $disabled = $subnet6 ? '':'disabled'; $dhcpDisabled = $range6 ? '':'disabled'; -$hide_eth = hide_eth($network); ?> - -
- _(IPv6 custom network on interface)_ (_(optional)_): : > @@ -458,9 +423,6 @@ _(IPv6 custom network on interface)_ (_(optional)_): **_(Gateway)_:**" title="_(IPv6 address nnnn:xxxx::yyyy)_"> - -
- @@ -518,32 +480,24 @@ _(Preserve user defined networks)_: $route):?> - -
- _(IPv4 custom network on interface)_ : : **_(Subnet)_:** **_(Gateway)_:** **_(DHCP pool)_:**   ( _(hosts)_) - -
- - -
- _(IPv4 custom network on interface)_ : : **_(Subnet)_:** / **_(Gateway)_:** **_(DHCP pool)_:**   ( _(hosts)_) - -
- $route):?> +if (isset($dockercfg[$docker_dhcp6]) || empty($dockercfg["DOCKER_AUTO_$net"]) || $dockercfg["DOCKER_AUTO_$net"] != 'no'):?> - -
- _(IPv6 custom network on interface)_ : : **_(Subnet)_:** **_(Gateway)_:** - -
- - -
- _(IPv6 custom network on interface)_ : : **_(Subnet)_:** / **_(Gateway)_:** - -
- diff --git a/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php b/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php index 21fee16cad..185cb76bf5 100644 --- a/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php +++ b/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php @@ -1178,7 +1178,7 @@ public static function port() { public static function host() { $port = static::port(); if (!$port) return ''; - $port = lan_port($port,true)==0 && lan_port('wlan0') ? 'wlan0' : $port; + $port = lan_port($port,true)!=1 && lan_port('wlan0') ? 'wlan0' : $port; return exec("ip -br -4 addr show $port scope global | sed -r 's/\/[0-9]+//g' | awk '{print $3;exit}'"); } } diff --git a/emhttp/plugins/dynamix.docker.manager/javascript/docker.js b/emhttp/plugins/dynamix.docker.manager/javascript/docker.js index 7b170ac088..f08ea86ed8 100644 --- a/emhttp/plugins/dynamix.docker.manager/javascript/docker.js +++ b/emhttp/plugins/dynamix.docker.manager/javascript/docker.js @@ -2,7 +2,7 @@ var eventURL = '/plugins/dynamix.docker.manager/include/Events.php'; function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, tswebui, shell, id, Support, Project, Registry, donateLink, ReadMe) { var opts = []; - context.settings({right:false,above:'auto'}); + context.settings({right:false,above:false}); if (started && !paused) { if (webui !== '' && webui != '#') opts.push({text:_('WebUI'), icon:'fa-globe', action:function(e){e.preventDefault();window.open(webui,'_blank');}}); if (tswebui !== '' && tswebui != '#') opts.push({text:_('Tailscale WebUI'), icon:'fa-globe', action:function(e){e.preventDefault();window.open(tswebui,'_blank');}}); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index e5b7be2ebc..eaf4e1c060 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1228,11 +1228,10 @@ function getHostCPUModel() { } function getValidNetworks() { - global $lv; + global $lv,$libvirt_running; $arrValidNetworks = []; + exec("ls --indicator-style=none /sys/class/net | grep -Po '^virbr[0-9]+'",$arrBridges); exec("ls --indicator-style=none /sys/class/net | grep -Po '^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); - // add 'virbr0' as default first choice - array_unshift($arrBridges, 'virbr0'); // remove redundant references of bridge and bond interfaces $remove = []; foreach ($arrBridges as $name) { @@ -1240,12 +1239,11 @@ function getValidNetworks() { $remove = array_merge($remove, (array)@file("/sys/class/net/$name/bonding/slaves",FILE_IGNORE_NEW_LINES)); } elseif (substr($name,0,2) == 'br') { $remove = array_merge($remove, array_map(function($n){return end(explode('/',$n));}, glob("/sys/class/net/$name/brif/*"))); - } + } } $arrValidNetworks['bridges'] = array_diff($arrBridges, $remove); - // This breaks VMSettings.page if libvirt is not running - /* if ($libvirt_running == "yes") { + if ($libvirt_running == "yes") { $arrVirtual = $lv->libvirt_get_net_list($lv->get_connection()); if (($key = array_search('default', $arrVirtual)) !== false) { @@ -1255,7 +1253,7 @@ function getValidNetworks() { array_unshift($arrVirtual, 'default'); $arrValidNetworks['libvirt'] = array_values($arrVirtual); - }*/ + } return $arrValidNetworks; } @@ -1845,15 +1843,15 @@ function vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID) { $rtn = $lv->domain_define($xml); if (is_resource($rtn)) { - $arrResponse = ['success' => true]; + $arrResponse = ['success' => true]; write("addLog\0".htmlspecialchars(_("Creating XML successful"))); - } else { + } else { $lastvmerror = $lv->get_last_error(); $arrResponse = ['xml' => $xml,'error' => $lastvmerror]; write("addLog\0".htmlspecialchars(_("Creating XML Error:$lastvmerror"))); file_put_contents("/tmp/vmclonertn.debug", json_encode($arrResponse,JSON_PRETTY_PRINT)); } - + return($rtn); } diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index 3d3f0c5d5d..8557119515 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -40,6 +40,9 @@ $strCPUModel = getHostCPUModel(); $templateslocation = "/boot/config/plugins/dynamix.vm.manager/savedtemplates.json"; +// get MAC address of wireless interface (if existing) +$mac = file_exists('/sys/class/net/wlan0/address') ? trim(file_get_contents('/sys/class/net/wlan0/address')) : ''; + if (is_file($templateslocation)){ $arrAllTemplates["User-templates"] = ""; $ut = json_decode(file_get_contents($templateslocation),true); @@ -352,7 +355,7 @@ - + @@ -2017,13 +2020,17 @@ var storageType = ""; var storageLoc = ""; -function updateMAC(index,port) { - $('input[name="nic['+index+'][mac]"').prop('disabled',port=='wlan0'); - $('i.mac_generate.'+index).prop('disabled',port=='wlan0'); +function updateMAC(index, port) { + var wlan0 = ''; // mac address of wlan0 + var mac = $('input[name="nic['+index+'][mac]"]'); + mac.prop('disabled', port=='wlan0'); + $('i.mac_generate.'+index).prop('disabled', port=='wlan0'); $('span.wlan0').removeClass('hidden'); - if (port != 'wlan0') { + if (port == 'wlan0') { + mac.val(wlan0); + } else { $('span.wlan0').addClass('hidden'); - $('i.mac_generate.'+index).click(); + if (wlan0 && mac.val()==wlan0) $('i.mac_generate.'+index).click(); } } diff --git a/emhttp/plugins/dynamix.vm.manager/templates/XML_Expert.form.php b/emhttp/plugins/dynamix.vm.manager/templates/XML_Expert.form.php index 900c057334..cbc2bc279a 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/XML_Expert.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/XML_Expert.form.php @@ -45,7 +45,7 @@ exit; } - + // create new VM template if (isset($_POST['createvmtemplate'])) { $reply = addtemplatexml($_POST); @@ -116,7 +116,7 @@ - + @@ -217,12 +217,12 @@ function completeIfInTag(cm) { $button.val($button.attr('busyvalue')); swal({ title: "_(Template Name)_", - text: "_(Enter name:\nIf name already exists it will be replaced.)_", + text: "_(Enter name)_:\n_(If name already exists it will be replaced)_.", type: "input", showCancelButton: true, closeOnConfirm: false, //animation: "slide-from-top", - inputPlaceholder: "_(Leaving blank will use OS name.)_" + inputPlaceholder: "_(Leaving blank will use OS name)_." }, function(inputValue){ postdata=postdata+"&templatename="+inputValue; diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page index 33189fbb29..d77c3faf74 100644 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -17,27 +17,37 @@ Nchan="device_list,disk_load,parity_list" ?> 0; $poolsOnly = (_var($var,'SYS_ARRAY_SLOTS') == 0 ) ? true : false; /* only one of $present, $missing, or $wrong will be true, or all will be false */ -$forced = $present = $wrong = false; -foreach ($disks as $disk) { - if (strpos(_var($disk,'fsType'),'luks:')!==false || (_var($disk,'fsType')=='auto' && strpos(_var($var,'defaultFsType'),'luks:')!==false)) $forced = true; - if (_var($disk,'luksState',0)==1) $present = true; - if (_var($disk,'luksState',0)==2) $missing = true; - if (_var($disk,'luksState',0)==3) $wrong = true; +$forced = $present = $missing = $wrong = false; + +foreach (luks_filter($disks) as $disk) { + $fsType = _var($disk,'fsType'); + $luks = str_starts_with($fsType,'luks:'); + $auto = $fsType == 'auto'; + if ($luks || ($auto && str_starts_with(_var($var,'defaultFsType'),'luks:'))) $forced = true; + if ($luks || $auto) switch (_var($disk,'luksState',0)) { + case 1: $present = true; break; + case 2: $missing = true; break; + case 3: $wrong = true; break; + } } + $encrypt = $forced || $present || $missing || $wrong; if ($forced && ($present || $missing || $wrong)) $forced = false; function check_encryption() { global $forced, $missing, $wrong; - if ($forced) $status = _('Enter new key'); - elseif ($missing) $status = _('Missing key'); - elseif ($wrong) $status = _('Wrong key'); - else return; + if ($forced) + $status = _('Enter new key'); + elseif ($missing) + $status = _('Missing key'); + elseif ($wrong) + $status = _('Wrong key'); + else + return; echo ""; echo "
_(Rename disabled, snapshot(s) exists)_. .
",_('Encryption status').":$statuspermit reformat
",_('Encryption input').":"; echo "> +: @@ -804,7 +805,7 @@ _(Compression)_:
_(Autotrim)_: -: diff --git a/emhttp/plugins/dynamix/DiskSettings.page b/emhttp/plugins/dynamix/DiskSettings.page index a5cb7cdf80..892c8e2439 100644 --- a/emhttp/plugins/dynamix/DiskSettings.page +++ b/emhttp/plugins/dynamix/DiskSettings.page @@ -4,8 +4,8 @@ Icon="icon-disks" Tag="icon-disk" --- - form.oldluks.value = base64(form.oldtext.value); + form.oldluks.value = base64(form.oldtext.value.replace(/\\"/g,'"')); form.oldtext.disabled = true; form.oldfile.disabled = true; - form.newluks.value = base64(form.newtext.value); - form.newtext.disabled = true; - form.newcopy.disabled = true; - form.newfile.disabled = true; + var valid = new RegExp('^[ -~]+$'); + if (form.newinput.value == 'file') return true; + if (valid.test(form.newtext.value)) { + form.newluks.value = base64(form.newtext.value.replace(/\\"/g,'"')); + form.newtext.disabled = true; + form.newcopy.disabled = true; + form.newfile.disabled = true; + return true; + } else { + swal({ + title:"_(Printable Characters Only)_", + text:"_(Use **ASCII** characters from space ' ' to tilde '~')_
_(Otherwise use the **keyfile** method for UTF8 input)_", + html:true, + type:'error', + confirmButtonText:"_(Ok)_" + }); + return false; + } } + function getFileContent(event,form,file) { var input = event.target; var reader = new FileReader(); @@ -268,7 +283,7 @@ _(Default critical SSD temperature threshold)_ (°
_(Change encryption key)_
-
+ diff --git a/emhttp/plugins/dynamix/Eth0.page b/emhttp/plugins/dynamix/Eth0.page index 7b4858ea9a..41c0b50526 100644 --- a/emhttp/plugins/dynamix/Eth0.page +++ b/emhttp/plugins/dynamix/Eth0.page @@ -15,7 +15,7 @@ Tag="icon-ethernet" */ ?> /dev/null | grep -Pom1 ' metric \K\d+'"); - if ($metric) return $metric + $index; - exec("ip -$prot route show default 2>/dev/null | grep -Po ' metric \K\d+'",$metrics); - return (count($metrics) ? max($metrics) : 0) + $index + 1; + $index = exec("cat $system/$port/ifindex 2>/dev/null"); + return $index ?: exec("cat $system/*/ifindex | sort -n | tail -1") + 1; } // remove non-existing ethernet ports @@ -206,22 +204,24 @@ function prepareSettings(form) { $(form).find('select[name^="USE_DHCP:"]').each(function() { var i = $(this).prop('name').split(':')[1]; var protocol = $(form).find('select[name="PROTOCOL:'+i+'"]').val() || 'ipv4'; - var gw4 = (port == 'eth0') ? true : $(form).find('input[name="USE_GW4:'+i+'"]').prop('checked'); + var gw4 = $(form).find('input[name="USE_GW4:'+i+'"]').prop('checked'); if (protocol != 'ipv6' && $(this).val() != 'no') { $(form).find('input[name="IPADDR:'+i+'"]').val(''); $(form).find('input[name="GATEWAY:'+i+'"]').val(''); - $(form).find('input[name="METRIC:'+i+'"]').val(gw4?'':'0'); + $(form).find('input[name="METRIC:'+i+'"]').val(''); } + if (gw4 === false) $(form).find('input[name="METRIC:'+i+'"]').val('0'); }); $(form).find('select[name^="USE_DHCP6:"]').each(function() { var i = $(this).prop('name').split(':')[1]; var protocol = $(form).find('select[name="PROTOCOL:'+i+'"]').val() || 'ipv4'; - var gw6 = (port == 'eth0') ? true : $(form).find('input[name="USE_GW6:'+i+'"]').prop('checked'); + var gw6 = $(form).find('input[name="USE_GW6:'+i+'"]').prop('checked'); if (protocol != 'ipv4' && $(this).val() != 'no') { $(form).find('input[name="IPADDR6:'+i+'"]').val(''); $(form).find('input[name="GATEWAY6:'+i+'"]').val(''); - $(form).find('input[name="METRIC6:'+i+'"]').val(gw6?'':'0'); + $(form).find('input[name="METRIC6:'+i+'"]').val(''); } + if (gw6 === false) $(form).find('input[name="METRIC6:'+i+'"]').val('0'); if ($(this).val() != 'yes') $(form).find('input[name="PRIVACY6:'+i+'"]').val(''); }); if (port == 'eth0') { @@ -476,9 +476,9 @@ function addVLAN(port) { } function removeVLAN(element) { - var id = $(element).prop('id').split('-'); + var form = $(element).closest('form'); $(element).remove(); - $('#view-'+id[1]).find('select').first().trigger('change'); + form.find('select').first().trigger('change'); } function showVLAN(port) { @@ -729,7 +729,7 @@ _(IPv4 address)_: _(IPv4 default gateway)_: : " class="narrow" pattern="" title="_(IPv4 address A.B.C.D)_"> - " class="slim"> ** + " class="slim"> ** :eth_ipv4_default_gateway_help: @@ -752,7 +752,7 @@ _(IPv6 address)_: _(IPv6 default gateway)_: : " pattern="" title="_(IPv6 address nnnn:xxxx::yyyy)_"> - " class="slim"> ** + " class="slim"> ** :eth_ipv6_default_gateway_help: @@ -815,7 +815,7 @@ _(IPv4 address assignment)_: - > + > :eth_ipv4_address_assignment_help: @@ -830,7 +830,7 @@ _(IPv4 address)_:
_(IPv4 default gateway)_: : " class="narrow" pattern="" title="_(IPv4 address A.B.C.D)_"> - ',4,$i)?>" class="slim"> ** + " class="slim"> ** :eth_ipv4_default_gateway_help: @@ -846,7 +846,7 @@ _(IPv6 address assignment)_: - > + > :eth_ipv6_address_assignment_help: @@ -859,7 +859,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_: : " pattern="" title="_(IPv6 address nnnn:xxxx::yyyy)_"> - ',6,$i)?>" class="slim"> ** + " class="slim"> ** :eth_ipv6_default_gateway_help: @@ -921,7 +921,7 @@ _(IPv4 address)_:
_(IPv4 default gateway)_: : - ** + **
@@ -942,7 +942,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_: : - ** + **
diff --git a/emhttp/plugins/dynamix/EthX.page b/emhttp/plugins/dynamix/EthX.page index be1713f3b5..e00ca39d2a 100644 --- a/emhttp/plugins/dynamix/EthX.page +++ b/emhttp/plugins/dynamix/EthX.page @@ -171,7 +171,7 @@ _(IPv4 address assignment)_: - > + > :eth_ipv4_address_assignment_help: @@ -186,7 +186,7 @@ _(IPv4 address)_:
_(IPv4 default gateway)_: : " class="narrow" pattern="" title="_(IPv4 address A.B.C.D)_"> - " class="slim"> ** + " class="slim"> ** :eth_ipv4_default_gateway_help: @@ -200,7 +200,7 @@ _(IPv6 address assignment)_: - > + > :eth_ipv6_address_assignment_help: @@ -213,7 +213,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_: : " pattern="" title="_(IPv6 address nnnn:xxxx::yyyy)_"> - " class="slim"> ** + " class="slim"> ** :eth_ipv6_default_gateway_help: @@ -278,7 +278,7 @@ _(IPv4 address assignment)_: - > + > :eth_ipv4_address_assignment_help: @@ -293,7 +293,7 @@ _(IPv4 address)_:
_(IPv4 default gateway)_: : " class="narrow" pattern="" title="_(IPv4 address A.B.C.D)_"> - ',4,$i)?>" class="slim"> ** + " class="slim"> ** :eth_ipv4_default_gateway_help: @@ -307,7 +307,7 @@ _(IPv6 address assignment)_: - > + > :eth_ipv6_address_assignment_help: @@ -320,7 +320,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_: : " pattern="" title="_(IPv6 address nnnn:xxxx::yyyy)_"> - ',6,$i)?>" class="slim"> ** + " class="slim"> ** :eth_ipv6_default_gateway_help: @@ -383,7 +383,7 @@ _(IPv4 address)_:
_(IPv4 default gateway)_: : - ** + **
@@ -404,7 +404,7 @@ _(IPv6 address)_:
_(IPv6 default gateway)_: : - ** + **
diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index 68474afcfd..a985174c4d 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -1,6 +1,6 @@ 0) { mkdir($dirname, $permissions, $recursive); write_logging( "created dir:$dirname\n");} else chmod($zfsdataset,$permissions); @@ -396,8 +402,8 @@ function my_rmdir($dirname) { return($return); } function get_realvolume($path) { - if (strpos($path,"/mnt/user/",0) === 0) - $reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null")); + if (strpos($path,"/mnt/user/",0) === 0) + $reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null")); else { $realexplode = explode("/",str_replace("/mnt/","",$path)); $reallocation = $realexplode[0]; @@ -411,8 +417,7 @@ function write_logging($value) { file_put_contents('/tmp/my_mkdir_output', $value, FILE_APPEND); } -function device_exists($name) -{ +function device_exists($name) { global $disks,$devs; return (array_key_exists($name, $disks) && !str_contains(_var($disks[$name],'status'),'_NP')) || (array_key_exists($name, $devs)); } diff --git a/emhttp/plugins/dynamix/include/KeyUpload.php b/emhttp/plugins/dynamix/include/KeyUpload.php index 193af71f5b..75846a9a02 100644 --- a/emhttp/plugins/dynamix/include/KeyUpload.php +++ b/emhttp/plugins/dynamix/include/KeyUpload.php @@ -1,6 +1,6 @@ diff --git a/emhttp/plugins/dynamix/include/RoutingTable.php b/emhttp/plugins/dynamix/include/RoutingTable.php index 2bfa73efb3..1741cbc99c 100644 --- a/emhttp/plugins/dynamix/include/RoutingTable.php +++ b/emhttp/plugins/dynamix/include/RoutingTable.php @@ -26,8 +26,8 @@ if ($gateway && $route) exec("/etc/rc.d/rc.inet1 ".escapeshellarg("{$gateway}_{$route}_{$metric}_add")); break; default: - exec("ip -4 route show table all|grep -Pv '^(127\\.0\\.0\\.0)|table local|unreachable'",$ipv4); - exec("ip -6 route show table all|grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|table local|unreachable'",$ipv6); + exec("ip -4 route show table all | grep -Pv '^(127\\.0\\.0\\.0)|table local|unreachable|linkdown|broadcast'",$ipv4); + exec("ip -6 route show table all | grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|table local|unreachable|linkdown'",$ipv6); foreach ($ipv4 as $info) { $cell = explode(' ',$info); $route = $cell[0]; diff --git a/emhttp/plugins/dynamix/include/Wrappers.php b/emhttp/plugins/dynamix/include/Wrappers.php index 77c9a9ee61..b430ee17f1 100644 --- a/emhttp/plugins/dynamix/include/Wrappers.php +++ b/emhttp/plugins/dynamix/include/Wrappers.php @@ -25,14 +25,13 @@ function file_put_contents_atomic($filename,$data) { while (true) { $suffix = rand(); - if ( ! is_file("$filename$suffix") ) - break; + if (!is_file("$filename$suffix")) break; } $renResult = false; $writeResult = @file_put_contents("$filename$suffix",$data) === strlen($data); - if ( $writeResult ) + if ($writeResult) $renResult = @rename("$filename$suffix",$filename); - if ( ! $writeResult || ! $renResult ) { + if (!$writeResult || !$renResult) { my_logger("File_put_contents_atomic failed to write / rename $filename"); @unlink("$filename$suffix"); return false; @@ -74,13 +73,13 @@ function agent_fullname($agent, $state) { function get_plugin_attr($attr, $file) { global $docroot; exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin ".escapeshellarg($attr)." ".escapeshellarg($file), $result, $error); - if ($error===0) return $result[0]; + if ($error === 0) return $result[0]; } function plugin_update_available($plugin, $os=false) { $local = get_plugin_attr('version', "/var/log/plugins/$plugin.plg"); $remote = get_plugin_attr('version', "/tmp/plugins/$plugin.plg"); - if ($remote && strcmp($remote,$local)>0) { + if ($remote && strcmp($remote,$local) > 0) { if ($os) return $remote; if (!$unraid = get_plugin_attr('Unraid', "/tmp/plugins/$plugin.plg")) return $remote; $server = get_plugin_attr('version', "/var/log/plugins/unRAIDServer.plg"); @@ -102,7 +101,7 @@ function fahrenheit($temp) { function displayTemp($temp) { global $display; - return (is_numeric($temp) && _var($display,'unit')=='F') ? fahrenheit($temp) : $temp; + return (is_numeric($temp) && _var($display,'unit') == 'F') ? fahrenheit($temp) : $temp; } function get_value(&$name, $key, $default) { @@ -217,11 +216,10 @@ function my_logger($message, $logger='webgui') { * @param ?array $getinfo Empty array passed by reference, will contain results of curl_getinfo and curl_error, or null if not needed * @return string|false $out The fetched content */ -function http_get_contents(string $url, array $opts = [], ?array &$getinfo = NULL) { +function http_get_contents(string $url, array $opts=[], ?array &$getinfo=NULL) { $ch = curl_init(); - if(isset($getinfo)) { - curl_setopt($ch, CURLINFO_HEADER_OUT, TRUE); - } + if (isset($getinfo)) + curl_setopt($ch, CURLINFO_HEADER_OUT, true); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -232,8 +230,8 @@ function http_get_contents(string $url, array $opts = [], ?array &$getinfo = NUL curl_setopt($ch, CURLOPT_REFERER, ""); curl_setopt($ch, CURLOPT_FAILONERROR, true); curl_setopt($ch, CURLOPT_USERAGENT, 'Unraid'); - if(is_array($opts) && $opts) { - foreach($opts as $key => $val) { + if (is_array($opts) && count($opts) > 0) { + foreach ($opts as $key => $val) { curl_setopt($ch, $key, $val); } } @@ -247,8 +245,8 @@ function http_get_contents(string $url, array $opts = [], ?array &$getinfo = NUL $getinfo = curl_getinfo($ch); } if ($errno = curl_errno($ch)) { - $msg = "Curl error $errno: " . (curl_error($ch) ?: curl_strerror($errno)) . ". Requested url: '$url'"; - if(isset($getinfo)) { + $msg = "Curl error $errno: ".(curl_error($ch) ?: curl_strerror($errno)).". Requested url: '$url'"; + if (isset($getinfo)) { $getinfo['error'] = $msg; } my_logger($msg, "http_get_contents"); @@ -272,4 +270,8 @@ function lan_port($port, $state=false) { $exist = file_exists("$system/$port"); return !$state ? $exist : ($exist ? (@file_get_contents("$system/$port/carrier") ?: 0) : false); } + +function shieldarg(...$args) { + return implode(' ', array_map('escapeshellarg', $args)); +} ?> diff --git a/emhttp/plugins/dynamix/include/update.encryption.php b/emhttp/plugins/dynamix/include/update.encryption.php index 1bb9376469..eb49c36b6e 100644 --- a/emhttp/plugins/dynamix/include/update.encryption.php +++ b/emhttp/plugins/dynamix/include/update.encryption.php @@ -1,6 +1,6 @@ 1) exec("cryptsetup luksRemoveKey /dev/$disk $key &>/dev/null"); + if ($slots > 1) exec("cryptsetup luksRemoveKey ".shieldarg("/dev/$disk", $key)." &>/dev/null"); } + function diskname($name) { global $disks; - foreach ($disks as $disk) if (strncmp($name,$disk['device'],strlen($disk['device']))==0) return $disk['name']; + foreach ($disks as $disk) if (strncmp($name, $disk['device'], strlen($disk['device'])) == 0) return $disk['name']; return $name; } -function reply($text,$type) { - global $oldkey,$newkey,$delkey; + +function reply($text, $type) { + global $oldkey, $newkey; $reply = _var($_POST,'#reply'); - if (realpath(dirname($reply))=='/var/tmp') file_put_contents($reply,$text."\0".$type); + if (realpath(dirname($reply)) == '/var/tmp') file_put_contents($reply, $text."\0".$type); delete_file($oldkey); - if (_var($_POST,'newinput','text')=='text' || $delkey) delete_file($newkey); + if (_var($_POST,'newinput','text') == 'text') delete_file($newkey); die(); } if (isset($_POST['oldinput'])) { switch ($_POST['oldinput']) { case 'text': - file_put_contents($oldkey,base64_decode(_var($_POST,'oldluks'))); + file_put_contents($oldkey, base64_decode(_var($_POST,'oldluks'))); break; case 'file': - file_put_contents($oldkey,base64_decode(explode(';base64,',_var($_POST,'olddata','x;base64,'))[1])); + file_put_contents($oldkey, base64_decode(explode(';base64,',_var($_POST,'olddata','x;base64,'))[1])); break; } } else { - if (is_file($newkey)) copy($newkey,$oldkey); + if (is_file($newkey)) copy($newkey, $oldkey); } if (is_file($oldkey)) { $disk = $crypto[0]; // check first disk only (key is the same for all disks) - exec("cryptsetup luksOpen --test-passphrase --key-file $oldkey /dev/$disk &>/dev/null",$null,$error); + exec("cryptsetup luksOpen --test-passphrase --key-file ".shieldarg($oldkey, "/dev/$disk")." &>/dev/null", $null, $error); } else $error = 1; if ($error > 0) reply(_('Incorrect existing key'),'warning'); @@ -86,25 +88,25 @@ function reply($text,$type) { if (isset($_POST['newinput'])) { switch ($_POST['newinput']) { case 'text': - file_put_contents($newkey,base64_decode(_var($_POST,'newluks'))); + file_put_contents($newkey, base64_decode(_var($_POST,'newluks'))); $luks = 'luksKey'; - $data = _var($_POST,'newluks'); + $data = str_replace('+', '%2B', _var($_POST,'newluks')); break; case 'file': - file_put_contents($newkey,base64_decode(explode(';base64,',_var($_POST,'newdata','x;base64,'))[1])); + file_put_contents($newkey, base64_decode(explode(';base64,',_var($_POST,'newdata','x;base64,'))[1])); $luks = 'luksKey=&luksKeyfile'; $data = $newkey; break; } $good = $bad = []; foreach ($crypto as $disk) { - exec("cryptsetup luksAddKey --key-file $oldkey /dev/$disk $newkey &>/dev/null",$null,$error); - if ($error==0) $good[] = $disk; else $bad[] = diskname($disk); + exec("cryptsetup luksAddKey --key-file ".shieldarg($oldkey, "/dev/$disk", $newkey)." &>/dev/null", $null, $error); + if ($error == 0) $good[] = $disk; else $bad[] = diskname($disk); } - if (count($bad)==0) { + if (count($bad) == 0) { // all okay, remove the old key - foreach ($good as $disk) removeKey($oldkey,$disk); - exec("emcmd 'changeDisk=apply&$luks=$data'"); + foreach ($good as $disk) removeKey($oldkey, $disk); + exec("emcmd ".escapeshellarg("changeDisk=apply&$luks=$data")); reply(_('Key successfully changed'),'success'); } else { // something went wrong, restore key diff --git a/emhttp/plugins/dynamix/nchan/device_list b/emhttp/plugins/dynamix/nchan/device_list index 4b291d0322..e16bacf3d3 100755 --- a/emhttp/plugins/dynamix/nchan/device_list +++ b/emhttp/plugins/dynamix/nchan/device_list @@ -1,7 +1,7 @@ #!/usr/bin/php -q "._('Device to be encrypted').""; break; case 1: diff --git a/emhttp/plugins/dynamix/nchan/file_manager b/emhttp/plugins/dynamix/nchan/file_manager index fb86127241..076a6b09d8 100755 --- a/emhttp/plugins/dynamix/nchan/file_manager +++ b/emhttp/plugins/dynamix/nchan/file_manager @@ -186,7 +186,7 @@ while (true) { } else { $target = validname($target, false); if ($target) { - $f = $exist ? 'f' : 'n'; + $f = $exist ? 'n' : 'f'; exec("mv -{$f}t ".escapeshellarg($target)." ".quoted($source)." 1>$status 2>$error & echo \$!", $pid); } else { $reply['error'] = 'Invalid target name'; diff --git a/emhttp/plugins/dynamix/scripts/reload_services b/emhttp/plugins/dynamix/scripts/reload_services index a1fe713cbd..babe9c7c15 100755 --- a/emhttp/plugins/dynamix/scripts/reload_services +++ b/emhttp/plugins/dynamix/scripts/reload_services @@ -8,9 +8,6 @@ queue(){ atq | grep -Pom1 '^\d+' } -# delayed execution -sleep ${1:-1} - JOB=$(queue) if [[ -n $JOB ]]; then atrm $JOB 2>/dev/null diff --git a/emhttp/plugins/dynamix/scripts/update_services b/emhttp/plugins/dynamix/scripts/update_services index a899507330..652e555b2d 100755 --- a/emhttp/plugins/dynamix/scripts/update_services +++ b/emhttp/plugins/dynamix/scripts/update_services @@ -15,6 +15,14 @@ else log "no queued job present" fi -echo "/usr/local/emhttp/webGui/scripts/reload_services ${1:-1}" | at -M now 2>/dev/null -log "queue new job $(queue), wait for ${1:-1}s" +# Validate delay parameter is numeric +if [[ -n $1 && ! $1 =~ ^[0-9]+$ ]]; then + log "invalid delay parameter: $1, using default" + DELAY=1 +else + DELAY=${1:-1} +fi + +echo "sleep $DELAY; /usr/local/emhttp/webGui/scripts/reload_services" | at -M now 2>/dev/null +log "queue new job $(queue), wait for ${DELAY}s" exit 0 diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker index 6a9719d35d..4d1e3290e6 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" @@ -30,11 +30,23 @@ DOCKER_TIMEOUT=$(awk -F'"' '/^DOCKER_TIMEOUT=/{print $2}' $DOCKER_CFG 2>/dev/nul # network file references INI=/var/local/emhttp/network.ini +STA=/var/local/emhttp/statics.ini TMP=/var/tmp/network.tmp # run & log functions . /etc/rc.d/rc.runlog +# 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 @@ -46,23 +58,29 @@ carrier(){ return 1 } +# add entry to watch list +add_entry(){ + rm -f /var/tmp/${1%% *}.down + [[ -e $STA ]] && echo "$1" >>$STA +} + +# delete enty from watch list +del_entry(){ + touch /var/tmp/$1.down + [[ -e $STA ]] && sed -i "/^$1 .*/d" $STA + sleep 0.5 +} + # 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 +121,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 +183,8 @@ driver(){ ATTACH='macvlan' MODE='bridge' fi + # wlan0 has forced ipvlan + [[ $1 == wlan && $2 == forced ]] && ATTACH=ipvlan } # Custom networks @@ -213,9 +233,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 +266,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 +274,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 +320,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 +343,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 dev $NETWORK | awk '$1 !~ /^default/ {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 '^f[a-f]|^$') || 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 dev $NETWORK | awk '$1 !~ /^(default|f[a-f])/ {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 +385,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 +395,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 - VHOST=$NETWORK - elif [[ $TYPE == wlan ]]; then + driver $TYPE forced + if [[ $TYPE == br || $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 @@ -447,58 +423,61 @@ docker_network_start(){ done # 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}')) + SHIM=shim-$NETWORK if [[ $DOCKER_ALLOW_ACCESS == yes && -n $IPV4 ]]; then - # create shim interface and copy parent IPv4 address to 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 + # create shim interface + if [[ ! -e $SYSTEM/$SHIM ]]; then + run ip link add link $NETWORK name $SHIM type $ATTACH mode $MODE + run ip link set $SHIM up + fi # disable IPv6 on shim interface - echo 1 >$CONF6/$LINK/disable_ipv6 - 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 + echo 1 >$CONF6/$SHIM/disable_ipv6 + run ip -6 addr flush dev $SHIM + # copy parent IPv4 address to shim interface + run ip -4 addr add $IPV4 dev $SHIM metric 0 + add_entry "$SHIM $IPV4 metric 0" + GW4=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}') + if [[ -n $GW4 ]]; then + run ip -4 route add default via $GW4 dev $SHIM metric 0 + add_entry "$SHIM GW4 default via $GW4 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 + log "created network $SHIM for host access" + elif [[ -e $SYSTEM/$SHIM ]]; then + # remove shim interface assignment + del_entry $SHIM + run ip -4 addr del $IPV4 dev $SHIM metric 0 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 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 + 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 + run ip -4 addr add $IPV4 dev $VHOST metric 0 + add_entry "$VHOST $IPV4 metric 0" + GW4=$(ip -4 route show to default dev $NETWORK | awk '{print $3;exit}') + if [[ -n $GW4 ]]; then + run ip -4 route add default via $GW4 dev $VHOST metric 0 + add_entry "$VHOST GW4 default via $GW4 metric 0" fi + log "created network $VHOST for host access" + elif [[ -n $IPV4 && -e $SYSTEM/$VHOST ]]; then + # remove vhost interface assignment + del_entry $VHOST + run ip -4 addr del $IPV4 dev $VHOST metric 0 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,30 +485,27 @@ 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 + IPV4=$(ip -4 -br addr show scope global primary dev $NETWORK | awk '{print $3;exit}') + [[ $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 + if [[ $TYPE == br || $TYPE == wlan ]]; then + SHIM=shim-$NETWORK + if [[ -e $SYSTEM/$SHIM ]]; then + del_entry $SHIM + run ip -4 addr del $IPV4 dev $SHIM metric 0 fi else VHOST=vhost${NETWORK//[^0-9.]/} - [[ -e $SYSTEM/$VHOST ]] && run ip addr flush dev $VHOST + if [[ -e $SYSTEM/$VHOST ]]; then + del_entry $VHOST + run ip -4 addr del $IPV4 dev $VHOST metric 0 + fi fi done done @@ -549,7 +525,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 +626,6 @@ case "$1" in docker_service_start docker_network_start docker_container_start &>/dev/null & - disown ;; 'stop') docker_container_stop @@ -670,7 +645,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 6670cbbfc0..4e1075b094 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, June 2025 # Adapted by Bergware for use in Unraid OS - April 2016 # - improved interface configuration @@ -81,6 +81,14 @@ # - 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 + +# Adapted by Bergware for use in Unraid OS - June 2025 +# - revert metric assignment to static addresses +# - remove duplicate ipv6 parameters +# - improve gateway creation + ########### # LOGGING # ########### @@ -122,22 +130,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 +154,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 +175,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 +184,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 +203,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 +217,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 +244,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 +262,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 +274,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 +284,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 +292,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,41 +319,41 @@ 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 + echo $2 >$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 + [[ -d $CONF6/${IFACE/$1/$2} ]] && ipv6_ra ${IFACE/$1/$2} $4 + [[ -d $CONF6/${IFACE/$1/$3} ]] && ipv6_ra ${IFACE/$1/$3} $4 } -# 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 + [[ -d $CONF6/$IFACE ]] && ipv6_ra $IFACE $1 + [[ -d $CONF6/$VHOST ]] && ipv6_ra $VHOST $1 # repeat action on related interfaces if [[ ${IFACE:0:4} == bond ]]; then - ipv6_conf bond br eth $1 $2 + ipv6_conf bond br eth $1 elif [[ ${IFACE:0:2} == br ]]; then - ipv6_conf br bond eth $1 $2 + ipv6_conf br bond eth $1 else - ipv6_conf eth bond br $1 $2 + ipv6_conf eth bond br $1 fi sleep 1 } -# function to assign IP address +# assign IP address ipaddr_up(){ if [[ -z $RENEW ]]; then # disable IPv6 per interface when IPv4 only @@ -360,7 +371,7 @@ ipaddr_up(){ fi if [[ $DHCP == yes ]]; then # bring up interface using DHCP/SLAAC - [[ -z $RENEW ]] && ipv6_addr 1 1 + [[ -z $RENEW ]] && ipv6_addr 1 DHCP_OPTIONS="-q -n -p -t ${DHCP_TIMEOUT[$i]:-10}" [[ -n $DHCP_HOSTNAME ]] && DHCP_OPTIONS="$DHCP_OPTIONS -h $DHCP_HOSTNAME" [[ $DHCP_KEEP_RESOLV == yes ]] && DHCP_OPTIONS="$DHCP_OPTIONS -C resolv.conf" @@ -371,7 +382,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,32 +396,32 @@ 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 + ipv6_addr 0 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 dev $IFACE metric 1 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 dev $IFACE metric 1 [[ -n $PRIV6 && -d $CONF6/$IFACE ]] && echo 0 >$CONF6/$IFACE/use_tempaddr fi fi else # bring up interface without IP address - ipv6_addr 0 0 + ipv6_addr 0 ipaddr_down 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 +433,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 +447,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 +464,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 +566,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 +623,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 @@ -621,9 +632,8 @@ gateway_up(){ i=(${x/,/ }) [[ -z ${i[1]} ]] && DEV=${IFNAME[$i]} || DEV=${IFNAME[$i]}.${VLANID[$x]} 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 ") + AD="metric ${METRIC[$x]:-1}" + EXIST=$(ip -4 route show to default via $(unzero $GW) dev $DEV | grep -m1 "$AD ") [[ $IP != ipv6 && -z $EXIST ]] && run ip -4 route add default via $(unzero $GW) dev $DEV $AD done for GW6 in ${GATEWAY6[@]}; do @@ -633,14 +643,13 @@ gateway_up(){ i=(${x/,/ }) [[ -z ${i[1]} ]] && DEV=${IFNAME[$i]} || DEV=${IFNAME[$i]}.${VLANID[$x]} 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 ") + AD6="metric ${METRIC6[$x]:-1}" + EXIST=$(ip -6 route show to default via $(unzero6 $GW6) dev $DEV | grep -m1 "$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 +658,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 +668,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 +682,7 @@ start(){ gateway_up } -# function to stop network +# stop network stop(){ gateway_down for INTERFACE in ${IFNAME[@]}; do @@ -682,7 +691,7 @@ stop(){ lo_down } -# function to show network status +# show network status status(){ echo "INTERFACE STATE INFORMATION" echo "========================================================================" @@ -693,38 +702,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 99861be3b4..fb5f5f9d03 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 af02d607c2..b43f02b5c1 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 diff --git a/etc/rc.d/rc.nginx b/etc/rc.d/rc.nginx index 0f903a5699..c9298385ea 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 c2f419784e..d35744affe 100755 --- a/etc/rc.d/rc.wireless +++ b/etc/rc.d/rc.wireless @@ -6,12 +6,17 @@ # # Bergware - created for Unraid OS, January 2025 +# Adapted by Bergware for use in Unraid OS - June 2025 +# - put metric value at end of assignment + 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 +33,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 +141,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 dev $PORT metric $INDEX + # 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 dev shim-$PORT metric $(($INDEX - 1)) + fi + fi + [[ -n $GATEWAY4 ]] && run ip -4 route add default via $GATEWAY4 dev $PORT metric $INDEX 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 dev $PORT metric $INDEX + [[ -n $GATEWAY6 ]] && run ip -6 route add default via $GATEWAY6 dev $PORT metric $INDEX fi fi if [[ $DNS == yes ]]; then @@ -148,17 +171,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 +272,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 - run pkill wpa_supplicant + 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 --ns $$ 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 +334,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 +343,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 afe6c39e17..4938f55392 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(){ +# 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 - # 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 + 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 @@ -124,13 +145,12 @@ for ((i=0; i<${SYSNICS:-1}; i++)); do echo "DESCRIPTION:0=\"${DESCRIPTION[$i]}\"" >>$INI echo "PROTOCOL:0=\"${PROTOCOL[$i]}\"" >>$INI echo "USE_DHCP:0=\"${USE_DHCP[$i]}\"" >>$INI - 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 +159,19 @@ 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}')" + if [[ -n $IPV4 ]]; then + echo "$IPV4" >>$STA + GW4="$(ip -4 route show to default dev $IFACE)" + [[ -n $GW4 ]] && echo "$IFACE GW4 $GW4" >>$STA + fi 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,25 +184,31 @@ 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}')" + if [[ -n $IPV6 ]]; then + echo "$IPV6" >>$STA + GW6="$(ip -6 route show to default dev $IFACE)" + [[ -n $GW6 ]] && echo "$IFACE GW6 $GW6" >>$STA + fi fi echo "USE_MTU=\"${USE_MTU[$i]}\"" >>$INI echo "MTU=\"${MTU[$i]}\"" >>$INI if [[ -n ${VLANS[$i]} ]]; then # process VLAN interfaces echo "TYPE=\"trunk\"" >>$INI - for ((j=1;j<${VLANS[$i]};j++)); do + for ((j=1; j<${VLANS[$i]}; j++)); do echo "VLANID:$j=\"${VLANID[$i,$j]}\"" >>$INI echo "DESCRIPTION:$j=\"${DESCRIPTION[$i,$j]}\"" >>$INI echo "PROTOCOL:$j=\"${PROTOCOL[$i,$j]}\"" >>$INI echo "USE_DHCP:$j=\"${USE_DHCP[$i,$j]}\"" >>$INI - echo "USE_GW4:$j=\"${USE_GW4[$i,$j]}\"" >>$INI + DEV=$IFACE.${VLANID[$i,$j]} 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 +217,20 @@ 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}')" + if [[ -n $IPV4 ]]; then + echo "${IPV4/@$IFACE/}" >>$STA + GW4="$(ip -4 route show to default dev $DEV)" + [[ -n $GW4 ]] && echo "$DEV GW4 $GW4" >>$STA + fi fi echo "USE_DHCP6:$j=\"${USE_DHCP6[$i,$j]}\"" >>$INI - echo "USE_GW6:$j=\"${USE_GW6[$i,$j]}\"" >>$INI + DEV=$IFACE.${VLANID[$i,$j]} 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 +243,13 @@ 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}')" + if [[ -n $IPV6 ]]; then + echo "${IPV6/@$IFACE/}" >>$STA + GW6="$(ip -6 route show to default dev $DEV)" + [[ -n $GW6 ]] && echo "$DEV GW6 $GW6" >>$STA + fi fi done else @@ -214,6 +259,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 +288,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 0000000000..110e92f572 --- /dev/null +++ b/sbin/monitor_interface @@ -0,0 +1,96 @@ +#!/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, June 2025 + +FILE=/var/local/emhttp/statics.ini +SYSTEM=/sys/class/net + +md5(){ + [[ -r $FILE ]] && md5sum $FILE | awk '{print $1;exit}' +} + +port(){ + [[ -e $SYSTEM/$1 ]] +} + +carrier(){ + cat $SYSTEM/$1/carrier 2>/dev/null +} + +state(){ + local n NEW OLD HOLD + OLD=$(carrier $1) + HOLD=4 + # new state should hold for at least 4 seconds + for n in {1..4}; do + sleep 1 + NEW=$(carrier $1) + [[ $NEW != $OLD ]] && ((HOLD--)) + done + [[ $HOLD -eq 0 ]] && echo $NEW || echo $OLD +} + +init(){ + TASK=() + if [[ -r $FILE ]]; then + # initialize values from file, maintained by 'create_network_ini' + while IFS=$'\n' read -r ROW; do + TASK+=("$ROW") + done <$FILE + fi + MD5=$(md5) +} + +while :; do + if port wlan0; then + # monitor file content changes + [[ $MD5 != $(md5) ]] && init + LAST= + for i in ${!TASK[@]}; do + ADDR=(${TASK[$i]}) + PORT=${ADDR[0]} + if port $PORT; then + [[ $LAST != ${PORT%.*} ]] && STATE=$(state ${PORT%.*}) + case ${ADDR[1]} in + GW4) + if [[ $STATE == 1 ]]; then + # no existing default and new default is defined? + IPV4=$(ip -4 -br addr show scope global primary dev $PORT | awk '{print $3;exit}') + [[ -n $IPV4 && "${TASK[$i]}" =~ "default" && -z $(ip -4 route show to default dev $PORT) ]] && ip -4 route add dev ${TASK[$i]/GW4/} + fi + ;; + GW6) + if [[ $STATE == 1 ]]; then + # no existing default and new default is defined? + IPV6=$(ip -6 -br addr show scope global primary -deprecated dev $PORT | awk '{print $3;exit}') + [[ -n $IPV6 && "${TASK[$i]}" =~ "default" && -z $(ip -6 route show to default dev $PORT) ]] && ip -6 route add dev ${TASK[$i]/GW6/} + fi + ;; + *) + case $STATE in + 0) # down + # IP address present, remove it + [[ -n $(ip -br addr show to ${ADDR[1]%/*} dev $PORT) ]] && ip addr del dev ${TASK[$i]} + ;; + 1) # up + # IP address not present? create it + [[ ! -f /var/tmp/$PORT.down && -z $(ip -br addr show to ${ADDR[1]%/*} dev $PORT) ]] && ip addr add dev ${TASK[$i]} + esac + esac + LAST=${PORT%.*} + fi + done + fi + # check every 3 seconds + sleep 3 +done & + diff --git a/sbin/monitor_nchan b/sbin/monitor_nchan index 76d50e7691..297af5632a 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 %% diff --git a/sbin/mover b/sbin/mover index e956cb7bf8..bab99064bd 100755 --- a/sbin/mover +++ b/sbin/mover @@ -27,7 +27,9 @@ CFGPATH="/boot/config/shares" DEBUGGING="" move() { - /usr/bin/find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $DEBUGGING + find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING + # second pass to clean up leftover empty directories + find "$1" -depth -type d 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING } start() { @@ -104,7 +106,7 @@ empty() { for SHAREPATH in /mnt/$DISK/* ; do SHARE=$(basename "$SHAREPATH") if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then - find "$SHAREPATH" -depth 2>/dev/null | /usr/libexec/unraid/move -e $DEBUGGING + move "$SHAREPATH" "-e" fi done