isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); } rmdir($dir); } function find_first_physical_ethernet(): ?string { foreach (scandir('/sys/class/net') as $iface) { if ($iface === '.' || $iface === '..' || $iface === 'lo') { continue; } $net = "/sys/class/net/$iface"; if (!is_link("$net/device")) { continue; } $type = @trim(file_get_contents("$net/type")); if ($type !== '1') { continue; } if (is_dir("$net/wireless")) { continue; } if (is_dir("$net/bridge")) { continue; } $addrAssignType = @trim(file_get_contents("$net/addr_assign_type")); if ($addrAssignType !== '0') { continue; } return $iface; } return null; } function build_interface(array $cfg, string $type): array { $out = []; /* ---------- IPv4 ---------- */ if ($cfg['mode'] === 'dhcp') { $out['dhcp4'] = true; } elseif ($cfg['mode'] === 'static') { $out['dhcp4'] = false; if ($cfg["network_{$type}_ip"] !== '') { $out['addresses'][] = $cfg["network_{$type}_ip"]; // already CIDR } if ($cfg["network_{$type}_gateway"] !== '') { $out['gateway4'] = $cfg["network_{$type}_gateway"]; } $dns = array_filter([ $cfg["network_{$type}_dns1"], $cfg["network_{$type}_dns2"] ]); if ($dns) { $out['nameservers']['addresses'] = array_values($dns); } } else { $out['dhcp4'] = false; } /* ---------- IPv6 ---------- */ if ($cfg['modev6'] === 'auto') { $out['dhcp6'] = true; $out['accept-ra'] = true; } elseif ($cfg['modev6'] === 'dhcpv6') { $out['dhcp6'] = true; $out['accept-ra'] = false; } elseif ($cfg['modev6'] === 'static') { $out['dhcp6'] = false; $out['accept-ra'] = false; if ( $cfg["network_{$type}_ipv6"] !== '' && $cfg["network_{$type}_ipv6_prefix"] !== '' ) { $out['addresses'][] = $cfg["network_{$type}_ipv6"] . '/' . $cfg["network_{$type}_ipv6_prefix"]; } if ($cfg["network_{$type}_ipv6_gateway"] !== '') { $out['gateway6'] = $cfg["network_{$type}_ipv6_gateway"]; } $dns6 = array_filter([ $cfg["network_{$type}_ipv6_dns1"], $cfg["network_{$type}_ipv6_dns2"] ]); if ($dns6) { $out['nameservers']['addresses'] = array_merge($out['nameservers']['addresses'] ?? [], $dns6); } } else { $out['dhcp6'] = false; $out['accept-ra'] = false; } return $out; } function generate_netplan(array $data, string $iface): array { $netplan = [ 'network' => [ 'version' => 2, 'renderer' => 'networkd', 'ethernets' => [], 'vlans' => [] ] ]; /* ---------- BASE INTERFACE (PRIMARY FIRST) ---------- */ if ( $data['primary']['mode'] !== 'disabled' || $data['primary']['modev6'] !== 'disabled' ) { $base_vlan = trim($data['primary']['network_primary_vlan'] ?? ''); if ($base_vlan === '') { // Configure base NIC $netplan['network']['ethernets'][$iface] = build_interface($data['primary'], 'primary'); } } /* ---------- BASE INTERFACE (SECONDARY ONLY IF NOT SET) ---------- */ if ( !isset($netplan['network']['ethernets'][$iface]) && ( $data['secondary']['mode'] !== 'disabled' || $data['secondary']['modev6'] !== 'disabled' ) ) { $base_vlan = trim($data['secondary']['network_secondary_vlan'] ?? ''); if ($base_vlan === '') { $netplan['network']['ethernets'][$iface] = build_interface($data['secondary'], 'secondary'); } } /* ---------- VLANs (PRIMARY) ---------- */ $p_vlan = trim($data['primary']['network_primary_vlan'] ?? ''); if ($p_vlan !== '') { // Ensure base interface exists $netplan['network']['ethernets'][$iface] ??= new stdClass(); $netplan['network']['vlans']["{$iface}.{$p_vlan}"] = array_merge( ['id' => (int)$p_vlan, 'link' => $iface], build_interface($data['primary'], 'primary') ); } /* ---------- VLANs (SECONDARY) ---------- */ $s_vlan = trim($data['secondary']['network_secondary_vlan'] ?? ''); if ($s_vlan !== '') { $netplan['network']['ethernets'][$iface] ??= new stdClass(); $netplan['network']['vlans']["{$iface}.{$s_vlan}"] = array_merge( ['id' => (int)$s_vlan, 'link' => $iface], build_interface($data['secondary'], 'secondary') ); } /* ---------- Normalize vlans ---------- */ if (empty($netplan['network']['vlans'])) { $netplan['network']['vlans'] = new stdClass(); } return $netplan; } function validate_config(array $data): bool { $p_enabled = ( $data['primary']['mode'] !== 'disabled' || $data['primary']['modev6'] !== 'disabled' ); $s_enabled = ( $data['secondary']['mode'] !== 'disabled' || $data['secondary']['modev6'] !== 'disabled' ); $p_vlan = trim($data['primary']['network_primary_vlan'] ?? ''); $s_vlan = trim($data['secondary']['network_secondary_vlan'] ?? ''); /* If both enabled → at least one VLAN required */ if ($p_enabled && $s_enabled && $p_vlan === '' && $s_vlan === '') { echo ""; return false; } /* Block duplicate VLAN IDs */ if ($p_vlan !== '' && $s_vlan !== '' && $p_vlan === $s_vlan) { echo ""; return false; } return true; } function netplan_yaml(array $data, int $indent = 0): string { $out = ''; $pad = str_repeat(' ', $indent); foreach ($data as $key => $value) { if ($value instanceof stdClass) { $out .= "{$pad}{$key}: {}\n"; continue; } if (is_bool($value)) { $out .= "{$pad}{$key}: " . ($value ? 'true' : 'false') . "\n"; continue; } if (!is_array($value)) { $out .= "{$pad}{$key}: {$value}\n"; continue; } if (array_keys($value) === range(0, count($value) - 1)) { $out .= "{$pad}{$key}:\n"; foreach ($value as $item) { $out .= "{$pad} - {$item}\n"; } continue; } $out .= "{$pad}{$key}:\n"; $out .= netplan_yaml($value, $indent + 1); } return $out; }