Support for multiple network interfaces (#1052)
* Support for multiple network interfaces * Fix mypy * Fix flake8 Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
This commit is contained in:
parent
2529d6a5f5
commit
477b5b120e
|
|
@ -13,5 +13,6 @@ jobs:
|
|||
- run: python --version
|
||||
- run: mypy --version
|
||||
# one day this will be enabled
|
||||
# run: mypy --strict --module archinstall || exit 0
|
||||
- name: run mypy
|
||||
run: mypy --strict --module archinstall || exit 0
|
||||
run: mypy --follow-imports=skip archinstall/lib/menu/selection_menu.py archinstall/lib/models/network_configuration.py archinstall/lib/menu/list_manager.py archinstall/lib/user_interaction/network_conf.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
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# 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 __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")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import time
|
|||
|
||||
import archinstall
|
||||
from archinstall import ConfigurationOutput
|
||||
from archinstall.lib.models.network_configuration import NetworkConfigurationHandler
|
||||
|
||||
if archinstall.arguments.get('help'):
|
||||
print("See `man archinstall` for help.")
|
||||
|
|
@ -167,7 +168,8 @@ def perform_installation(mountpoint):
|
|||
network_config = archinstall.arguments.get('nic', None)
|
||||
|
||||
if network_config:
|
||||
network_config.config_installer(installation)
|
||||
handler = NetworkConfigurationHandler(network_config)
|
||||
handler.config_installer(installation)
|
||||
|
||||
if archinstall.arguments.get('audio', None) is not None:
|
||||
installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}", level=logging.INFO)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,13 @@ import logging
|
|||
import os
|
||||
import time
|
||||
import pathlib
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import archinstall
|
||||
from archinstall import ConfigurationOutput
|
||||
from archinstall import ConfigurationOutput, NetworkConfigurationHandler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_: Any
|
||||
|
||||
if archinstall.arguments.get('help'):
|
||||
print("See `man archinstall` for help.")
|
||||
|
|
@ -397,7 +401,8 @@ def os_setup(installation):
|
|||
network_config = archinstall.arguments.get('nic', None)
|
||||
|
||||
if network_config:
|
||||
network_config.config_installer(installation)
|
||||
handler = NetworkConfigurationHandler(network_config)
|
||||
handler.config_installer(installation)
|
||||
|
||||
if archinstall.arguments.get('audio', None) is not None:
|
||||
installation.log(f"This audio server will be used: {archinstall.arguments.get('audio', None)}",level=logging.INFO)
|
||||
|
|
|
|||
Loading…
Reference in New Issue