From 537b9cab037aecfd18edef156dd3ea55072918e9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Mar 2022 01:57:57 +1100 Subject: Rework network config (#1001) * Update network configuration * Rework network configuration * Update documentation * Fix flake8 * Update Co-authored-by: Daniel Girtler Co-authored-by: Anton Hvornum --- archinstall/lib/installer.py | 43 ++++++----- archinstall/lib/models/network_configuration.py | 97 +++++++++++++++++++++++++ archinstall/lib/user_interaction.py | 35 +++++---- 3 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 archinstall/lib/models/network_configuration.py (limited to 'archinstall/lib') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index cf643b27..894bcc2a 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -35,6 +35,7 @@ __packages__ = ["base", "base-devel", "linux-firmware", "linux", "linux-lts", "l __accessibility_packages__ = ["brltty", "espeakup", "alsa-utils"] from .pacman import run_pacman +from .models.network_configuration import NetworkConfiguration class InstallationFile: @@ -479,37 +480,35 @@ class Installer: def drop_to_shell(self) -> None: subprocess.check_call(f"/usr/bin/arch-chroot {self.target}", shell=True) - def configure_nic(self, - nic :str, - dhcp :bool = True, - ip :Optional[str] = None, - gateway :Optional[str] = None, - dns :Optional[str] = None, - *args :str, - **kwargs :str - ) -> None: + def configure_nic(self, network_config: NetworkConfiguration) -> None: from .systemd import Networkd - if dhcp: - conf = Networkd(Match={"Name": nic}, Network={"DHCP": "yes"}) + if network_config.dhcp: + conf = Networkd(Match={"Name": network_config.iface}, Network={"DHCP": "yes"}) else: - assert ip + network = {"Address": network_config.ip} + if network_config.gateway: + network["Gateway"] = network_config.gateway + if network_config.dns: + dns = network_config.dns + network["DNS"] = dns if isinstance(dns, list) else [dns] - network = {"Address": ip} - if gateway: - network["Gateway"] = gateway - if dns: - assert type(dns) == list - network["DNS"] = dns - - conf = Networkd(Match={"Name": nic}, Network=network) + conf = Networkd(Match={"Name": network_config.iface}, Network=network) for plugin in plugins.values(): if hasattr(plugin, 'on_configure_nic'): - if (new_conf := plugin.on_configure_nic(nic, dhcp, ip, gateway, dns)): + new_conf = plugin.on_configure_nic( + network_config.iface, + network_config.dhcp, + network_config.ip, + network_config.gateway, + network_config.dns + ) + + if new_conf: conf = new_conf - with open(f"{self.target}/etc/systemd/network/10-{nic}.network", "a") as netconf: + with open(f"{self.target}/etc/systemd/network/10-{network_config.iface}.network", "a") as netconf: netconf.write(str(conf)) def copy_iso_network_config(self, enable_services :bool = False) -> bool: diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py new file mode 100644 index 00000000..f1ee4c3f --- /dev/null +++ b/archinstall/lib/models/network_configuration.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import List, Optional, Dict + +from ... import log + + +class NicType(str, Enum): + ISO = "iso" + NM = "nm" + MANUAL = "manual" + + +@dataclass +class NetworkConfiguration: + type: NicType + iface: str = None + ip: str = None + dhcp: bool = True + gateway: str = None + dns: List[str] = None + + def __str__(self): + if self.is_iso(): + return "Copy ISO configuration" + elif self.is_network_manager(): + return "Use NetworkManager" + elif self.is_manual(): + if self.dhcp: + return f'iface={self.iface}, dhcp=auto' + else: + return f'iface={self.iface}, ip={self.ip}, dhcp=staticIp, gateway={self.gateway}, dns={self.dns}' + else: + return 'Unknown type' + + # for json serialization when calling json.dumps(...) on this class + def json(self): + return self.__dict__ + + @classmethod + def parse_arguments(cls, config: Dict[str, str]) -> Optional["NetworkConfiguration"]: + nic_type = config.get('type', None) + + if not nic_type: + return None + + try: + type = NicType(nic_type) + except ValueError: + options = [e.value for e in NicType] + log(_('Unknown nic type: {}. Possible values are {}').format(nic_type, options), fg='red') + exit(1) + + if type == NicType.MANUAL: + if config.get('dhcp', False) or not any([config.get(v) for v in ['ip', 'gateway', 'dns']]): + return NetworkConfiguration(type, iface=config.get('iface', '')) + + ip = config.get('ip', '') + if not ip: + log('Manual nic configuration with no auto DHCP requires an IP address', fg='red') + exit(1) + + return NetworkConfiguration( + type, + iface=config.get('iface', ''), + ip=ip, + gateway=config.get('gateway', ''), + dns=config.get('dns', []), + dhcp=False + ) + else: + return NetworkConfiguration(type) + + def is_iso(self) -> bool: + return self.type == NicType.ISO + + def is_network_manager(self) -> bool: + return self.type == NicType.NM + + def is_manual(self) -> bool: + return self.type == NicType.MANUAL + + def config_installer(self, installation: 'Installer'): + # If user selected to copy the current ISO network configuration + # Perform a copy of the config + if self.is_iso(): + installation.copy_iso_network_config(enable_services=True) # Sources the ISO network configuration to the install medium. + elif self.is_network_manager(): + installation.add_additional_packages("networkmanager") + installation.enable_service('NetworkManager.service') + # Otherwise, if a interface was selected, configure that interface + elif self.is_manual(): + installation.configure_nic(self) + installation.enable_service('systemd-networkd') + installation.enable_service('systemd-resolved') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 7524dd8b..1f62b7fd 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -14,6 +14,7 @@ from typing import List, Any, Optional, Dict, Union, TYPE_CHECKING # https://stackoverflow.com/a/39757388/929999 from .menu.text_input import TextInput +from .models.network_configuration import NetworkConfiguration, NicType if TYPE_CHECKING: from .disk.partition import Partition @@ -450,11 +451,12 @@ def ask_additional_packages_to_install(pre_set_packages :List[str] = []) -> List return packages -def ask_to_configure_network(preset :Dict[str, Any] = {}) -> Dict[str, Any]: - # Optionally configure one network interface. - # while 1: - # {MAC: Ifname} +def ask_to_configure_network(preset :Dict[str, Any] = {}) -> Optional[NetworkConfiguration]: + """ + Configure the network on the newly installed system + """ interfaces = { + 'none': str(_('No network configuration')), 'iso_config': str(_('Copy ISO network configuration to installation')), 'network_manager': str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)')), **list_interfaces() @@ -473,17 +475,17 @@ def ask_to_configure_network(preset :Dict[str, Any] = {}) -> Dict[str, Any]: except ValueError: pass - nic = Menu(_('Select one network interface to configure'), interfaces.values(),cursor_index=cursor_idx).run() + nic = Menu(_('Select one network interface to configure'), interfaces.values(), cursor_index=cursor_idx, sort=False).run() if not nic: - return {} - - # nic = network_manager_nt + return None - if nic == interfaces['iso_config']: - return {'type': 'iso_config'} + if nic == interfaces['none']: + return None + elif nic == interfaces['iso_config']: + return NetworkConfiguration(NicType.ISO) elif nic == interfaces['network_manager']: - return {'type': 'network_manager', 'NetworkManager': True} + return NetworkConfiguration(NicType.NM) else: # Current workaround: # For selecting modes without entering text within brackets, @@ -543,10 +545,17 @@ def ask_to_configure_network(preset :Dict[str, Any] = {}) -> Dict[str, Any]: if len(dns_input): dns = dns_input.split(' ') - return {'type': nic, 'dhcp': False, 'ip': ip, 'gateway': gateway, 'dns': dns} + return NetworkConfiguration( + NicType.MANUAL, + iface=nic, + ip=ip, + gateway=gateway, + dns=dns, + dhcp=False + ) else: # this will contain network iface names - return {'type': nic} + return NetworkConfiguration(NicType.MANUAL, iface=nic) def partition_overlap(partitions :list, start :str, end :str) -> bool: -- cgit v1.2.3-54-g00ecf