Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall
diff options
context:
space:
mode:
authorDaniel <blackrabbit256@gmail.com>2022-04-22 21:24:12 +1000
committerGitHub <noreply@github.com>2022-04-22 13:24:12 +0200
commit477b5b120e120766d789a691fce60cec843aff43 (patch)
treed2580a7510c2c3ffff694d6145ee1b93c1fd6ea6 /archinstall
parent2529d6a5f59eb6a16f95bf9d1117a6033c527df9 (diff)
Support for multiple network interfaces (#1052)
* Support for multiple network interfaces * Fix mypy * Fix flake8 Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
Diffstat (limited to 'archinstall')
-rw-r--r--archinstall/__init__.py5
-rw-r--r--archinstall/lib/menu/global_menu.py15
-rw-r--r--archinstall/lib/menu/list_manager.py126
-rw-r--r--archinstall/lib/menu/selection_menu.py4
-rw-r--r--archinstall/lib/models/network_configuration.py220
-rw-r--r--archinstall/lib/user_interaction/manage_users_conf.py33
-rw-r--r--archinstall/lib/user_interaction/network_conf.py161
-rw-r--r--archinstall/lib/user_interaction/subvolume_config.py33
8 files changed, 344 insertions, 253 deletions
diff --git a/archinstall/__init__.py b/archinstall/__init__.py
index 32665efd..638ac08a 100644
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -12,6 +12,7 @@ from .lib.installer import __packages__, Installer, accessibility_tools_in_use
from .lib.locale_helpers import *
from .lib.luks import *
from .lib.mirrors import *
+from .lib.models.network_configuration import NetworkConfigurationHandler
from .lib.networking import *
from .lib.output import *
from .lib.models.dataclasses import (
@@ -207,7 +208,9 @@ def load_config():
if arguments.get('servers', None) is not None:
storage['_selected_servers'] = arguments.get('servers', None)
if arguments.get('nic', None) is not None:
- arguments['nic'] = NetworkConfiguration.parse_arguments(arguments.get('nic'))
+ handler = NetworkConfigurationHandler()
+ handler.parse_arguments(arguments.get('nic'))
+ arguments['nic'] = handler.configuration
def post_process_arguments(arguments):
storage['arguments'] = arguments
diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py
index 50d3180b..fba0ce29 100644
--- a/archinstall/lib/menu/global_menu.py
+++ b/archinstall/lib/menu/global_menu.py
@@ -1,11 +1,12 @@
from __future__ import annotations
-from typing import Any, List, Optional
+from typing import Any, List, Optional, Union
from ..menu import Menu
from ..menu.selection_menu import Selector, GeneralMenu
from ..general import SysCommand, secret
from ..hardware import has_uefi
+from ..models import NetworkConfiguration
from ..storage import storage
from ..output import log
from ..profiles import is_desktop_profile
@@ -139,7 +140,7 @@ class GlobalMenu(GeneralMenu):
Selector(
_('Configure network'),
ask_to_configure_network,
- display_func=lambda x: x if x else _('Not configured, unavailable unless setup manually'),
+ display_func=lambda x: self._prev_network_configuration(x),
default={})
self._menu_options['timezone'] = \
Selector(
@@ -192,6 +193,16 @@ class GlobalMenu(GeneralMenu):
return _('Install ({} config(s) missing)').format(missing)
return 'Install'
+ def _prev_network_configuration(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str:
+ if not cur_value:
+ return _('Not configured, unavailable unless setup manually')
+ else:
+ if isinstance(cur_value, list):
+ ifaces = [x.iface for x in cur_value]
+ return f'Configured ifaces: {ifaces}'
+ else:
+ return str(cur_value)
+
def _prev_install_missing_config(self) -> Optional[str]:
if missing := self._missing_configs():
text = str(_('Missing configurations:\n'))
diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py
index 5b4f720c..5da34e06 100644
--- a/archinstall/lib/menu/list_manager.py
+++ b/archinstall/lib/menu/list_manager.py
@@ -89,10 +89,21 @@ from .text_input import TextInput
from .menu import Menu
from os import system
from copy import copy
-from typing import Union
+from typing import Union, Any, List, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ _: Any
+
class ListManager:
- def __init__(self,prompt :str, base_list :Union[list,dict] ,base_actions :list = None,null_action :str = None, default_action :Union[str,list] = None, header :Union[str,list] = None):
+ def __init__(
+ self,
+ prompt :str,
+ base_list :Union[list,dict] ,
+ base_actions :list = None,
+ null_action :str = None,
+ default_action :Union[str,list] = None,
+ header :Union[str,list] = None):
"""
param :prompt Text which will appear at the header
type param: string | DeferredTranslation
@@ -115,16 +126,16 @@ class ListManager:
"""
explainer = str(_('\n Choose an object from the list, and select one of the available actions for it to execute'))
- self.prompt = prompt + explainer if prompt else explainer
+ self._prompt = prompt + explainer if prompt else explainer
- self.null_action = str(null_action) if null_action else None
+ self._null_action = str(null_action) if null_action else None
if not default_action:
- self.default_action = [self.null_action,]
+ self._default_action = [self._null_action]
elif isinstance(default_action,(list,tuple)):
- self.default_action = default_action
+ self._default_action = default_action
else:
- self.default_action = [str(default_action),]
+ self._default_action = [str(default_action),]
self.header = header if header else None
self.cancel_action = str(_('Cancel'))
@@ -133,24 +144,23 @@ class ListManager:
self.bottom_list = [self.confirm_action,self.cancel_action]
self.bottom_item = [self.cancel_action]
self.base_actions = base_actions if base_actions else [str(_('Add')),str(_('Copy')),str(_('Edit')),str(_('Delete'))]
-
self.base_data = base_list
- self.data = copy(base_list) # as refs, changes are immediate
+ self._data = copy(base_list) # as refs, changes are immediate
# default values for the null case
self.target = None
- self.action = self.null_action
- if len(self.data) == 0 and self.null_action:
- self.exec_action()
+ self.action = self._null_action
+ if len(self._data) == 0 and self._null_action:
+ self.exec_action(self._data)
def run(self):
while True:
- self.data_formatted = self.reformat()
- options = self.data_formatted + [self.separator]
- if self.default_action:
- options += self.default_action
+ self._data_formatted = self.reformat(self._data)
+ options = self._data_formatted + [self.separator]
+ if self._default_action:
+ options += self._default_action
options += self.bottom_list
system('clear')
- target = Menu(self.prompt,
+ target = Menu(self._prompt,
options,
sort=False,
clear_screen=False,
@@ -162,53 +172,53 @@ class ListManager:
break
if target and target == self.separator:
continue
- if target and target in self.default_action:
+ if target and target in self._default_action:
self.action = target
target = None
self.target = None
- self.exec_action()
+ self.exec_action(self._data)
continue
- if isinstance(self.data,dict):
- key = list(self.data.keys())[self.data_formatted.index(target)]
- self.target = {key: self.data[key]}
+ if isinstance(self._data,dict):
+ key = list(self._data.keys())[self._data_formatted.index(target)]
+ self.target = {key: self._data[key]}
else:
- self.target = self.data[self.data_formatted.index(target)]
+ self.target = self._data[self._data_formatted.index(target)]
# Possible enhacement. If run_actions returns false a message line indicating the failure
self.run_actions(target)
if not target or target == self.cancel_action: # TODO dubious
return self.base_data # return the original list
else:
- return self.data
+ return self._data
def run_actions(self,prompt_data=None):
options = self.action_list() + self.bottom_item
prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target)
- self.action = Menu(prompt,
- options,
- sort=False,
- skip=False,
- clear_screen=False,
- clear_menu_on_exit=False,
- preset_values=self.bottom_item,
- show_search_hint=False).run()
- if self.action == self.cancel_action:
+ self.action = Menu(
+ prompt,
+ options,
+ sort=False,
+ clear_screen=False,
+ clear_menu_on_exit=False,
+ preset_values=self.bottom_item,
+ show_search_hint=False).run()
+ if not self.action or self.action == self.cancel_action:
return False
else:
- return self.exec_action()
+ return self.exec_action(self._data)
"""
The following methods are expected to be overwritten by the user if the needs of the list are beyond the simple case
"""
- def reformat(self):
+ def reformat(self, data: Any) -> List[Any]:
"""
method to get the data in a format suitable to be shown
- It is executed once for run loop and processes the whole self.data structure
+ It is executed once for run loop and processes the whole self._data structure
"""
- if isinstance(self.data,dict):
- return list(map(lambda x:f"{x} : {self.data[x]}",self.data))
+ if isinstance(data,dict):
+ return list(map(lambda x:f"{x} : {data[x]}",data))
else:
- return list(map(lambda x:str(x),self.data))
+ return list(map(lambda x:str(x),data))
def action_list(self):
"""
@@ -217,32 +227,32 @@ class ListManager:
"""
return self.base_actions
- def exec_action(self):
+ def exec_action(self, data: Any):
"""
what's executed one an item (self.target) and one action (self.action) is selected.
Should be overwritten by the user
- The result is expected to update self.data in this routine, else it is ignored
+ The result is expected to update self._data in this routine, else it is ignored
The basic code is useful for simple lists and dictionaries (key:value pairs, both strings)
"""
# TODO guarantee unicity
- if isinstance(self.data,list):
+ if isinstance(self._data,list):
if self.action == str(_('Add')):
self.target = TextInput(_('Add :'),None).run()
- self.data.append(self.target)
+ self._data.append(self.target)
if self.action == str(_('Copy')):
while True:
target = TextInput(_('Copy to :'),self.target).run()
if target != self.target:
- self.data.append(self.target)
+ self._data.append(self.target)
break
elif self.action == str(_('Edit')):
tgt = self.target
- idx = self.data.index(self.target)
+ idx = self._data.index(self.target)
result = TextInput(_('Edite :'),tgt).run()
- self.data[idx] = result
+ self._data[idx] = result
elif self.action == str(_('Delete')):
- del self.data[self.data.index(self.target)]
- elif isinstance(self.data,dict):
+ del self._data[self._data.index(self.target)]
+ elif isinstance(self._data,dict):
# allows overwrites
if self.target:
origkey,origval = list(self.target.items())[0]
@@ -252,27 +262,15 @@ class ListManager:
if self.action == str(_('Add')):
key = TextInput(_('Key :'),None).run()
value = TextInput(_('Value :'),None).run()
- self.data[key] = value
+ self._data[key] = value
if self.action == str(_('Copy')):
while True:
key = TextInput(_('Copy to new key:'),origkey).run()
if key != origkey:
- self.data[key] = origval
+ self._data[key] = origval
break
elif self.action == str(_('Edit')):
value = TextInput(_(f'Edit {origkey} :'),origval).run()
- self.data[origkey] = value
+ self._data[origkey] = value
elif self.action == str(_('Delete')):
- del self.data[origkey]
-
- return True
-
-
-if __name__ == "__main__":
- # opciones = ['uno','dos','tres','cuatro']
- # opciones = archinstall.list_mirrors()
- opciones = {'uno':1,'dos':2,'tres':3,'cuatro':4}
- acciones = ['editar','borrar','aƱadir']
- cabecera = ["En Jaen Donde Resido","Vive don Lope de Sosa"]
- opciones = ListManager('Vamos alla',opciones,None,_('Add'),default_action=acciones,header=cabecera).run()
- print(opciones)
+ del self._data[origkey]
diff --git a/archinstall/lib/menu/selection_menu.py b/archinstall/lib/menu/selection_menu.py
index cbcb2583..ca48fda2 100644
--- a/archinstall/lib/menu/selection_menu.py
+++ b/archinstall/lib/menu/selection_menu.py
@@ -1,7 +1,7 @@
from __future__ import annotations
-import sys
-import logging
+import logging
+import sys
from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING
from .menu import Menu
diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py
index eb4c25ee..16136177 100644
--- a/archinstall/lib/models/network_configuration.py
+++ b/archinstall/lib/models/network_configuration.py
@@ -2,7 +2,12 @@ from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
-from typing import List, Optional, Dict
+from typing import List, Optional, Dict, Union, Any, TYPE_CHECKING
+
+from ..output import log
+
+if TYPE_CHECKING:
+ _: Any
class NicType(str, Enum):
@@ -14,11 +19,11 @@ class NicType(str, Enum):
@dataclass
class NetworkConfiguration:
type: NicType
- iface: str = None
- ip: str = None
+ iface: Optional[str] = None
+ ip: Optional[str] = None
dhcp: bool = True
- gateway: str = None
- dns: List[str] = None
+ gateway: Optional[str] = None
+ dns: Union[None, List[str]] = None
def __str__(self):
if self.is_iso():
@@ -37,63 +42,6 @@ class NetworkConfiguration:
def json(self):
return self.__dict__
- @classmethod
- def parse_arguments(cls, config: Union[str,Dict[str, str]]) -> Optional["NetworkConfiguration"]:
- from ... import log
-
- nic_type = config.get('type', None)
-
- if not nic_type:
- # old style definitions
- if isinstance(config,str): # is a ISO network
- return NetworkConfiguration(NicType.ISO)
- elif config.get('NetworkManager'): # is a network manager configuration
- return NetworkConfiguration(NicType.NM)
- elif 'ip' in config:
- return NetworkConfiguration(
- NicType.MANUAL,
- iface=config.get('nic', ''),
- ip=config.get('ip'),
- gateway=config.get('gateway', ''),
- dns=config.get('dns', []),
- dhcp=False
- )
- elif 'nic' in config:
- return NetworkConfiguration(
- NicType.MANUAL,
- iface=config.get('nic', ''),
- dhcp=True
- )
- else: # not recognized
- 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
@@ -103,37 +51,123 @@ class NetworkConfiguration:
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)
+
+class NetworkConfigurationHandler:
+ def __init__(self, config: Union[None, NetworkConfiguration, List[NetworkConfiguration]] = None):
+ self._configuration = config
+
+ @property
+ def configuration(self):
+ return self._configuration
+
+ def config_installer(self, installation: Any):
+ if self._configuration is None:
+ return
+
+ if isinstance(self._configuration, list):
+ for config in self._configuration:
+ installation.configure_nic(config)
+
installation.enable_service('systemd-networkd')
installation.enable_service('systemd-resolved')
-
- def get(self, key :str, default_value :Any = None) -> Any:
- result = self.__getitem__(key)
- if result is None:
- return default_value
- else:
- return result
-
- def __getitem__(self, key :str) -> Any:
- if key == 'type':
- return self.type
- elif key == 'iface':
- return self.iface
- elif key == 'gateway':
- return self.gateway
- elif key == 'dns':
- return self.dns
- elif key == 'dhcp':
- return self.dhcp
else:
- raise KeyError(f"key {key} not available at NetworkConfiguration")
+ # If user selected to copy the current ISO network configuration
+ # Perform a copy of the config
+ if self._configuration.is_iso():
+ installation.copy_iso_network_config(
+ enable_services=True) # Sources the ISO network configuration to the install medium.
+ elif self._configuration.is_network_manager():
+ installation.add_additional_packages("networkmanager")
+ installation.enable_service('NetworkManager.service')
+
+ def _backwards_compability_config(self, config: Union[str,Dict[str, str]]) -> Union[List[NetworkConfiguration], NetworkConfiguration, None]:
+ def get(config: Dict[str, str], key: str) -> List[str]:
+ if (value := config.get(key, None)) is not None:
+ return [value]
+ return []
+
+ if isinstance(config, str): # is a ISO network
+ return NetworkConfiguration(NicType.ISO)
+ elif config.get('NetworkManager'): # is a network manager configuration
+ return NetworkConfiguration(NicType.NM)
+ elif 'ip' in config:
+ return [NetworkConfiguration(
+ NicType.MANUAL,
+ iface=config.get('nic', ''),
+ ip=config.get('ip'),
+ gateway=config.get('gateway', ''),
+ dns=get(config, 'dns'),
+ dhcp=False
+ )]
+ elif 'nic' in config:
+ return [NetworkConfiguration(
+ NicType.MANUAL,
+ iface=config.get('nic', ''),
+ dhcp=True
+ )]
+ else: # not recognized
+ return None
+
+ def _parse_manual_config(self, config: Dict[str, Any]) -> Union[None, List[NetworkConfiguration]]:
+ manual_configs: List = config.get('config', [])
+
+ if not manual_configs:
+ return None
+
+ if not isinstance(manual_configs, list):
+ log(_('Manual configuration setting must be a list'))
+ exit(1)
+
+ configurations = []
+
+ for manual_config in manual_configs:
+ iface = manual_config.get('iface', None)
+
+ if iface is None:
+ log(_('No iface specified for manual configuration'))
+ exit(1)
+
+ if manual_config.get('dhcp', False) or not any([manual_config.get(v, '') for v in ['ip', 'gateway', 'dns']]):
+ configurations.append(
+ NetworkConfiguration(NicType.MANUAL, iface=iface)
+ )
+ else:
+ ip = config.get('ip', '')
+ if not ip:
+ log(_('Manual nic configuration with no auto DHCP requires an IP address'), fg='red')
+ exit(1)
+
+ configurations.append(
+ NetworkConfiguration(
+ NicType.MANUAL,
+ iface=iface,
+ ip=ip,
+ gateway=config.get('gateway', ''),
+ dns=config.get('dns', []),
+ dhcp=False
+ )
+ )
+
+ return configurations
+
+ def parse_arguments(self, config: Any):
+ nic_type = config.get('type', None)
+
+ if not nic_type:
+ # old style definitions
+ network_config = self._backwards_compability_config(config)
+ if network_config:
+ return network_config
+ 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:
+ self._configuration = NetworkConfiguration(type_)
+ else: # manual configuration settings
+ self._configuration = self._parse_manual_config(config)
diff --git a/archinstall/lib/user_interaction/manage_users_conf.py b/archinstall/lib/user_interaction/manage_users_conf.py
index 664327d6..a6ff3111 100644
--- a/archinstall/lib/user_interaction/manage_users_conf.py
+++ b/archinstall/lib/user_interaction/manage_users_conf.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import logging
import re
-from typing import Any, Dict, TYPE_CHECKING
+from typing import Any, Dict, TYPE_CHECKING, List
from ..menu import Menu
from ..menu.list_manager import ListManager
@@ -34,25 +34,22 @@ class UserList(ListManager):
str(_('Promote/Demote user')),
str(_('Delete User'))
]
- self.default_action = self.actions[0]
- super().__init__(prompt, lusers, self.actions, self.default_action)
+ super().__init__(prompt, lusers, self.actions, self.actions[0])
- def reformat(self):
-
- def format_element(elem):
+ def reformat(self, data: Any) -> List[Any]:
+ def format_element(elem :str):
# secret gives away the length of the password
- if self.data[elem].get('!password'):
+ if data[elem].get('!password'):
pwd = '*' * 16
- # pwd = archinstall.secret(self.data[elem]['!password'])
else:
pwd = ''
- if self.data[elem].get('sudoer'):
- super = 'Superuser'
+ if data[elem].get('sudoer'):
+ super_user = 'Superuser'
else:
- super = ' '
- return f"{elem:16}: password {pwd:16} {super}"
+ super_user = ' '
+ return f"{elem:16}: password {pwd:16} {super_user}"
- return list(map(lambda x: format_element(x), self.data))
+ return list(map(lambda x: format_element(x), data))
def action_list(self):
if self.target:
@@ -71,7 +68,7 @@ class UserList(ListManager):
else:
return self.actions
- def exec_action(self):
+ def exec_action(self, data: Any):
if self.target:
active_user = list(self.target.keys())[0]
else:
@@ -80,14 +77,14 @@ class UserList(ListManager):
if self.action == self.actions[0]: # add
new_user = self.add_user()
# no unicity check, if exists will be replaced
- self.data.update(new_user)
+ data.update(new_user)
elif self.action == self.actions[1]: # change password
- self.data[active_user]['!password'] = get_password(
+ data[active_user]['!password'] = get_password(
prompt=str(_('Password for user "{}": ').format(active_user)))
elif self.action == self.actions[2]: # promote/demote
- self.data[active_user]['sudoer'] = not self.data[active_user]['sudoer']
+ data[active_user]['sudoer'] = not data[active_user]['sudoer']
elif self.action == self.actions[3]: # delete
- del self.data[active_user]
+ del data[active_user]
def _check_for_correct_username(self, username: str) -> bool:
if re.match(r'^[a-z_][a-z0-9_-]*\$?$', username) and len(username) <= 32:
diff --git a/archinstall/lib/user_interaction/network_conf.py b/archinstall/lib/user_interaction/network_conf.py
index f90a2af8..80c9106b 100644
--- a/archinstall/lib/user_interaction/network_conf.py
+++ b/archinstall/lib/user_interaction/network_conf.py
@@ -2,8 +2,7 @@ from __future__ import annotations
import ipaddress
import logging
-from copy import copy
-from typing import Any, Optional, Dict, TYPE_CHECKING
+from typing import Any, Optional, TYPE_CHECKING, List, Union
from ..menu.text_input import TextInput
from ..models.network_configuration import NetworkConfiguration, NicType
@@ -11,69 +10,77 @@ from ..models.network_configuration import NetworkConfiguration, NicType
from ..networking import list_interfaces
from ..menu import Menu
from ..output import log
+from ..menu.list_manager import ListManager
if TYPE_CHECKING:
_: Any
-def ask_to_configure_network(preset: Dict[str, Any] = {}) -> Optional[NetworkConfiguration]:
+class ManualNetworkConfig(ListManager):
"""
- Configure the network on the newly installed system
+ subclass of ListManager for the managing of network configuration accounts
"""
- 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()
- }
- # for this routine it's easier to set the cursor position rather than a preset value
- cursor_idx = None
- if preset:
- if preset['type'] == 'iso_config':
- cursor_idx = 0
- elif preset['type'] == 'network_manager':
- cursor_idx = 1
+
+ def __init__(self, prompt: str, ifaces: Union[None, NetworkConfiguration, List[NetworkConfiguration]]):
+ """
+ param: prompt
+ type: str
+ param: ifaces already defined previously
+ type: Dict
+ """
+
+ if ifaces is not None and isinstance(ifaces, list):
+ display_values = {iface.iface: iface for iface in ifaces}
else:
- try:
- # let's hope order in dictionaries stay
- cursor_idx = list(interfaces.values()).index(preset.get('type'))
- except ValueError:
- pass
+ display_values = {}
- nic = Menu(_('Select one network interface to configure'), interfaces.values(), cursor_index=cursor_idx,
- sort=False).run()
+ self._action_add = str(_('Add interface'))
+ self._action_edit = str(_('Edit interface'))
+ self._action_delete = str(_('Delete interface'))
- if not nic:
- return None
+ self._iface_actions = [self._action_edit, self._action_delete]
- if nic == interfaces['none']:
- return None
- elif nic == interfaces['iso_config']:
- return NetworkConfiguration(NicType.ISO)
- elif nic == interfaces['network_manager']:
- return NetworkConfiguration(NicType.NM)
- else:
- # Current workaround:
- # For selecting modes without entering text within brackets,
- # printing out this part separate from options, passed in
- # `generic_select`
- # we only keep data if it is the same nic as before
- if preset.get('type') != nic:
- preset_d = {'type': nic, 'dhcp': True, 'ip': None, 'gateway': None, 'dns': []}
- else:
- preset_d = copy(preset)
+ super().__init__(prompt, display_values, self._iface_actions, self._action_add)
+ def run_manual(self) -> List[NetworkConfiguration]:
+ ifaces = super().run()
+ if ifaces is not None:
+ return list(ifaces.values())
+ return []
+
+ def exec_action(self, data: Any):
+ if self.action == self._action_add:
+ iface_name = self._select_iface(data.keys())
+ if iface_name:
+ iface = NetworkConfiguration(NicType.MANUAL, iface=iface_name)
+ data[iface_name] = self._edit_iface(iface)
+ elif self.target:
+ iface_name = list(self.target.keys())[0]
+ iface = data[iface_name]
+
+ if self.action == self._action_edit:
+ data[iface_name] = self._edit_iface(iface)
+ elif self.action == self._action_delete:
+ del data[iface_name]
+
+ def _select_iface(self, existing_ifaces: List[str]) -> Optional[str]:
+ all_ifaces = list_interfaces().values()
+ available = set(all_ifaces) - set(existing_ifaces)
+ iface = Menu(str(_('Select interface to add')), list(available), skip=True).run()
+ return iface
+
+ def _edit_iface(self, edit_iface :NetworkConfiguration):
+ iface_name = edit_iface.iface
modes = ['DHCP (auto detect)', 'IP (static)']
default_mode = 'DHCP (auto detect)'
- cursor_idx = 0 if preset_d.get('dhcp', True) else 1
- prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(nic, default_mode)
- mode = Menu(prompt, modes, default_option=default_mode, cursor_index=cursor_idx).run()
- # TODO preset values for ip and gateway
+ prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(iface_name, default_mode)
+ mode = Menu(prompt, modes, default_option=default_mode).run()
+
if mode == 'IP (static)':
while 1:
- prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(nic)
- ip = TextInput(prompt, preset_d.get('ip')).run().strip()
+ prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name)
+ ip = TextInput(prompt, edit_iface.ip).run().strip()
# Implemented new check for correct IP/subnet input
try:
ipaddress.ip_interface(ip)
@@ -84,7 +91,7 @@ def ask_to_configure_network(preset: Dict[str, Any] = {}) -> Optional[NetworkCon
# Implemented new check for correct gateway IP address
while 1:
gateway = TextInput(_('Enter your gateway (router) IP address or leave blank for none: '),
- preset_d.get('gateway')).run().strip()
+ edit_iface.gateway).run().strip()
try:
if len(gateway) == 0:
gateway = None
@@ -94,18 +101,58 @@ def ask_to_configure_network(preset: Dict[str, Any] = {}) -> Optional[NetworkCon
except ValueError:
log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red')
- dns = None
- if preset_d.get('dns'):
- preset_d['dns'] = ' '.join(preset_d['dns'])
+ if edit_iface.dns:
+ display_dns = ' '.join(edit_iface.dns)
else:
- preset_d['dns'] = None
- dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '),
- preset_d['dns']).run().strip()
+ display_dns = None
+ dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '), display_dns).run().strip()
if len(dns_input):
dns = dns_input.split(' ')
- return NetworkConfiguration(NicType.MANUAL, iface=nic, ip=ip, gateway=gateway, dns=dns, dhcp=False)
+ return NetworkConfiguration(NicType.MANUAL, iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
else:
# this will contain network iface names
- return NetworkConfiguration(NicType.MANUAL, iface=nic)
+ return NetworkConfiguration(NicType.MANUAL, iface=iface_name)
+
+
+def ask_to_configure_network(preset: Union[None, NetworkConfiguration, List[NetworkConfiguration]]) -> Optional[Union[List[NetworkConfiguration], NetworkConfiguration]]:
+ """
+ Configure the network on the newly installed system
+ """
+ network_options = {
+ '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)')),
+ 'manual': str(_('Manual configuration'))
+ }
+ # for this routine it's easier to set the cursor position rather than a preset value
+ cursor_idx = None
+
+ if preset and not isinstance(preset, list):
+ if preset.type == 'iso_config':
+ cursor_idx = 0
+ elif preset.type == 'network_manager':
+ cursor_idx = 1
+
+ nic = Menu(_(
+ 'Select one network interface to configure'),
+ list(network_options.values()),
+ cursor_index=cursor_idx,
+ sort=False
+ ).run()
+
+ if not nic:
+ return preset
+
+ if nic == network_options['none']:
+ return None
+ elif nic == network_options['iso_config']:
+ return NetworkConfiguration(NicType.ISO)
+ elif nic == network_options['network_manager']:
+ return NetworkConfiguration(NicType.NM)
+ elif nic == network_options['manual']:
+ manual = ManualNetworkConfig('Configure interfaces', preset)
+ return manual.run_manual()
+
+ return preset
diff --git a/archinstall/lib/user_interaction/subvolume_config.py b/archinstall/lib/user_interaction/subvolume_config.py
index 6de8d0ef..0515876b 100644
--- a/archinstall/lib/user_interaction/subvolume_config.py
+++ b/archinstall/lib/user_interaction/subvolume_config.py
@@ -1,3 +1,5 @@
+from typing import List, Any, Dict
+
from ..menu.list_manager import ListManager
from ..menu.selection_menu import Selector, GeneralMenu
from ..menu.text_input import TextInput
@@ -12,8 +14,8 @@ class SubvolumeList(ListManager):
self.ObjectDefaultAction = str(_('Add'))
super().__init__(prompt,list,None,self.ObjectNullAction,self.ObjectDefaultAction)
- def reformat(self):
- def presentation(key,value):
+ def reformat(self, data: Any) -> List[Any]:
+ def presentation(key :str, value :Dict):
text = _(" Subvolume :{:16}").format(key)
if isinstance(value,str):
text += _(" mounted at {:16}").format(value)
@@ -26,32 +28,31 @@ class SubvolumeList(ListManager):
text += _(" with option {}").format(', '.join(value['options']))
return text
- return sorted(list(map(lambda x:presentation(x,self.data[x]),self.data)))
+ return sorted(list(map(lambda x:presentation(x,data[x]),data)))
def action_list(self):
return super().action_list()
- def exec_action(self):
+ def exec_action(self, data: Any):
if self.target:
origkey,origval = list(self.target.items())[0]
else:
origkey = None
if self.action == str(_('Delete')):
- del self.data[origkey]
- return True
-
- if self.action == str(_('Add')):
- self.target = {}
- print(_('\n Fill the desired values for a new subvolume \n'))
- with SubvolumeMenu(self.target,self.action) as add_menu:
- for data in ['name','mountpoint','options']:
- add_menu.exec_option(data)
+ del data[origkey]
else:
- SubvolumeMenu(self.target,self.action).run()
- self.data.update(self.target)
+ if self.action == str(_('Add')):
+ self.target = {}
+ print(_('\n Fill the desired values for a new subvolume \n'))
+ with SubvolumeMenu(self.target,self.action) as add_menu:
+ for data in ['name','mountpoint','options']:
+ add_menu.exec_option(data)
+ else:
+ SubvolumeMenu(self.target,self.action).run()
+
+ data.update(self.target)
- return True
class SubvolumeMenu(GeneralMenu):
def __init__(self,parameters,action=None):