Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into integrate-curses
Browse files Browse the repository at this point in the history
  • Loading branch information
svartkanin committed Nov 7, 2024
2 parents 7fcedaf + 0370e89 commit 52a67fa
Show file tree
Hide file tree
Showing 23 changed files with 2,032 additions and 291 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
on: [ push, pull_request ]
name: Pylint linting
jobs:
pylint:
runs-on: ubuntu-latest
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@v4
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pyparted python-simple-term-menu pkgconfig gcc
- run: pip install --break-system-packages --upgrade pip
- name: Install Pylint and Pylint plug-ins
run: pip install --break-system-packages .[dev]
- run: python --version
- run: pylint --version
- name: Lint with Pylint
run: pylint .
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ repos:
args: [--config=.flake8]
fail_fast: true
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.12.0
rev: v1.13.0
hooks:
- id: mypy
args: [
'--config-file=pyproject.toml'
]
fail_fast: true
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.7.2
hooks:
- id: ruff
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ It will go through everything from packaging, building and running *(with qemu)*
# FAQ
## Keyring out-of-date
For a description of the problem see https://archinstall.archlinux.page/help/known_issues.html#keyring-is-out-of-date-2213 and discussion in issue https://github.com/archlinux/archinstall/issues/2213.
For a quick fix the below command will install the latest keyrings
```pacman -Sy archlinux-keyring```
## How to dual boot with Windows
To install Arch Linux alongside an existing Windows installation using `archinstall`, follow these steps:
Expand Down
4 changes: 4 additions & 0 deletions archinstall/lib/disk/device_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,10 @@ def wipe_dev(self, block_device: BDevice) -> None:
"""
info(f'Wiping partitions and metadata: {block_device.device_info.path}')
for partition in block_device.partition_infos:
luks = Luks2(partition.path)
if luks.isLuks():
luks.erase()

self._wipe(partition.path)

self._wipe(block_device.device_info.path)
Expand Down
8 changes: 4 additions & 4 deletions archinstall/lib/disk/device_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ def _fetch_lsblk_info(
cmd.append(str(dev_path))

try:
result = SysCommand(cmd).decode()
worker = SysCommand(cmd)
except SysCallError as err:
# Get the output minus the message/info from lsblk if it returns a non-zero exit code.
if err.worker:
Expand All @@ -1448,12 +1448,12 @@ def _fetch_lsblk_info(
raise err

try:
block_devices = json.loads(result)
data = json.loads(worker.output(remove_cr=False))
except json.decoder.JSONDecodeError as err:
error(f"Could not decode lsblk JSON: {result}")
error(f"Could not decode lsblk JSON:\n{worker.output().decode().rstrip()}")
raise err

blockdevices = block_devices['blockdevices']
blockdevices = data['blockdevices']
return [LsblkInfo.from_json(device) for device in blockdevices]


Expand Down
46 changes: 25 additions & 21 deletions archinstall/lib/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,28 +302,29 @@ def execute(self) -> bool:
# https://stackoverflow.com/questions/4022600/python-pty-fork-how-does-it-work
if not self.pid:
history_logfile = pathlib.Path(f"{storage['LOG_PATH']}/cmd_history.txt")

change_perm = False
if history_logfile.exists() is False:
change_perm = True

try:
change_perm = False
if history_logfile.exists() is False:
change_perm = True
with history_logfile.open("a") as cmd_log:
cmd_log.write(f"{time.time()} {self.cmd}\n")

try:
with history_logfile.open("a") as cmd_log:
cmd_log.write(f"{time.time()} {self.cmd}\n")

if change_perm:
os.chmod(str(history_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
except (PermissionError, FileNotFoundError):
# If history_logfile does not exist, ignore the error
pass
except Exception as e:
exception_type = type(e).__name__
error(f"Unexpected {exception_type} occurred in {self.cmd}: {e}")
raise e

if storage.get('arguments', {}).get('debug'):
debug(f"Executing: {self.cmd}")
if change_perm:
os.chmod(str(history_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
except (PermissionError, FileNotFoundError):
# If history_logfile does not exist, ignore the error
pass
except Exception as e:
exception_type = type(e).__name__
error(f"Unexpected {exception_type} occurred in {self.cmd}: {e}")
raise e

if storage.get('arguments', {}).get('debug'):
debug(f"Executing: {self.cmd}")

try:
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
except FileNotFoundError:
error(f"{self.cmd[0]} does not exist.")
Expand Down Expand Up @@ -441,11 +442,14 @@ def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', stri
return val.strip()
return val

def output(self) -> bytes:
def output(self, remove_cr: bool = True) -> bytes:
if not self.session:
raise ValueError('No session available')

return self.session._trace_log.replace(b'\r\n', b'\n')
if remove_cr:
return self.session._trace_log.replace(b'\r\n', b'\n')

return self.session._trace_log

@property
def exit_code(self) -> Optional[int]:
Expand Down
3 changes: 3 additions & 0 deletions archinstall/lib/interactions/network_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ def _select_iface(self, data: List[Nic]) -> Optional[str]:
existing_ifaces = [d.iface for d in data]
available = set(all_ifaces) - set(existing_ifaces)

if not available:
return None

if not available:
return None

Expand Down
13 changes: 13 additions & 0 deletions archinstall/lib/luks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ def mapper_dev(self) -> Optional[Path]:
return Path(f'/dev/mapper/{self.mapper_name}')
return None

def isLuks(self) -> bool:
try:
SysCommand(f'cryptsetup isLuks {self.luks_dev_path}')
return True
except SysCallError:
return False

def erase(self) -> None:
debug(f'Erasing luks partition: {self.luks_dev_path}')
worker = SysCommandWorker(f'cryptsetup erase {self.luks_dev_path}')
worker.poll()
worker.write(b'YES\n', line_ending=False)

def __post_init__(self) -> None:
if self.luks_dev_path is None:
raise ValueError('Partition must have a path set')
Expand Down
1 change: 0 additions & 1 deletion archinstall/lib/menu/abstract_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ def menu_text(self, padding: int = 0) -> str:
if current:
padding += 5
description = unicode_ljust(str(self.description), padding, ' ')
current = current
else:
description = self.description
current = ''
Expand Down
34 changes: 20 additions & 14 deletions archinstall/lib/mirrors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
import json
import pathlib
from dataclasses import dataclass, field
Expand All @@ -6,7 +7,7 @@

from .menu import AbstractSubMenu, ListManager
from .networking import fetch_data_from_url
from .output import warn, FormattedOutput
from .output import FormattedOutput, debug
from .storage import storage
from .models.mirrors import MirrorStatusListV3, MirrorStatusEntryV3

Expand Down Expand Up @@ -383,17 +384,22 @@ def _parse_mirror_list(mirrorlist: str) -> Dict[str, List[MirrorStatusEntryV3]]:


def list_mirrors() -> Dict[str, List[MirrorStatusEntryV3]]:
regions: Dict[str, List[MirrorStatusEntryV3]] = {}

if storage['arguments']['offline']:
with pathlib.Path('/etc/pacman.d/mirrorlist').open('r') as fp:
mirrorlist = fp.read()
else:
if not storage['arguments']['offline']:
url = "https://archlinux.org/mirrors/status/json/"
try:
mirrorlist = fetch_data_from_url(url)
except ValueError as err:
warn(f'Could not fetch an active mirror-list: {err}')
return regions

return _parse_mirror_list(mirrorlist)
attempts = 3

for attempt_nr in range(attempts):
try:
mirrorlist = fetch_data_from_url(url)
return _parse_mirror_list(mirrorlist)
except Exception as e:
debug(f'Error while fetching mirror list: {e}')
time.sleep(attempt_nr + 1)

debug('Unable to fetch mirror list remotely, falling back to local mirror list')

# we'll use the local mirror list if the offline flag is set
# or if fetching the mirror list remotely failed
with pathlib.Path('/etc/pacman.d/mirrorlist').open('r') as fp:
mirrorlist = fp.read()
return _parse_mirror_list(mirrorlist)
52 changes: 34 additions & 18 deletions archinstall/lib/models/mirrors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pydantic import BaseModel, field_validator, model_validator
import datetime
import http.client
import urllib.error
import urllib.parse
import urllib.request
from typing import (
Expand Down Expand Up @@ -33,27 +34,42 @@ class MirrorStatusEntryV3(BaseModel):
_speed: float | None = None
_hostname: str | None = None
_port: int | None = None
_speedtest_retries: int | None = None

@property
def speed(self) -> float | None:
def speed(self) -> float:
if self._speed is None:
info(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db")
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db")

try:
with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer:
size = len(handle.read())

self._speed = size / timer.time
debug(f" speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)")
except http.client.IncompleteRead:
debug(" speed: <undetermined>")
self._speed = 0
except urllib.error.URLError as error:
debug(f" speed: <undetermined> ({error})")
self._speed = 0
except Exception as error:
debug(f" speed: <undetermined> ({error})")
if not self._speedtest_retries:
self._speedtest_retries = 3
elif self._speedtest_retries < 1:
self._speedtest_retries = 1

_retry = 0
while _retry < self._speedtest_retries and self._speed is None:
info(f"Checking download speed of {self._hostname}[{self.score}] by fetching: {self.url}core/os/x86_64/core.db")
req = urllib.request.Request(url=f"{self.url}core/os/x86_64/core.db")

try:
with urllib.request.urlopen(req, None, 5) as handle, DownloadTimer(timeout=5) as timer:
size = len(handle.read())

self._speed = size / timer.time
debug(f" speed: {self._speed} ({int(self._speed / 1024 / 1024 * 100) / 100}MiB/s)")
# Do not retry error
except (urllib.error.URLError, ) as error:
debug(f" speed: <undetermined> ({error}), skip")
self._speed = 0
# Do retry error
except (http.client.IncompleteRead, ConnectionResetError) as error:
debug(f" speed: <undetermined> ({error}), retry")
# Catch all
except Exception as error:
debug(f" speed: <undetermined> ({error}), skip")
self._speed = 0

_retry += 1

if self._speed is None:
self._speed = 0

return self._speed
Expand Down
2 changes: 1 addition & 1 deletion archinstall/lib/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class PasswordStrength(Enum):
STRONG = 'strong'

@property
def value(self) -> str:
def value(self) -> str: # pylint: disable=invalid-overridden-method
match self:
case PasswordStrength.VERY_WEAK: return str(_('very weak'))
case PasswordStrength.WEAK: return str(_('weak'))
Expand Down
6 changes: 4 additions & 2 deletions archinstall/lib/networking.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,10 @@ def fetch_data_from_url(url: str, params: Optional[dict] = None) -> str:
response = urlopen(full_url, context=ssl_context)
data = response.read().decode('UTF-8')
return data
except URLError:
raise ValueError(f'Unable to fetch data from url: {url}')
except URLError as e:
raise ValueError(f'Unable to fetch data from url: {url}\n{e}')
except Exception as e:
raise ValueError(f'Unexpected error when parsing response: {e}')


def calc_checksum(icmp_packet) -> int:
Expand Down
2 changes: 1 addition & 1 deletion archinstall/lib/packages/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ def installed_package(package: str) -> LocalPackage:
except SysCallError:
pass

return LocalPackage({field.name: package_info.get(field.name) for field in dataclasses.fields(LocalPackage)}) # type: ignore
return LocalPackage({field.name: package_info.get(field.name) for field in dataclasses.fields(LocalPackage)}) # type: ignore # pylint: disable=no-value-for-parameter
Binary file added archinstall/locales/ga/LC_MESSAGES/base.mo
Binary file not shown.
Loading

0 comments on commit 52a67fa

Please sign in to comment.