Merge master into lang-for-target-locale

Resolve the binary base.mo conflict
This commit is contained in:
Softer 2026-07-14 12:08:25 +03:00
commit 13a7367ee0
47 changed files with 4605 additions and 421 deletions

View File

@ -6,7 +6,7 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- run: pacman --noconfirm -Syu bandit
- name: Security checkup with Bandit
run: bandit -r archinstall || exit 0

View File

@ -6,7 +6,7 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -21,8 +21,8 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
- name: Install pre-dependencies
run: |
pacman -Sy --noconfirm tree git python-pyparted python-setuptools python-sphinx python-sphinx_rtd_theme python-build python-installer python-wheel

View File

@ -26,7 +26,7 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- run: pwd
- run: find .
- run: cat /etc/os-release

View File

@ -6,7 +6,7 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -6,7 +6,7 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -7,7 +7,7 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -11,7 +11,7 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -18,7 +18,7 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Prepare arch
run: |
pacman-key --init

View File

@ -4,6 +4,6 @@ jobs:
ruff_format_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
- run: ruff format --diff

View File

@ -4,5 +4,5 @@ jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0

View File

@ -15,7 +15,7 @@ jobs:
name: Validate translations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install gettext
run: sudo apt-get update && sudo apt-get install -y gettext
- name: Run translation checks

View File

@ -26,7 +26,7 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- run: pwd
- run: find .
- run: cat /etc/os-release

View File

@ -1,7 +1,7 @@
default_stages: ['pre-commit']
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.16
rev: v0.15.21
hooks:
# fix unused imports and sort them
- id: ruff
@ -31,7 +31,7 @@ repos:
args: [--config=.flake8]
fail_fast: true
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v2.1.0
rev: v2.3.0
hooks:
- id: mypy
args: [

View File

@ -5,7 +5,7 @@
# Contributor: demostanis worlds <demostanis@protonmail.com>
pkgname=archinstall
pkgver=4.3
pkgver=4.4
pkgrel=1
pkgdesc="Just another guided/automated Arch Linux installer with a twist"
arch=(any)

View File

@ -1,6 +1,7 @@
from typing import TYPE_CHECKING, Self, override
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType, SelectResult
from archinstall.default_profiles.desktops.utils import provision_seat_access
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType, SelectResult
from archinstall.lib.log import info
from archinstall.lib.menu.helpers import Selection
from archinstall.lib.profile.profiles_handler import profile_handler
@ -94,6 +95,9 @@ class DesktopProfile(Profile):
for profile in self.current_selection:
profile.provision(install_session, users)
if seat_access := profile.custom_settings.get(CustomSetting.SeatAccess):
provision_seat_access(install_session, users, seat_access)
@override
def install(self, install_session: Installer) -> None:
# Install common packages for all desktop environments

View File

@ -1,6 +1,8 @@
from enum import Enum
from archinstall.lib.installer import Installer
from archinstall.lib.menu.helpers import Selection
from archinstall.lib.models.users import User
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
@ -11,6 +13,16 @@ class SeatAccess(Enum):
polkit = 'polkit'
def provision_seat_access(
install_session: Installer,
users: list[User],
seat_access: str,
) -> None:
if seat_access == SeatAccess.seatd.value:
for user in users:
install_session.arch_chroot(f'usermod -a -G seat {user.username}')
async def select_seat_access(profile_name: str, default: str | None) -> SeatAccess:
header = tr('{} needs access to your seat').format(profile_name)
header += f' ({tr("collection of hardware devices i.e. keyboard, mouse")})' + '\n'

View File

@ -1,7 +1,9 @@
import argparse
import json
import os
import stat
import sys
import tempfile
import urllib.error
import urllib.parse
from argparse import ArgumentParser, Namespace
@ -11,9 +13,10 @@ from pathlib import Path
from typing import Any, Self
from urllib.request import Request, urlopen
from pydantic import TypeAdapter
from pydantic.dataclasses import dataclass as p_dataclass
from archinstall.lib.crypt import decrypt
from archinstall.lib.crypt import decrypt, encrypt
from archinstall.lib.log import debug, error, logger, warn
from archinstall.lib.menu.util import get_password
from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration
@ -31,6 +34,8 @@ from archinstall.lib.models.profile import ProfileConfiguration
from archinstall.lib.models.users import Password, User, UserSerialization
from archinstall.lib.plugins import load_plugin
from archinstall.lib.translationhandler import Language, tr, translation_handler
from archinstall.lib.utils.format import as_key_value_pair
from archinstall.lib.utils.util import is_valid_path
from archinstall.lib.version import get_version
from archinstall.tui.components import tui
@ -56,7 +61,8 @@ class Arguments:
debug: bool = False
offline: bool = False
no_pkg_lookups: bool = False
plugin: str | None = None
plugin: Path | None = None
plugin_url: str | None = None
skip_version_check: bool = False
skip_wifi_check: bool = False
advanced: bool = False
@ -140,6 +146,11 @@ class ArchConfigType(StrEnum):
return tr('Disk encryption password')
USER_CONFIG_FILE: Path = Path('user_configuration.json')
USER_CREDS_FILE: Path = Path('user_credentials.json')
DEFAULT_SAVE_PATH = logger.directory
@dataclass
class ArchConfig:
version: str | None = None
@ -367,6 +378,94 @@ class ArchConfig:
return arch_config
def user_config_to_json(self) -> str:
config = self.safe_config()
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(config)
return json.dumps(python_dict, indent=4, sort_keys=True)
def user_credentials_to_json(self) -> str:
cfg = self.unsafe_config()
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(cfg)
return json.dumps(python_dict, indent=4, sort_keys=True)
def write_debug(self) -> None:
debug(' -- Chosen configuration --')
debug(self.user_config_to_json())
def save(
self,
dest_path: Path | None = None,
creds: bool = False,
password: str | None = None,
) -> None:
save_path = dest_path or DEFAULT_SAVE_PATH
if not is_valid_path(save_path):
warn(
f'Destination directory {save_path} does not exist or is not a directory\n.',
'Configuration files can not be saved',
)
return
self.save_user_config(save_path)
if creds:
self.save_user_creds(save_path, password=password)
def save_user_config(self, dest_path: Path) -> None:
if not is_valid_path(dest_path):
error(f'Invalid path {dest_path}. User configuration could not be saved.')
return
target = dest_path / USER_CONFIG_FILE
data = self.user_config_to_json()
target.write_text(data)
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def save_user_creds(
self,
dest_path: Path,
password: str | None = None,
) -> None:
if not is_valid_path(dest_path):
error(f'Invalid path {dest_path}. User credentials could not be saved.')
return
data = self.user_credentials_to_json()
if password:
data = encrypt(password, data)
target = dest_path / USER_CREDS_FILE
target.write_text(data)
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def as_summary(self) -> str:
"""
Render a concise two-column summary of the current configuration.
Returns an empty string if nothing meaningful to show.
"""
cfg: dict[str, str | list[str] | bool] = {}
for key, value in self.plain_cfg().items():
cfg[key.text()] = value
for config_type, obj in self.sub_cfg().items():
if not hasattr(obj, 'summary'):
continue
summary = obj.summary()
if summary:
cfg[config_type.text()] = summary
simple_summary = as_key_value_pair(cfg, ignore_empty=True)
return simple_summary
class ArchConfigHandler:
def __init__(self) -> None:
@ -517,10 +616,17 @@ class ArchConfigHandler:
parser.add_argument(
'--plugin',
nargs='?',
type=str,
type=Path,
default=None,
help='File path to a plugin to load',
)
parser.add_argument(
'--plugin-url',
type=str,
nargs='?',
default=None,
help='Url to a plugin file to load',
)
parser.add_argument(
'--skip-version-check',
action='store_true',
@ -560,7 +666,11 @@ class ArchConfigHandler:
warn(f'Warning: --debug mode will write certain credentials to {logger.path}!')
if args.plugin:
plugin_path = Path(args.plugin)
load_plugin(args.plugin)
if args.plugin_url:
plugin_data = self._fetch_from_url(args.plugin_url)
plugin_path = self._write_plugin_to_temp_file(plugin_data)
load_plugin(plugin_path)
if args.creds_decryption_key is None:
@ -652,6 +762,27 @@ class ArchConfigHandler:
sys.exit(1)
def _write_plugin_to_temp_file(self, plugin_data: str) -> Path:
if not plugin_data.strip():
error('The downloaded plugin is empty')
sys.exit(1)
tmp_file = tempfile.NamedTemporaryFile(
mode='w',
suffix='.py',
prefix='archinstall_plugin_',
delete=False,
)
try:
with tmp_file as f:
f.write(plugin_data)
except OSError as err:
error(f'Could not write the downloaded plugin to a temporary file: {err}')
sys.exit(1)
return Path(tmp_file.name)
def _read_file(self, path: Path) -> str:
if not path.exists():
error(f'Could not find file {path}')

View File

@ -3,7 +3,7 @@ from typing import override
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration, PlymouthTheme
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
@ -66,6 +66,13 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
key='removable',
enabled=removable_enabled,
),
MenuItem(
text=tr('Plymouth'),
action=self._select_plymouth,
value=self._bootloader_conf.plymouth,
preview_action=self._prev_plymouth,
key='plymouth',
),
]
def _prev_bootloader(self, item: MenuItem) -> str | None:
@ -85,6 +92,11 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
return tr('Will install to /EFI/BOOT/ (removable location, safe default)')
return tr('Will install to custom location with NVRAM entry')
def _prev_plymouth(self, item: MenuItem) -> str | None:
if item.value:
return f'{tr("Plymouth")}: {item.value.value}'
return None
@override
async def show(self) -> BootloaderConfiguration:
_ = await super().show()
@ -117,6 +129,37 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
return bootloader
async def _select_plymouth(self, preset: PlymouthTheme | None) -> PlymouthTheme | None:
# Plymouth is purely cosmetic and a frequent source of boot breakage
# (notably with the NVIDIA driver and disk encryption), so confirm before
# enabling it. When it is already enabled the user is only changing the
# theme, so the warning is skipped.
if preset is None:
prompt = (
'[ansi_bright_yellow]'
+ tr('Plymouth adds a cosmetic boot splash but can cause boot problems on some setups:')
+ '\n\n'
+ tr('black screen with the NVIDIA driver')
+ '\n'
+ tr('hidden password prompt with disk encryption (LUKS)')
+ '\n\n'
+ tr('Would you like to enable it?')
+ '[/]\n'
)
result = await Confirmation(header=prompt, allow_skip=True, preset=False).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
if not result.get_value():
return preset
case ResultType.Reset:
raise ValueError('Unhandled result type')
return await select_plymouth_theme(preset)
async def _select_uki(self, preset: bool) -> bool:
prompt = tr('Would you like to use unified kernel images?') + '\n'
@ -215,3 +258,24 @@ async def select_bootloader(
return result.get_value()
case ResultType.Reset:
raise ValueError('Unhandled result type')
async def select_plymouth_theme(preset: PlymouthTheme | None = None) -> PlymouthTheme | None:
items = [MenuItem(t.value, value=t) for t in PlymouthTheme]
group = MenuItemGroup(items, sort_items=False)
group.set_focus_by_value(preset)
result = await Selection[PlymouthTheme](
group,
header=tr('Select Plymouth theme'),
allow_reset=True,
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Reset:
return None
case ResultType.Selection:
return PlymouthTheme(result.get_value())

View File

@ -1,171 +1,53 @@
import json
import readline
import stat
from pathlib import Path
from typing import Any
from pydantic import TypeAdapter
from archinstall.lib.args import ArchConfig, ArchConfigType
from archinstall.lib.crypt import encrypt
from archinstall.lib.log import debug, logger, warn
from archinstall.lib.args import USER_CONFIG_FILE, USER_CREDS_FILE, ArchConfig
from archinstall.lib.log import debug
from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.menu.util import get_password, prompt_dir
from archinstall.lib.translationhandler import tr
from archinstall.lib.utils.format import as_key_value_pair
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class ConfigurationOutput:
def __init__(self, config: ArchConfig):
"""
Configuration output handler to parse the existing
configuration data structure and prepare for output on the
console and for saving it to configuration files
async def confirm_config(config: ArchConfig) -> bool:
header = f'{tr("The specified configuration will be applied")}. '
header += tr('Would you like to continue?') + '\n'
:param config: Archinstall configuration object
:type config: ArchConfig
"""
group = MenuItemGroup.yes_no()
group.set_preview_for_all(lambda x: config.user_config_to_json())
self._config = config
self._default_save_path = logger.directory
self._user_config_file = Path('user_configuration.json')
self._user_creds_file = Path('user_credentials.json')
result = await Confirmation(
group=group,
header=header,
allow_skip=False,
preset=True,
preview_location='bottom',
preview_header=tr('Configuration preview'),
).show()
@property
def user_configuration_file(self) -> Path:
return self._user_config_file
if not result.get_value():
return False
@property
def user_credentials_file(self) -> Path:
return self._user_creds_file
def user_config_to_json(self) -> str:
config = self._config.safe_config()
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(config)
return json.dumps(python_dict, indent=4, sort_keys=True)
def user_credentials_to_json(self) -> str:
cfg = self._config.unsafe_config()
adapter = TypeAdapter(dict[ArchConfigType, Any])
python_dict = adapter.dump_python(cfg)
return json.dumps(python_dict, indent=4, sort_keys=True)
def write_debug(self) -> None:
debug(' -- Chosen configuration --')
debug(self.user_config_to_json())
def as_summary(self) -> str:
"""
Render a concise two-column summary of the current configuration.
Returns an empty string if nothing meaningful to show.
"""
cfg: dict[str, str | list[str] | bool] = {}
for key, value in self._config.plain_cfg().items():
cfg[key.text()] = value
for config_type, obj in self._config.sub_cfg().items():
if not hasattr(obj, 'summary'):
continue
summary = obj.summary()
if summary:
cfg[config_type.text()] = summary
simple_summary = as_key_value_pair(cfg, ignore_empty=True)
return simple_summary
async def confirm_config(self) -> bool:
header = f'{tr("The specified configuration will be applied")}. '
header += tr('Would you like to continue?') + '\n'
group = MenuItemGroup.yes_no()
group.set_preview_for_all(lambda x: self.user_config_to_json())
result = await Confirmation(
group=group,
header=header,
allow_skip=False,
preset=True,
preview_location='bottom',
preview_header=tr('Configuration preview'),
).show()
if not result.get_value():
return False
return True
def _is_valid_path(self, dest_path: Path) -> bool:
dest_path_ok = dest_path.exists() and dest_path.is_dir()
if not dest_path_ok:
warn(
f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
'Configuration files can not be saved',
)
return dest_path_ok
def save_user_config(self, dest_path: Path) -> None:
if self._is_valid_path(dest_path):
target = dest_path / self._user_config_file
target.write_text(self.user_config_to_json())
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def save_user_creds(
self,
dest_path: Path,
password: str | None = None,
) -> None:
data = self.user_credentials_to_json()
if password:
data = encrypt(password, data)
if self._is_valid_path(dest_path):
target = dest_path / self._user_creds_file
target.write_text(data)
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def save(
self,
dest_path: Path | None = None,
creds: bool = False,
password: str | None = None,
) -> None:
save_path = dest_path or self._default_save_path
if self._is_valid_path(save_path):
self.save_user_config(save_path)
if creds:
self.save_user_creds(save_path, password=password)
return True
async def save_config(config: ArchConfig) -> None:
def preview(item: MenuItem) -> str | None:
match item.value:
case 'user_config':
serialized = config_output.user_config_to_json()
return f'{config_output.user_configuration_file}\n{serialized}'
serialized = config.user_config_to_json()
return f'{USER_CONFIG_FILE}\n{serialized}'
case 'user_creds':
if maybe_serial := config_output.user_credentials_to_json():
return f'{config_output.user_credentials_file}\n{maybe_serial}'
if maybe_serial := config.user_credentials_to_json():
return f'{USER_CREDS_FILE}\n{maybe_serial}'
return tr('No configuration')
case 'all':
output = [str(config_output.user_configuration_file)]
config_output.user_credentials_to_json()
output.append(str(config_output.user_credentials_file))
output = [str(USER_CONFIG_FILE)]
config.user_credentials_to_json()
output.append(str(USER_CREDS_FILE))
return '\n'.join(output)
return None
config_output = ConfigurationOutput(config)
items = [
MenuItem(
tr('Save user configuration (including disk layout)'),
@ -248,8 +130,8 @@ async def save_config(config: ArchConfig) -> None:
match save_option:
case 'user_config':
config_output.save_user_config(dest_path)
config.save_user_config(dest_path)
case 'user_creds':
config_output.save_user_creds(dest_path, password=enc_password)
config.save_user_creds(dest_path, password=enc_password)
case 'all':
config_output.save(dest_path, creds=True, password=enc_password)
config.save(dest_path, creds=True, password=enc_password)

View File

@ -311,6 +311,10 @@ async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | N
devices = device_handler.devices
if len(devices) < 1:
await Notify(tr('No disks were detected. A disk is required to be able to install Arch Linux')).show()
return None
items = [
MenuItem(
str(d.device_info.path),

View File

@ -6,7 +6,7 @@ from archinstall.lib.args import ArchConfig
from archinstall.lib.authentication.authentication_menu import AuthenticationMenu
from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu
from archinstall.lib.bootloader.utils import validate_bootloader_layout
from archinstall.lib.configuration import ConfigurationOutput, save_config
from archinstall.lib.configuration import save_config
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
from archinstall.lib.exceptions import SysCallError
from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone
@ -581,7 +581,6 @@ class GlobalMenu(AbstractMenu[None]):
def _prev_install_invalid_config(self, item: MenuItem) -> PreviewResult | None:
self.sync_all_to_config()
config_output = ConfigurationOutput(self._arch_config)
warnings = self._get_install_warnings()
messages: list[tuple[str, MsgLevelType]] = []
@ -608,7 +607,7 @@ class GlobalMenu(AbstractMenu[None]):
messages.append((text, MsgLevelType.MsgWarning))
if not errors:
summary = config_output.as_summary()
summary = self._arch_config.as_summary()
if summary:
messages.append((summary, MsgLevelType.MsgNone))

View File

@ -32,7 +32,7 @@ from archinstall.lib.locale.utils import verify_keyboard_layout, verify_x11_keyb
from archinstall.lib.log import debug, error, info, log, logger, warn
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
from archinstall.lib.models.application import ZramAlgorithm
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration, PlymouthTheme
from archinstall.lib.models.device import (
DiskEncryption,
DiskLayoutConfiguration,
@ -228,6 +228,9 @@ class Installer:
while self._service_state('archlinux-keyring-wkd-sync.service') not in ('dead', 'failed', 'exited'):
time.sleep(1)
if self._service_state('archlinux-keyring-wkd-sync.service') == 'failed':
warn('archlinux-keyring-wkd-sync failed, keyring may need reinit during pacman sync')
def _verify_boot_part(self) -> None:
"""
Check that mounted /boot device has at minimum size for installation
@ -1755,6 +1758,28 @@ class Installer:
self._helper_flags['bootloader'] = 'refind'
def _install_plymouth(self, plymouth: PlymouthTheme) -> None:
debug(f'Installing plymouth with theme: {plymouth.value}')
self.add_additional_packages(['plymouth'])
for param in ('quiet', 'splash'):
if param not in self._kernel_params:
self._kernel_params.append(param)
if 'plymouth' not in self._hooks:
for hook, insert_after in [('encrypt', False), ('sd-encrypt', False), ('systemd', True), ('filesystems', False), ('keyboard', True)]:
try:
idx = self._hooks.index(hook)
self._hooks.insert(idx + (1 if insert_after else 0), 'plymouth')
break
except ValueError:
continue
else:
self._hooks.append('plymouth')
self.arch_chroot(f'plymouth-set-default-theme {plymouth.value}')
self.mkinitcpio(['-P'])
def _config_uki(
self,
root: PartitionModification | LvmVolume,
@ -1807,10 +1832,7 @@ class Installer:
error('Error generating initramfs (continuing anyway)')
def add_bootloader(
self,
bootloader: Bootloader,
uki_enabled: bool = False,
bootloader_removable: bool = False,
self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False, plymouth: PlymouthTheme | None = None
) -> None:
"""
Adds a bootloader to the installation instance.
@ -1824,6 +1846,7 @@ class Installer:
:param bootloader: Type of bootloader to be added
:param uki_enabled: Whether to use unified kernel images
:param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine)
:param plymouth: Optional Plymouth theme to install and configure
"""
for plugin in plugins.values():
@ -1859,6 +1882,9 @@ class Installer:
warn(f'Bootloader {bootloader.value} lacks removable support; disabling.')
bootloader_removable = False
if plymouth is not None:
self._install_plymouth(plymouth)
if uki_enabled:
keep_initramfs = (
bootloader == Bootloader.Grub

View File

@ -19,14 +19,15 @@ async def get_password(
def password_hint(value: str) -> InputInfo | None:
if not value:
return None
strength = PasswordStrength.strength(value)
if strength in (PasswordStrength.VERY_WEAK, PasswordStrength.WEAK):
return InputInfo(message=tr('Password strength: Weak'), msg_level=MsgLevelType.MsgError)
elif strength == PasswordStrength.MODERATE:
return InputInfo(message=tr('Password strength: Moderate'), msg_level=MsgLevelType.MsgWarning)
elif strength == PasswordStrength.STRONG:
return InputInfo(message=tr('Password strength: Strong'), msg_level=MsgLevelType.MsgInfo)
return None
match strength:
case PasswordStrength.VERY_WEAK | PasswordStrength.WEAK:
return InputInfo(message=tr('Password strength: Weak'), msg_level=MsgLevelType.MsgError)
case PasswordStrength.MODERATE:
return InputInfo(message=tr('Password strength: Moderate'), msg_level=MsgLevelType.MsgWarning)
case PasswordStrength.STRONG:
return InputInfo(message=tr('Password strength: Strong'), msg_level=MsgLevelType.MsgInfo)
while True:
result = await Input(

View File

@ -60,15 +60,48 @@ class Bootloader(Enum):
return cls(bootloader)
class PlymouthTheme(Enum):
BGRT = 'bgrt'
FADE = 'fade-in'
GLOW = 'glow'
SCRIPT = 'script'
SOLAR = 'solar'
SPINNER = 'spinner'
SPINFINITY = 'spinfinity'
TRIBAR = 'tribar'
TEXT = 'text'
DETAILS = 'details'
@classmethod
def from_arg(cls, plymouth: str | None) -> Self | None:
if plymouth is None:
return None
plymouth = plymouth.lower()
values = [e.value for e in cls]
if plymouth not in values:
warn(f'Invalid plymouth value "{plymouth}". Allowed values: {", ".join(values)}')
sys.exit(1)
return cls(plymouth)
@dataclass
class BootloaderConfiguration(SubConfig):
bootloader: Bootloader
uki: bool = False
removable: bool = True
plymouth: PlymouthTheme | None = None
@override
def json(self) -> dict[str, Any]:
return {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable}
data = {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable}
if self.plymouth is not None:
data['plymouth'] = self.plymouth.value
return data
@override
def summary(self) -> list[str]:
@ -78,6 +111,8 @@ class BootloaderConfiguration(SubConfig):
out.append(tr('UKI enabled'))
if self.removable:
out.append(tr('Removable'))
if self.plymouth is not None:
out.append(tr('Plymouth "{}"').format(self.plymouth.value))
return out
@ -86,14 +121,16 @@ class BootloaderConfiguration(SubConfig):
bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot)
uki = config.get('uki', False)
removable = config.get('removable', True)
return cls(bootloader=bootloader, uki=uki, removable=removable)
plymouth = PlymouthTheme.from_arg(config.get('plymouth', None))
return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth)
@classmethod
def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self:
bootloader = Bootloader.get_default(uefi, skip_boot)
removable = uefi and bootloader.has_removable_support()
uki = uefi and bootloader.has_uki_support()
return cls(bootloader=bootloader, uki=uki, removable=removable)
plymouth = None
return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth)
def preview(self, uefi: bool) -> str:
text = f'{tr("Bootloader")}: {self.bootloader.value}'
@ -112,4 +149,7 @@ class BootloaderConfiguration(SubConfig):
removable_string = tr('Disabled')
text += f'{tr("Removable")}: {removable_string}'
text += '\n'
if self.plymouth is not None:
text += f'{tr("Plymouth")}: {self.plymouth.value}'
text += '\n'
return text

View File

@ -4,8 +4,8 @@ from collections.abc import Callable
from pathlib import Path
from archinstall.lib.command import SysCommand
from archinstall.lib.exceptions import RequirementError
from archinstall.lib.log import error, info, warn
from archinstall.lib.exceptions import RequirementError, SysCallError
from archinstall.lib.log import debug, error, info, warn
from archinstall.lib.pathnames import PACMAN_CONF
from archinstall.lib.plugins import plugins
from archinstall.lib.translationhandler import tr
@ -53,15 +53,45 @@ class Pacman:
def sync(self) -> None:
if self.synced:
return
self.ask(
'Could not sync a new package database',
'Could not sync mirrors',
self.run,
'-Syy',
default_cmd='pacman',
)
try:
self.run('-Syy')
except SysCallError as err:
if b'GPGME' in err.worker_log or b'keyring' in err.worker_log.lower():
warn('Pacman sync failed with keyring error, attempting keyring reinit')
self._reinit_keyring()
msg = 'Could not sync a new package database after keyring reinit'
else:
msg = 'Could not sync a new package database'
self.ask(msg, 'Could not sync mirrors', self.run, '-Syy')
self.synced = True
@staticmethod
def _is_running(process: str) -> bool:
try:
SysCommand(f'pgrep -x {process}')
return True
except SysCallError:
debug(f'{process} is not running')
return False
@staticmethod
def _reinit_keyring() -> None:
if Pacman._is_running('gpg-agent'):
try:
SysCommand('killall gpg-agent')
except SysCallError as err:
debug(f'Failed to kill gpg-agent: {err}')
try:
SysCommand('pacman-key --init')
SysCommand('pacman-key --populate archlinux')
debug('Keyring reinitialized successfully')
except SysCallError as err:
debug(f'Keyring reinit failed: {err}')
def strap(self, packages: str | list[str]) -> None:
self.sync()
if isinstance(packages, str):

View File

@ -1,9 +1,6 @@
import hashlib
import importlib.util
import os
import sys
import urllib.parse
import urllib.request
from importlib import metadata
from pathlib import Path
@ -34,23 +31,6 @@ def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
plugins[f.__name__] = f
def _localize_path(path: Path) -> Path:
"""
Support structures for load_plugin()
"""
url = urllib.parse.urlparse(str(path))
if url.scheme and url.scheme in ('https', 'http'):
converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py')
with open(converted_path, 'w') as temp_file:
temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8'))
return converted_path
else:
return path
def _import_via_path(path: Path, namespace: str | None = None) -> str:
if not namespace:
namespace = os.path.basename(path)
@ -82,17 +62,10 @@ def _import_via_path(path: Path, namespace: str | None = None) -> str:
def load_plugin(path: Path) -> None:
namespace: str | None = None
parsed_url = urllib.parse.urlparse(str(path))
info(f'Loading plugin from url {parsed_url}')
info(f'Loading plugin from {path}')
# The Profile was not a direct match on a remote URL
if not parsed_url.scheme:
# Path was not found in any known examples, check if it's an absolute path
if os.path.isfile(path):
namespace = _import_via_path(path)
elif parsed_url.scheme in ('https', 'http'):
localized = _localize_path(path)
namespace = _import_via_path(localized)
if os.path.isfile(path):
namespace = _import_via_path(path)
if namespace and namespace in sys.modules:
# Version dependency via __archinstall__version__ variable (if present) in the plugin

View File

@ -1,6 +1,7 @@
import secrets
import string
from datetime import UTC, datetime
from pathlib import Path
from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT
from archinstall.lib.utils.format import as_columns
@ -46,3 +47,7 @@ def format_cols(items: list[str], header: str | None = None) -> str:
# remove whitespaces on each row
text = '\n'.join(t.strip() for t in text.split('\n'))
return text
def is_valid_path(path: Path) -> bool:
return path.exists() and path.is_dir()

View File

@ -209,12 +209,29 @@ msgstr ""
msgid "Install to removable location"
msgstr ""
msgid "Plymouth"
msgstr ""
msgid "Will install to /EFI/BOOT/ (removable location, safe default)"
msgstr ""
msgid "Will install to custom location with NVRAM entry"
msgstr ""
msgid ""
"Plymouth adds a cosmetic boot splash but can cause boot problems on some "
"setups:"
msgstr ""
msgid "black screen with the NVIDIA driver"
msgstr ""
msgid "hidden password prompt with disk encryption (LUKS)"
msgstr ""
msgid "Would you like to enable it?"
msgstr ""
msgid "Would you like to use unified kernel images?"
msgstr ""
@ -248,6 +265,9 @@ msgstr ""
msgid "UEFI is not detected and some options are disabled"
msgstr ""
msgid "Select Plymouth theme"
msgstr ""
msgid "The specified configuration will be applied"
msgstr ""
@ -888,6 +908,10 @@ msgstr ""
msgid "Removable"
msgstr ""
#, python-brace-format
msgid "Plymouth \"{}\""
msgstr ""
msgid "Use a best-effort default partition layout"
msgstr ""

View File

@ -1423,7 +1423,7 @@ msgid "Yes"
msgstr "ναι"
msgid "No"
msgstr ""
msgstr "όχι"
#, fuzzy
msgid "Archinstall help"
@ -1434,7 +1434,7 @@ msgid " (default)"
msgstr "(προκαθορισμένο)"
msgid "Press Ctrl+h for help"
msgstr ""
msgstr "Πατήστε Ctrl+h για βοήθεια"
#, fuzzy
msgid "Choose an option to give Sway access to your hardware"
@ -1621,7 +1621,7 @@ msgid "Select execution mode"
msgstr "Επιλέξτε μία ενέργεια για '{}'"
msgid "Press ? for help"
msgstr ""
msgstr "Πατήστε ? για βοήθεια"
#, fuzzy
msgid "Choose an option to give Hyprland access to your hardware"
@ -1754,31 +1754,31 @@ msgid "Only ASCII characters are supported"
msgstr ""
msgid "Show help"
msgstr ""
msgstr "Εμφάνιση βοήθειας"
msgid "Exit help"
msgstr ""
msgstr "Έξοδος από τη βοήθεια"
msgid "Preview scroll up"
msgstr ""
msgstr "Κύλιση προεπισκόπησης πάνω"
msgid "Preview scroll down"
msgstr ""
msgstr "Κύλιση προεπισκόπησης κάτω"
msgid "Move up"
msgstr ""
msgstr "Μετακίνηση πάνω"
msgid "Move down"
msgstr ""
msgstr "Μετακίνηση κάτω"
msgid "Move right"
msgstr ""
msgstr "Μετακίνηση δεξιά"
msgid "Move left"
msgstr ""
msgstr "Μετακίνηση αριστερά"
msgid "Jump to entry"
msgstr ""
msgstr "Μετάβαση σε καταχώρηση"
msgid "Skip selection (if available)"
msgstr ""
@ -1795,20 +1795,20 @@ msgid "Select on multi select"
msgstr "Επιλέξτε μία ζώνη ώρας"
msgid "Reset"
msgstr ""
msgstr "Επαναφορά"
#, fuzzy
msgid "Skip selection menu"
msgstr "Επιλέξτε μία ενέργεια για '{}'"
msgid "Start search mode"
msgstr ""
msgstr "Έναρξη λειτουργίας αναζήτησης"
msgid "Exit search mode"
msgstr ""
msgstr "Έξοδος από τη λειτουργία αναζήτησης"
msgid "General"
msgstr ""
msgstr "Γενικά"
#, fuzzy
msgid "Navigation"
@ -1819,115 +1819,115 @@ msgid "Selection"
msgstr "Επιλέξτε διεπαφή προς προσθήκη"
msgid "Search"
msgstr ""
msgstr "Αναζήτηση"
msgid "Down"
msgstr ""
msgstr "Κάτω"
msgid "Up"
msgstr ""
msgstr "Πάνω"
#, fuzzy
msgid "Confirm"
msgstr "Επιβεβαίωση και έξοδος"
msgid "Focus right"
msgstr ""
msgstr "Εστίαση δεξιά"
msgid "Focus left"
msgstr ""
msgstr "Εστίαση αριστερά"
msgid "Toggle"
msgstr ""
msgstr "Εναλλαγή"
msgid "Show/Hide help"
msgstr ""
msgstr "Εμφάνιση/Απόκρυψη βοήθειας"
msgid "Quit"
msgstr ""
msgstr "Έξοδος"
msgid "First"
msgstr ""
msgstr "Πρώτο"
msgid "Last"
msgstr ""
msgstr "Τελευταίο"
#, fuzzy
msgid "Select"
msgstr "Επιλέξτε διεπαφή προς προσθήκη"
msgid "Page Up"
msgstr ""
msgstr "Σελίδα πάνω"
msgid "Page Down"
msgstr ""
msgstr "Σελίδα κάτω"
msgid "Page up"
msgstr ""
msgstr "Σελίδα πάνω"
msgid "Page down"
msgstr ""
msgstr "Σελίδα κάτω"
msgid "Page Left"
msgstr ""
msgstr "Σελίδα αριστερά"
msgid "Page Right"
msgstr ""
msgstr "Σελίδα δεξιά"
msgid "Cursor up"
msgstr ""
msgstr "Δρομέας πάνω"
msgid "Cursor down"
msgstr ""
msgstr "Δρομέας κάτω"
msgid "Cursor right"
msgstr ""
msgstr "Δρομέας δεξιά"
msgid "Cursor left"
msgstr ""
msgstr "Δρομέας αριστερά"
msgid "Top"
msgstr ""
msgstr "Κορυφή"
msgid "Bottom"
msgstr ""
msgstr "Βάση"
msgid "Home"
msgstr ""
msgstr "Αρχή"
msgid "End"
msgstr ""
msgstr "Τέλος"
#, fuzzy
msgid "Toggle option"
msgstr "Επιλογές υποόγκου"
msgid "Scroll Up"
msgstr ""
msgstr "Κύλιση πάνω"
msgid "Scroll Down"
msgstr ""
msgstr "Κύλιση κάτω"
msgid "Scroll Left"
msgstr ""
msgstr "Κύλιση αριστερά"
msgid "Scroll Right"
msgstr ""
msgstr "Κύλιση δεξιά"
msgid "Scroll Home"
msgstr ""
msgstr "Κύλιση στην αρχή"
msgid "Scroll End"
msgstr ""
msgstr "Κύλιση στο τέλος"
msgid "Focus Next"
msgstr ""
msgstr "Εστίαση στο επόμενο"
msgid "Focus Previous"
msgstr ""
msgstr "Εστίαση στο προηγούμενο"
msgid "Copy selected text"
msgstr ""
msgstr "Αντιγραφή επιλεγμένου κειμένου"
msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
msgstr ""
@ -2115,7 +2115,7 @@ msgid "Enter wifi password"
msgstr "Εισάγετε κωδικό: "
msgid "Ok"
msgstr ""
msgstr "Εντάξει"
msgid "Removable"
msgstr ""

View File

@ -9,7 +9,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.8\n"
"X-Generator: Poedit 3.9\n"
msgid "Recommended"
msgstr "推奨"
@ -103,7 +103,7 @@ msgid "Select font packages to install"
msgstr "インストールするフォントパッケージを選択"
msgid "ArchInstall Language"
msgstr "ArchInstall の言語"
msgstr "archInstall の言語"
msgid "Version"
msgstr "バージョン"
@ -166,7 +166,7 @@ msgid "Users"
msgstr "ユーザー"
msgid "Root encrypted password"
msgstr "Root 暗号化パスワード"
msgstr "root 暗号化パスワード"
msgid "Disk encryption password"
msgstr "ディスク暗号化パスワード"
@ -221,6 +221,9 @@ msgstr "Unified カーネルイメージ"
msgid "Install to removable location"
msgstr "リムーバブルな場所にインストールする"
msgid "Plymouth"
msgstr "Plymouth"
msgid "Will install to /EFI/BOOT/ (removable location, safe default)"
msgstr "/EFI/BOOT/(リムーバブルな場所、安全なデフォルト)にインストール"
@ -254,6 +257,9 @@ msgstr "インストールするブートローダーを選択"
msgid "UEFI is not detected and some options are disabled"
msgstr "UEFIが検出されず、一部のオプションが無効になります"
msgid "Select Plymouth theme"
msgstr "Plymouth のテーマを選択"
msgid "The specified configuration will be applied"
msgstr "指定された設定が適用されます"
@ -263,9 +269,6 @@ msgstr "続行しますか?"
msgid "Configuration preview"
msgstr "設定プレビュー"
msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system."
msgstr "警告: ネットワーク設定が選択されていません。インストール後のシステムで、ネットワークを手動で設定する必要があります。"
msgid "No configuration"
msgstr "設定なし"
@ -279,7 +282,7 @@ msgid "Save all"
msgstr "すべて保存"
msgid "Enter a directory for the configuration(s) to be saved"
msgstr "設定を保存するディレクトリを入力"
msgstr "設定を保存するディレクトリを入力してください"
#, python-brace-format
msgid "Do you want to save the configuration file(s) to {}?"
@ -348,7 +351,7 @@ msgid "You will use whatever drive-setup is mounted at the specified directory"
msgstr "指定されたディレクトリにマウントされているドライブの設定を使用します"
msgid "WARNING: Archinstall won't check the suitability of this setup"
msgstr "警告: Archinstall はこのセットアップの適合性をチェックしません"
msgstr "警告: archinstall はこのセットアップの適合性をチェックしません"
msgid "Select main filesystem"
msgstr "メインファイルシステムを選択"
@ -369,7 +372,7 @@ msgid "Would you like to create a separate partition for /home?"
msgstr "/home 用に別のパーティションを作成しますか?"
msgid "The selected drives do not have the minimum capacity required for an automatic suggestion\n"
msgstr "選択したドライブには、自動提案に必要な最小容量がありません\n"
msgstr "選択したドライブは、自動提案に必要な最小容量を満たしていません\n"
#, python-brace-format
msgid "Minimum capacity for /home partition: {}GiB\n"
@ -570,13 +573,13 @@ msgid "For the best compatibility with your Nvidia hardware, you may want to use
msgstr "Nvidia ハードウェアとの互換性を最大限に高めるには、Nvidia 独自のドライバーを使用することをお勧めします。\n"
msgid "Would you like to use swap on zram?"
msgstr "zram でスワップを使用しますか?"
msgstr "zram でスワップを使用しますか?"
msgid "Select zram compression algorithm:"
msgstr "zram 圧縮アルゴリズムを選択:"
msgstr "zram 圧縮アルゴリズムを選択:"
msgid "Archinstall language"
msgstr "Archinstall の言語"
msgstr "archinstall の言語"
msgid "Applications"
msgstr "アプリケーション"
@ -613,7 +616,7 @@ msgid "Btrfs snapshot type: {}"
msgstr "Btrfs スナップショットのタイプ: {}"
msgid "Swap on zram"
msgstr "Zram 上でスワップ"
msgstr "zram 上でスワップ"
msgid "Compression algorithm"
msgstr "圧縮アルゴリズム"
@ -627,16 +630,21 @@ msgstr "カラー出力"
msgid "Kernel"
msgstr "カーネル"
msgid "Missing configurations:\n"
msgstr "設定が存在しません:\n"
msgid "No network configuration selected. Network will need to be set up manually on the installed system."
msgstr "ネットワーク設定が選択されていません。インストール後のシステムで、ネットワークを手動で設定する必要があります。"
#, python-brace-format
msgid "Invalid configuration: {}"
msgstr "無効な設定: {}"
msgid "Missing configurations:"
msgstr "不足している設定:"
msgid "Invalid configuration:"
msgstr "無効な設定:"
msgid "Ready to install"
msgstr "インストールの準備が完了"
msgid "Warnings:"
msgstr "警告:"
msgid "Profiles"
msgstr "プロファイル"
@ -804,11 +812,11 @@ msgid "wide Unicode coverage, good fallback font"
msgstr "Unicode を広くカバーする良い代替フォント"
msgid "Zram enabled"
msgstr "Zram を有効にする"
msgstr "zram を有効にする"
#, python-brace-format
msgid "Zram algorithm {}"
msgstr "Zram アルゴリズム {}"
msgstr "zram のアルゴリズム {}"
msgid "Bluetooth enabled"
msgstr "Bluetooth を有効にする"
@ -839,7 +847,7 @@ msgid "Second factor login"
msgstr "2要素ログイン"
msgid "Root password set"
msgstr "Root パスワードを設定"
msgstr "root パスワードを設定"
#, python-brace-format
msgid "Configured {} user(s)"
@ -858,6 +866,10 @@ msgstr "UKI を有効にする"
msgid "Removable"
msgstr "リムーバブル"
#, python-brace-format
msgid "Plymouth \"{}\""
msgstr "Plymouth \"{}\""
msgid "Use a best-effort default partition layout"
msgstr "ベストエフォートのデフォルトパーティションレイアウトを使用"
@ -965,7 +977,7 @@ msgid "Color enabled"
msgstr "カラー出力を有効にする"
#, python-brace-format
msgid "{} grphics driver"
msgid "{} graphics driver"
msgstr "{} グラフィックドライバー"
#, python-brace-format
@ -1031,15 +1043,15 @@ msgstr "Wi-Fi に接続しますか?"
msgid "No wifi interface found"
msgstr "Wi-Fi インターフェースが見つかりません"
msgid "No wifi networks found"
msgstr "Wi-Fi ネットワークが見つかりません"
msgid "Select wifi network to connect to"
msgstr "接続する Wi-Fi ネットワークを選択"
msgid "Scanning wifi networks..."
msgstr "Wi-Fi ネットワークをスキャンしています..."
msgid "No wifi networks found"
msgstr "Wi-Fi ネットワークが見つかりません"
msgid "Failed setting up wifi"
msgstr "Wi-Fi の設定に失敗しました"
@ -1137,7 +1149,7 @@ msgstr "パスワードを入力"
#, python-brace-format
msgid "Should \"{}\" be a superuser (sudo)?\n"
msgstr "\"{}\" はスーパーユーザーに昇格しますかsudo\n"
msgstr "\"{}\" に sudo 権限を与えますか\n"
#, python-brace-format
msgid "About to upload \"{}\" to the publicly accessible {}"
@ -1154,7 +1166,7 @@ msgid "Failed to upload log."
msgstr "ログのアップロードに失敗しました。"
msgid "Archinstall requires root privileges to run. See --help for more."
msgstr "Archinstall を実行するには root 権限が必要です。詳細については --help を参照してください。"
msgstr "archinstall を実行するには root 権限が必要です。詳細については --help を参照してください。"
msgid "New version available"
msgstr "新しいバージョンが利用可能"
@ -1608,9 +1620,6 @@ msgstr " (デフォルト)"
#~ msgid "Subvolume name :"
#~ msgstr "サブボリューム名:"
#~ msgid "Select a mount point :"
#~ msgstr "マウントポイントを選択:"
#~ msgid "Select the desired subvolume options "
#~ msgstr "サブボリュームのオプションを選択"

View File

@ -116,7 +116,7 @@
{"abbr": "ne", "lang": "Nepali", "translated_lang": "नेपाली", "sys_lang": "ne_NP.UTF-8", "default_timezone": "Asia/Kathmandu"},
{"abbr": "nl", "lang": "Dutch", "translated_lang": "Nederlands", "sys_lang": "nl_NL.UTF-8", "default_timezone": "Europe/Amsterdam"},
{"abbr": "nn", "lang": "Norwegian Nynorsk"},
{"abbr": "nb", "lang": "Norwegian Bokmål"},
{"abbr": "nb", "lang": "Norwegian Bokmål", "translated_lang": "Norsk bokmål"},
{"abbr": "no", "lang": "Norwegian"},
{"abbr": "ny", "lang": "Nyanja"},
{"abbr": "oc", "lang": "Occitan (post 1500)"},
@ -173,7 +173,7 @@
{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو", "sys_lang": "ur_PK.UTF-8", "default_timezone": "Asia/Karachi"},
{"abbr": "uz", "lang": "Uzbek", "translated_lang": "O'zbek", "sys_lang": "uz_UZ.UTF-8", "default_timezone": "Asia/Tashkent"},
{"abbr": "ve", "lang": "Venda"},
{"abbr": "vi", "lang": "Vietnamese"},
{"abbr": "vi", "lang": "Vietnamese", "translated_lang": "Tiếng Việt"},
{"abbr": "vo", "lang": "Volapük"},
{"abbr": "wa", "lang": "Walloon"},
{"abbr": "wo", "lang": "Wolof"},

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -2395,3 +2395,33 @@ msgstr "набір апаратних пристроїв, напр. клавіа
msgid "No network configuration selected. Network will need to be set up manually on the installed system."
msgstr "Конфігурацію мережі не обрано. Мережу доведеться налаштувати вручну на встановленій системі."
msgid "Plymouth"
msgstr "Plymouth"
msgid ""
"Plymouth adds a cosmetic boot splash but can cause boot problems on some "
"setups:"
msgstr ""
"Plymouth додає косметичну заставку завантаження, але на деяких конфігураціях "
"може спричинити проблеми із завантаженням:"
msgid "black screen with the NVIDIA driver"
msgstr "чорний екран із драйвером NVIDIA"
msgid "hidden password prompt with disk encryption (LUKS)"
msgstr "прихований запит пароля при шифруванні диска (LUKS)"
msgid "Would you like to enable it?"
msgstr "Увімкнути Plymouth?"
msgid "Select Plymouth theme"
msgstr "Оберіть тему Plymouth"
#, python-brace-format
msgid "Plymouth \"{}\""
msgstr "Plymouth \"{}\""
#, python-brace-format
msgid "{} graphics driver"
msgstr "Графічний драйвер {}"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ from archinstall.lib.applications.application_handler import ApplicationHandler
from archinstall.lib.args import ArchConfig, ArchConfigHandler
from archinstall.lib.authentication.authentication_handler import AuthenticationHandler
from archinstall.lib.bootloader.utils import validate_bootloader_layout
from archinstall.lib.configuration import ConfigurationOutput
from archinstall.lib.configuration import confirm_config
from archinstall.lib.disk.filesystem import FilesystemHandler
from archinstall.lib.disk.utils import disk_layouts
from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation
@ -119,6 +119,7 @@ def perform_installation(
config.bootloader_config.bootloader,
config.bootloader_config.uki,
config.bootloader_config.removable,
config.bootloader_config.plymouth,
)
if config.network_config:
@ -212,9 +213,8 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
if not arch_config_handler.args.silent:
show_menu(arch_config_handler, mirror_list_handler)
config = ConfigurationOutput(arch_config_handler.config)
config.write_debug()
config.save()
arch_config_handler.config.write_debug()
arch_config_handler.config.save()
# Safety net for silent/config-file flow. The TUI menu blocks Install via
# GlobalMenu._validate_bootloader() before reaching this point.
@ -230,7 +230,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
if not arch_config_handler.args.silent:
aborted = False
res: bool = tui.run(config.confirm_config)
res: bool = tui.run(lambda: confirm_config(arch_config_handler.config))
if not res:
debug('Installation aborted')

View File

@ -1,6 +1,6 @@
from archinstall.default_profiles.minimal import MinimalProfile
from archinstall.lib.args import ArchConfigHandler
from archinstall.lib.configuration import ConfigurationOutput
from archinstall.lib.configuration import confirm_config
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
from archinstall.lib.disk.filesystem import FilesystemHandler
from archinstall.lib.installer import Installer
@ -68,16 +68,15 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
disk_config = await DiskLayoutConfigurationMenu(disk_layout_config=None).show()
arch_config_handler.config.disk_config = disk_config
config = ConfigurationOutput(arch_config_handler.config)
config.write_debug()
config.save()
arch_config_handler.config.write_debug()
arch_config_handler.config.save()
if arch_config_handler.args.dry_run:
return
if not arch_config_handler.args.silent:
aborted = False
res: bool = tui.run(config.confirm_config)
res: bool = tui.run(lambda: confirm_config(arch_config_handler.config))
if not res:
debug('Installation aborted')

View File

@ -2,7 +2,7 @@ import sys
from pathlib import Path
from archinstall.lib.args import ArchConfig, ArchConfigHandler
from archinstall.lib.configuration import ConfigurationOutput
from archinstall.lib.configuration import confirm_config
from archinstall.lib.disk.filesystem import FilesystemHandler
from archinstall.lib.disk.utils import disk_layouts
from archinstall.lib.global_menu import GlobalMenu
@ -69,16 +69,15 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
if not arch_config_handler.args.silent:
show_menu(arch_config_handler)
config = ConfigurationOutput(arch_config_handler.config)
config.write_debug()
config.save()
arch_config_handler.config.write_debug()
arch_config_handler.config.save()
if arch_config_handler.args.dry_run:
return
if not arch_config_handler.args.silent:
aborted = False
res: bool = tui.run(config.confirm_config)
res: bool = tui.run(lambda: confirm_config(arch_config_handler.config))
if not res:
debug('Installation aborted')

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "archinstall"
version = "4.3"
version = "4.4"
description = "Arch Linux installer - guided, templates etc."
authors = [
{name = "Anton Hvornum", email = "anton@hvornum.se"},
@ -20,8 +20,8 @@ classifiers = [
dependencies = [
"pyparted==3.13.0",
"pydantic==2.13.4",
"cryptography==48.0.0",
"textual==8.2.7",
"cryptography==49.0.0",
"textual==8.2.8",
"markdown-it-py==4.0.0",
"linkify-it-py==2.1.0",
]
@ -32,14 +32,14 @@ Documentation = "https://archinstall.readthedocs.io/"
Source = "https://github.com/archlinux/archinstall"
[project.optional-dependencies]
log = ["systemd_python==235"]
log = ["systemd_python==236"]
dev = [
"mypy==2.1.0",
"mypy==2.3.0",
"flake8==7.3.0",
"pre-commit==4.6.0",
"ruff==0.15.16",
"pylint==4.0.5",
"pytest==9.0.3",
"ruff==0.15.21",
"pylint==4.0.6",
"pytest==9.1.1",
"hypothesis>=6.152.4",
]
doc = ["sphinx"]
@ -72,7 +72,6 @@ python_version = "3.14"
files = "."
exclude = [
"^build/",
"^test_tooling/",
]
disallow_any_explicit = false
disallow_any_expr = false

View File

@ -11,6 +11,8 @@ import sys
import time
from argparse import ArgumentParser
from collections.abc import Iterator
from functools import cache
from pathlib import Path
from select import EPOLLHUP, EPOLLIN, epoll
from shutil import which
from types import TracebackType
@ -25,25 +27,32 @@ class ArgumentError(Exception):
pass
def get_master(interface):
def cpu_count() -> int:
count = os.cpu_count()
if not count:
return 1
return count - 1
def get_master(interface: str) -> str | None:
master_path = pathlib.Path(f'/sys/class/net/{interface}/master')
return master_path.readlink().name if master_path.exists() else None
def gray(text):
def gray(text: str) -> str:
return f'\033[38;5;246m{text}\033[0m'
def orange(text):
def orange(text: str) -> str:
return f'\033[38;5;208m{text}\033[0m'
def red(text):
def red(text: str) -> str:
return f'\033[31m{text}\033[0m'
sudo_password = None # Gets populated later
harddrives = {}
harddrives: dict[Path, str] = {}
username = getpass.getuser()
groupname = grp.getgrgid(os.getgid()).gr_name
@ -71,37 +80,37 @@ hardware = parser.add_argument_group('Hardware', 'General hardware specs for the
hardware.add_argument('--bios', action='store_true', help='Disables EFI (edk2/ovmf) and uses BIOS support instead', default=False)
hardware.add_argument('--memory', nargs='?', help='Ammount of memory to supply the machine', default=8192)
hardware.add_argument('--harddrive', action='append', help='Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G', type=str)
hardware.add_argument('--cpu', help='Sets the number of cores to allocate (default nproc -1)', type=str, default=os.cpu_count() - 1 if os.cpu_count() else 1)
hardware.add_argument('--cpu', help='Sets the number of cores to allocate (default nproc -1)', type=str, default=cpu_count())
hardware.add_argument('--resolution', help="Sets Qemu's VGA resolution", type=str, default='1920x1107')
kernel = parser.add_argument_group('Kernel', '--kernel specific arguments')
kernel.add_argument('--initrd', nargs='?', help='Defines which ISO to run (skips build all together)', default=None, type=pathlib.Path)
args, unknowns = parser.parse_known_args() # pylint: disable=redefined-outer-name
cli_args, _ = parser.parse_known_args()
if args.bios and args.uki:
if cli_args.bios and cli_args.uki:
raise ArgumentError('Cannot boot a --uki image with --bios mode (at least not that I know of).')
if args.uki is None and args.kernel is None and args.iso is None and args.harddrive is None:
if cli_args.uki is None and cli_args.kernel is None and cli_args.iso is None and cli_args.harddrive is None:
raise ArgumentError('Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive')
if args.bridge is None and args.bridge_master:
if cli_args.bridge is None and cli_args.bridge_master:
raise ArgumentError('Cannot use --bridge-master without defining --bridge')
if args.bridge is None and args.bridge_mac:
if cli_args.bridge is None and cli_args.bridge_mac:
raise ArgumentError('Cannot use --bridge-mac without defining --bridge')
elif args.bridge and args.bridge_mac is None:
args.bridge_mac = '52:54:00:00:00:1'
elif cli_args.bridge and cli_args.bridge_mac is None:
cli_args.bridge_mac = '52:54:00:00:00:1'
if args.tap and not args.bridge and get_master(args.tap) is None:
if cli_args.tap and not cli_args.bridge and get_master(cli_args.tap) is None:
# We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists.
# Or the user has some creative iptables/nftables forwarding.
print(orange('--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8).'))
if args.tap is None and args.bridge:
if cli_args.tap is None and cli_args.bridge:
print(orange("--bridge* arguments will be ignored since there's no --tap defined"))
elif args.tap and args.tap_mac is None:
args.tap_mac = '52:54:00:00:00:2'
elif cli_args.tap and cli_args.tap_mac is None:
cli_args.tap_mac = '52:54:00:00:00:2'
class SysCallError(Exception):
@ -176,7 +185,7 @@ class SysCommandWorker:
return False
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]: # pylint: disable=redefined-outer-name
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
last_line = self._trace_log.rfind(b'\n')
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
for line in lines:
@ -334,81 +343,82 @@ class SysCommandWorker:
return self._trace_log.decode(encoding)
def ensure_sudo():
global sudo_password # pylint: disable=global-statement
@cache
def get_sudo_password() -> str:
sudo_password = getpass.getpass(f'[sudo] password for {username}: ')
if sudo_password == '':
raise ValueError('Certain commands need sudo to work and no sudo password was given.')
if sudo_password is None:
if (sudo_password := getpass.getpass(f'[sudo] password for {username}: ')) == '':
raise ValueError('Certain commands need sudo to work and no sudo password was given.')
return sudo_password
def setup_networking():
if args.tap:
if pathlib.Path(f'/sys/class/net/{args.tap}').exists() is False:
print(gray(f'Creating {args.tap} for user {username} and group {groupname}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip tuntap add dev {args.tap} mode tap user {username} group {groupname}'), False
def setup_networking() -> None:
if cli_args.tap:
if pathlib.Path(f'/sys/class/net/{cli_args.tap}').exists() is False:
print(gray(f'Creating {cli_args.tap} for user {username} and group {groupname}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip tuntap add dev {cli_args.tap} mode tap user {username} group {groupname}'), False
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
if args.bridge:
if pathlib.Path(f'/sys/class/net/{args.bridge}').exists() is False:
print(gray(f'Creating {args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {args.bridge} type bridge'), False
if cli_args.bridge:
if pathlib.Path(f'/sys/class/net/{cli_args.bridge}').exists() is False:
print(gray(f'Creating {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {cli_args.bridge} type bridge'), False
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
if args.bridge_mac:
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} address {args.bridge_mac}'), False
print(gray(f'Setting bridge {args.bridge} MAC address to {args.bridge_mac}'))
if cli_args.bridge_mac:
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge} address {cli_args.bridge_mac}'), False
print(gray(f'Setting bridge {cli_args.bridge} MAC address to {cli_args.bridge_mac}'))
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
if args.bridge_master and get_master(args.bridge) != args.bridge_master:
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge_master} master {args.bridge}'), False
print(gray(f'Setting interface {args.bridge_master} master to {args.bridge}'))
if cli_args.bridge_master and get_master(cli_args.bridge) != cli_args.bridge_master:
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge_master} master {cli_args.bridge}'), False
print(gray(f'Setting interface {cli_args.bridge_master} master to {cli_args}'))
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
print(gray(f'Setting interface {args.tap} master to {args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} master {args.bridge}'), False
print(gray(f'Setting interface {cli_args.tap} master to {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.tap} master {cli_args.bridge}'), False
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
print(gray(f'Bringing up bridge {args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} up'), False
print(gray(f'Bringing up bridge {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge} up'), False
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
print(gray(f'Bringing interface {args.tap} up'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} up'), False
print(gray(f'Bringing interface {cli_args.tap} up'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.tap} up'), False
while handle.is_alive():
if b'password for' in handle and pw_prompted is False:
ensure_sudo()
handle.write(bytes(sudo_password, 'UTF-8'))
sudo_pw = get_sudo_password()
handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True
def setup_disks():
if args.harddrive:
for harddrive_arg in args.harddrive:
def setup_disks() -> None:
if cli_args.harddrive:
for harddrive_arg in cli_args.harddrive:
path, size = harddrive_arg.split(':')
path = pathlib.Path(path.strip()).expanduser().resolve().absolute()
harddrives[path] = size.strip()
@ -425,7 +435,8 @@ def setup_disks():
setup_networking()
setup_disks()
if args.uki or args.bios is False:
disk_paths_hash = ''
if cli_args.uki or cli_args.bios is False:
disk_paths_hash = hashlib.sha1((''.join(sorted([str(x) for x in harddrives.keys()]))).encode()).hexdigest()
shutil.copy2('/usr/share/ovmf/x64/OVMF_CODE.secboot.4m.fd', f'./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}')
@ -439,16 +450,16 @@ qemu += ' -machine q35,accel=kvm'
qemu += ' -object rng-random,filename=/dev/urandom,id=rng0'
qemu += ' -device virtio-rng-pci,rng=rng0'
qemu += ' -global driver=cfi.pflash01,property=secure,value=on'
qemu += f' -smp {args.cpu},sockets=1,dies=1,cores={args.cpu},threads=1'
qemu += f' -smp {cli_args.cpu},sockets=1,dies=1,cores={cli_args.cpu},threads=1'
# qemu += f' -vga vga'
qemu += f' -device VGA,edid=on,xres={args.resolution.split("x")[0]},yres={args.resolution.split("x")[1]}'
qemu += f' -device VGA,edid=on,xres={cli_args.resolution.split("x")[0]},yres={cli_args.resolution.split("x")[1]}'
qemu += ' -device intel-iommu,device-iotlb=on,caching-mode=on'
qemu += f' -m {args.memory}'
if args.bios is False:
qemu += f' -m {cli_args.memory}'
if cli_args.bios is False:
qemu += f' -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}'
qemu += f' -drive if=pflash,format=raw,file=./OVMF_VARS.4m.fd.{disk_paths_hash}'
if args.uki:
qemu += f' -kernel {args.uki}'
if cli_args.uki:
qemu += f' -kernel {cli_args.uki}'
boot_index += 1
scsi_index = 0
for scsi_index, hdd in enumerate(harddrives.keys()):
@ -460,18 +471,18 @@ for scsi_index, hdd in enumerate(harddrives.keys()):
qemu += f' -blockdev \'{{"driver":"file","filename":"{hdd}","aio":"threads","node-name":"libvirt-{scsi_index}-storage","cache":{{"direct":false,"no-flush":false}},"auto-read-only":true,"discard":"unmap"}}\'' # noqa: E501
qemu += f' -blockdev \'{{"node-name":"libvirt-{scsi_index}-format","read-only":false,"discard":"unmap","cache":{{"direct":true,"no-flush":false}},"driver":"qcow2","file":"libvirt-{scsi_index}-storage","backing":null}}\'' # noqa: E501
boot_index += 1
if args.iso:
if cli_args.iso:
qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{scsi_index + 1}'
qemu += f' -device scsi-cd,drive=cdrom0,bus=scsi{scsi_index + 1}.0,bootindex={boot_index}'
qemu += f' -drive file={args.iso},media=cdrom,if=none,format=raw,cache=none,id=cdrom0'
qemu += f' -drive file={cli_args.iso},media=cdrom,if=none,format=raw,cache=none,id=cdrom0'
boot_index += 1
# if args.vfio:
# qemu += f' -drive file={args.vfio},index=2,media=cdrom'
# if cli_args.vfio:
# qemu += f' -drive file={cli_args.vfio},index=2,media=cdrom'
if args.tap:
qemu += f' -device virtio-net-pci,mac={args.tap_mac},id=network0,netdev=network0.0,status=on,bus=pcie.0'
qemu += f' -netdev tap,ifname={args.tap},id=network0.0,script=no,downscript=no'
if cli_args.tap:
qemu += f' -device virtio-net-pci,mac={cli_args.tap_mac},id=network0,netdev=network0.0,status=on,bus=pcie.0'
qemu += f' -netdev tap,ifname={cli_args.tap},id=network0.0,script=no,downscript=no'
print(gray(qemu))

View File

@ -50,6 +50,7 @@ def test_default_args(monkeypatch: MonkeyPatch) -> None:
offline=False,
no_pkg_lookups=False,
plugin=None,
plugin_url=None,
skip_version_check=False,
advanced=False,
)
@ -106,7 +107,8 @@ def test_correct_parsing_args(
debug=True,
offline=True,
no_pkg_lookups=True,
plugin='pytest_plugin.py',
plugin=Path('pytest_plugin.py'),
plugin_url=None,
skip_version_check=True,
advanced=True,
)

View File

@ -3,8 +3,7 @@ from pathlib import Path
from pytest import MonkeyPatch
from archinstall.lib.args import ArchConfigHandler
from archinstall.lib.configuration import ConfigurationOutput
from archinstall.lib.args import USER_CONFIG_FILE, USER_CREDS_FILE, ArchConfigHandler
def test_user_config_roundtrip(
@ -20,12 +19,10 @@ def test_user_config_roundtrip(
# as there is no version present in the test environment we'll set it manually
arch_config.version = '3.0.2'
config_output = ConfigurationOutput(arch_config)
test_out_dir = Path('/tmp/')
test_out_file = test_out_dir / config_output.user_configuration_file
test_out_file = test_out_dir / USER_CONFIG_FILE
config_output.save(test_out_dir)
arch_config.save(test_out_dir)
result = json.loads(test_out_file.read_text())
expected = json.loads(config_fixture.read_text())
@ -55,12 +52,10 @@ def test_creds_roundtrip(
handler = ArchConfigHandler()
arch_config = handler.config
config_output = ConfigurationOutput(arch_config)
test_out_dir = Path('/tmp/')
test_out_file = test_out_dir / config_output.user_credentials_file
test_out_file = test_out_dir / USER_CREDS_FILE
config_output.save(test_out_dir, creds=True)
arch_config.save(test_out_dir, creds=True)
result = json.loads(test_out_file.read_text())
expected = json.loads(creds_fixture.read_text())