240 lines
5.2 KiB
Python
240 lines
5.2 KiB
Python
import re
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import NotRequired, Self, TypedDict, override
|
|
|
|
from archinstall.lib.output import debug
|
|
from archinstall.lib.translationhandler import tr
|
|
|
|
|
|
class NicType(Enum):
|
|
ISO = 'iso'
|
|
NM = 'nm'
|
|
NM_IWD = 'nm_iwd'
|
|
IWD = 'iwd'
|
|
MANUAL = 'manual'
|
|
|
|
def display_msg(self) -> str:
|
|
match self:
|
|
case NicType.ISO:
|
|
return tr('Copy ISO network configuration to installation')
|
|
case NicType.NM:
|
|
return tr('Use Network Manager (default backend)')
|
|
case NicType.NM_IWD:
|
|
return tr('Use Network Manager (iwd backend)')
|
|
case NicType.IWD:
|
|
return tr('Use iwd standalone (no Network Manager)')
|
|
case NicType.MANUAL:
|
|
return tr('Manual configuration')
|
|
|
|
|
|
class _NicSerialization(TypedDict):
|
|
iface: str | None
|
|
ip: str | None
|
|
dhcp: bool
|
|
gateway: str | None
|
|
dns: list[str]
|
|
|
|
|
|
@dataclass
|
|
class Nic:
|
|
iface: str | None = None
|
|
ip: str | None = None
|
|
dhcp: bool = True
|
|
gateway: str | None = None
|
|
dns: list[str] = field(default_factory=list)
|
|
|
|
def table_data(self) -> dict[str, str | bool | list[str]]:
|
|
return {
|
|
'iface': self.iface if self.iface else '',
|
|
'ip': self.ip if self.ip else '',
|
|
'dhcp': self.dhcp,
|
|
'gateway': self.gateway if self.gateway else '',
|
|
'dns': self.dns,
|
|
}
|
|
|
|
def json(self) -> _NicSerialization:
|
|
return {
|
|
'iface': self.iface,
|
|
'ip': self.ip,
|
|
'dhcp': self.dhcp,
|
|
'gateway': self.gateway,
|
|
'dns': self.dns,
|
|
}
|
|
|
|
@classmethod
|
|
def parse_arg(cls, arg: _NicSerialization) -> Self:
|
|
return cls(
|
|
iface=arg.get('iface', None),
|
|
ip=arg.get('ip', None),
|
|
dhcp=arg.get('dhcp', True),
|
|
gateway=arg.get('gateway', None),
|
|
dns=arg.get('dns', []),
|
|
)
|
|
|
|
def as_systemd_config(self) -> str:
|
|
match: list[tuple[str, str]] = []
|
|
network: list[tuple[str, str]] = []
|
|
|
|
if self.iface:
|
|
match.append(('Name', self.iface))
|
|
|
|
if self.dhcp:
|
|
network.append(('DHCP', 'yes'))
|
|
else:
|
|
if self.ip:
|
|
network.append(('Address', self.ip))
|
|
if self.gateway:
|
|
network.append(('Gateway', self.gateway))
|
|
for dns in self.dns:
|
|
network.append(('DNS', dns))
|
|
|
|
config = {'Match': match, 'Network': network}
|
|
|
|
config_str = ''
|
|
for top, entries in config.items():
|
|
config_str += f'[{top}]\n'
|
|
config_str += '\n'.join(f'{k}={v}' for k, v in entries)
|
|
config_str += '\n\n'
|
|
|
|
return config_str
|
|
|
|
|
|
class _NetworkConfigurationSerialization(TypedDict):
|
|
type: str
|
|
nics: NotRequired[list[_NicSerialization]]
|
|
|
|
|
|
@dataclass
|
|
class NetworkConfiguration:
|
|
type: NicType
|
|
nics: list[Nic] = field(default_factory=list)
|
|
|
|
def json(self) -> _NetworkConfigurationSerialization:
|
|
config: _NetworkConfigurationSerialization = {'type': self.type.value}
|
|
if self.nics:
|
|
config['nics'] = [n.json() for n in self.nics]
|
|
|
|
return config
|
|
|
|
@classmethod
|
|
def parse_arg(cls, config: _NetworkConfigurationSerialization) -> Self | None:
|
|
nic_type = config.get('type', None)
|
|
if not nic_type:
|
|
return None
|
|
|
|
match NicType(nic_type):
|
|
case NicType.ISO:
|
|
return cls(NicType.ISO)
|
|
case NicType.NM:
|
|
return cls(NicType.NM)
|
|
case NicType.NM_IWD:
|
|
return cls(NicType.NM_IWD)
|
|
case NicType.IWD:
|
|
return cls(NicType.IWD)
|
|
case NicType.MANUAL:
|
|
nics_arg = config.get('nics', [])
|
|
if nics_arg:
|
|
nics = [Nic.parse_arg(n) for n in nics_arg]
|
|
return cls(NicType.MANUAL, nics)
|
|
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class WifiNetwork:
|
|
bssid: str
|
|
frequency: str
|
|
signal_level: str
|
|
flags: str
|
|
ssid: str
|
|
|
|
@override
|
|
def __hash__(self) -> int:
|
|
return hash((self.bssid, self.frequency, self.signal_level, self.flags, self.ssid))
|
|
|
|
def table_data(self) -> dict[str, str | int]:
|
|
"""Format WiFi data for table display"""
|
|
return {
|
|
'SSID': self.ssid,
|
|
'Signal': f'{self.signal_level} dBm',
|
|
'Frequency': f'{self.frequency} MHz',
|
|
'Security': self.flags,
|
|
'BSSID': self.bssid,
|
|
}
|
|
|
|
@classmethod
|
|
def from_wpa(cls, results: str) -> list[Self]:
|
|
entries = []
|
|
|
|
for line in results.splitlines():
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
|
|
parts = line.split()
|
|
if len(parts) != 5:
|
|
continue
|
|
|
|
wifi = cls(bssid=parts[0], frequency=parts[1], signal_level=parts[2], flags=parts[3], ssid=parts[4])
|
|
entries.append(wifi)
|
|
|
|
return entries
|
|
|
|
|
|
@dataclass
|
|
class WifiConfiguredNetwork:
|
|
network_id: int
|
|
ssid: str
|
|
bssid: str
|
|
flags: list[str]
|
|
|
|
@classmethod
|
|
def from_wpa_cli_output(cls, list_networks: str) -> list[Self]:
|
|
"""
|
|
Example output from 'wpa_cli list_networks'
|
|
|
|
Selected interface 'wlan0'
|
|
network id / ssid / bssid / flags
|
|
0 WifiGuest any [CURRENT]
|
|
1 any [DISABLED]
|
|
2 any [DISABLED]
|
|
"""
|
|
|
|
lines = list_networks.strip().splitlines()
|
|
lines = lines[1:] # remove the header row from the wpa_cli output
|
|
|
|
networks = []
|
|
|
|
for line in lines:
|
|
line = line.strip()
|
|
parts = line.split('\t')
|
|
|
|
if len(parts) < 3:
|
|
continue
|
|
|
|
try:
|
|
# flags = cls._extract_flags(parts[3])
|
|
flags: list[str] = []
|
|
|
|
networks.append(
|
|
cls(
|
|
network_id=int(parts[0]),
|
|
ssid=parts[1],
|
|
bssid=parts[2],
|
|
flags=flags,
|
|
)
|
|
)
|
|
except ValueError, IndexError:
|
|
debug('Parsing error for network output')
|
|
|
|
return networks
|
|
|
|
@staticmethod
|
|
def _extract_flags(flag_string: str) -> list[str]:
|
|
pattern = r'\[([^\]]+)\]'
|
|
|
|
extracted_values = re.findall(pattern, flag_string)
|
|
|
|
return extracted_values
|