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: container:
image: archlinux/archlinux:latest image: archlinux/archlinux:latest
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- run: pacman --noconfirm -Syu bandit - run: pacman --noconfirm -Syu bandit
- name: Security checkup with Bandit - name: Security checkup with Bandit
run: bandit -r archinstall || exit 0 run: bandit -r archinstall || exit 0

View File

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

View File

@ -21,8 +21,8 @@ jobs:
image: archlinux/archlinux:latest image: archlinux/archlinux:latest
options: --privileged options: --privileged
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
- name: Install pre-dependencies - name: Install pre-dependencies
run: | run: |
pacman -Sy --noconfirm tree git python-pyparted python-setuptools python-sphinx python-sphinx_rtd_theme python-build python-installer python-wheel 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 image: archlinux/archlinux:latest
options: --privileged options: --privileged
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- run: pwd - run: pwd
- run: find . - run: find .
- run: cat /etc/os-release - run: cat /etc/os-release

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
from enum import Enum from enum import Enum
from archinstall.lib.installer import Installer
from archinstall.lib.menu.helpers import Selection from archinstall.lib.menu.helpers import Selection
from archinstall.lib.models.users import User
from archinstall.lib.translationhandler import tr from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType from archinstall.tui.result import ResultType
@ -11,6 +13,16 @@ class SeatAccess(Enum):
polkit = 'polkit' 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: async def select_seat_access(profile_name: str, default: str | None) -> SeatAccess:
header = tr('{} needs access to your seat').format(profile_name) header = tr('{} needs access to your seat').format(profile_name)
header += f' ({tr("collection of hardware devices i.e. keyboard, mouse")})' + '\n' header += f' ({tr("collection of hardware devices i.e. keyboard, mouse")})' + '\n'

View File

@ -1,7 +1,9 @@
import argparse import argparse
import json import json
import os import os
import stat
import sys import sys
import tempfile
import urllib.error import urllib.error
import urllib.parse import urllib.parse
from argparse import ArgumentParser, Namespace from argparse import ArgumentParser, Namespace
@ -11,9 +13,10 @@ from pathlib import Path
from typing import Any, Self from typing import Any, Self
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from pydantic import TypeAdapter
from pydantic.dataclasses import dataclass as p_dataclass 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.log import debug, error, logger, warn
from archinstall.lib.menu.util import get_password from archinstall.lib.menu.util import get_password
from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration 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.models.users import Password, User, UserSerialization
from archinstall.lib.plugins import load_plugin from archinstall.lib.plugins import load_plugin
from archinstall.lib.translationhandler import Language, tr, translation_handler 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.lib.version import get_version
from archinstall.tui.components import tui from archinstall.tui.components import tui
@ -56,7 +61,8 @@ class Arguments:
debug: bool = False debug: bool = False
offline: bool = False offline: bool = False
no_pkg_lookups: 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_version_check: bool = False
skip_wifi_check: bool = False skip_wifi_check: bool = False
advanced: bool = False advanced: bool = False
@ -140,6 +146,11 @@ class ArchConfigType(StrEnum):
return tr('Disk encryption password') 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 @dataclass
class ArchConfig: class ArchConfig:
version: str | None = None version: str | None = None
@ -367,6 +378,94 @@ class ArchConfig:
return arch_config 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: class ArchConfigHandler:
def __init__(self) -> None: def __init__(self) -> None:
@ -517,10 +616,17 @@ class ArchConfigHandler:
parser.add_argument( parser.add_argument(
'--plugin', '--plugin',
nargs='?', nargs='?',
type=str, type=Path,
default=None, default=None,
help='File path to a plugin to load', 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( parser.add_argument(
'--skip-version-check', '--skip-version-check',
action='store_true', action='store_true',
@ -560,7 +666,11 @@ class ArchConfigHandler:
warn(f'Warning: --debug mode will write certain credentials to {logger.path}!') warn(f'Warning: --debug mode will write certain credentials to {logger.path}!')
if args.plugin: 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) load_plugin(plugin_path)
if args.creds_decryption_key is None: if args.creds_decryption_key is None:
@ -652,6 +762,27 @@ class ArchConfigHandler:
sys.exit(1) 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: def _read_file(self, path: Path) -> str:
if not path.exists(): if not path.exists():
error(f'Could not find file {path}') 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.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Confirmation, Selection 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.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType from archinstall.tui.result import ResultType
@ -66,6 +66,13 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
key='removable', key='removable',
enabled=removable_enabled, 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: 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 /EFI/BOOT/ (removable location, safe default)')
return tr('Will install to custom location with NVRAM entry') 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 @override
async def show(self) -> BootloaderConfiguration: async def show(self) -> BootloaderConfiguration:
_ = await super().show() _ = await super().show()
@ -117,6 +129,37 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
return bootloader 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: async def _select_uki(self, preset: bool) -> bool:
prompt = tr('Would you like to use unified kernel images?') + '\n' prompt = tr('Would you like to use unified kernel images?') + '\n'
@ -215,3 +258,24 @@ async def select_bootloader(
return result.get_value() return result.get_value()
case ResultType.Reset: case ResultType.Reset:
raise ValueError('Unhandled result type') 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 readline
import stat
from pathlib import Path
from typing import Any
from pydantic import TypeAdapter from archinstall.lib.args import USER_CONFIG_FILE, USER_CREDS_FILE, ArchConfig
from archinstall.lib.log import debug
from archinstall.lib.args import ArchConfig, ArchConfigType
from archinstall.lib.crypt import encrypt
from archinstall.lib.log import debug, logger, warn
from archinstall.lib.menu.helpers import Confirmation, Selection from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.menu.util import get_password, prompt_dir from archinstall.lib.menu.util import get_password, prompt_dir
from archinstall.lib.translationhandler import tr 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.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType from archinstall.tui.result import ResultType
class ConfigurationOutput: async def confirm_config(config: ArchConfig) -> bool:
def __init__(self, config: ArchConfig): header = f'{tr("The specified configuration will be applied")}. '
""" header += tr('Would you like to continue?') + '\n'
Configuration output handler to parse the existing
configuration data structure and prepare for output on the
console and for saving it to configuration files
:param config: Archinstall configuration object group = MenuItemGroup.yes_no()
:type config: ArchConfig group.set_preview_for_all(lambda x: config.user_config_to_json())
"""
self._config = config result = await Confirmation(
self._default_save_path = logger.directory group=group,
self._user_config_file = Path('user_configuration.json') header=header,
self._user_creds_file = Path('user_credentials.json') allow_skip=False,
preset=True,
preview_location='bottom',
preview_header=tr('Configuration preview'),
).show()
@property if not result.get_value():
def user_configuration_file(self) -> Path: return False
return self._user_config_file
@property return True
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)
async def save_config(config: ArchConfig) -> None: async def save_config(config: ArchConfig) -> None:
def preview(item: MenuItem) -> str | None: def preview(item: MenuItem) -> str | None:
match item.value: match item.value:
case 'user_config': case 'user_config':
serialized = config_output.user_config_to_json() serialized = config.user_config_to_json()
return f'{config_output.user_configuration_file}\n{serialized}' return f'{USER_CONFIG_FILE}\n{serialized}'
case 'user_creds': case 'user_creds':
if maybe_serial := config_output.user_credentials_to_json(): if maybe_serial := config.user_credentials_to_json():
return f'{config_output.user_credentials_file}\n{maybe_serial}' return f'{USER_CREDS_FILE}\n{maybe_serial}'
return tr('No configuration') return tr('No configuration')
case 'all': case 'all':
output = [str(config_output.user_configuration_file)] output = [str(USER_CONFIG_FILE)]
config_output.user_credentials_to_json() config.user_credentials_to_json()
output.append(str(config_output.user_credentials_file)) output.append(str(USER_CREDS_FILE))
return '\n'.join(output) return '\n'.join(output)
return None return None
config_output = ConfigurationOutput(config)
items = [ items = [
MenuItem( MenuItem(
tr('Save user configuration (including disk layout)'), tr('Save user configuration (including disk layout)'),
@ -248,8 +130,8 @@ async def save_config(config: ArchConfig) -> None:
match save_option: match save_option:
case 'user_config': case 'user_config':
config_output.save_user_config(dest_path) config.save_user_config(dest_path)
case 'user_creds': case 'user_creds':
config_output.save_user_creds(dest_path, password=enc_password) config.save_user_creds(dest_path, password=enc_password)
case 'all': 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 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 = [ items = [
MenuItem( MenuItem(
str(d.device_info.path), 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.authentication.authentication_menu import AuthenticationMenu
from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu
from archinstall.lib.bootloader.utils import validate_bootloader_layout 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.disk.disk_menu import DiskLayoutConfigurationMenu
from archinstall.lib.exceptions import SysCallError from archinstall.lib.exceptions import SysCallError
from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone 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: def _prev_install_invalid_config(self, item: MenuItem) -> PreviewResult | None:
self.sync_all_to_config() self.sync_all_to_config()
config_output = ConfigurationOutput(self._arch_config)
warnings = self._get_install_warnings() warnings = self._get_install_warnings()
messages: list[tuple[str, MsgLevelType]] = [] messages: list[tuple[str, MsgLevelType]] = []
@ -608,7 +607,7 @@ class GlobalMenu(AbstractMenu[None]):
messages.append((text, MsgLevelType.MsgWarning)) messages.append((text, MsgLevelType.MsgWarning))
if not errors: if not errors:
summary = config_output.as_summary() summary = self._arch_config.as_summary()
if summary: if summary:
messages.append((summary, MsgLevelType.MsgNone)) 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.log import debug, error, info, log, logger, warn
from archinstall.lib.mirror.mirror_handler import MirrorListHandler from archinstall.lib.mirror.mirror_handler import MirrorListHandler
from archinstall.lib.models.application import ZramAlgorithm 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 ( from archinstall.lib.models.device import (
DiskEncryption, DiskEncryption,
DiskLayoutConfiguration, DiskLayoutConfiguration,
@ -228,6 +228,9 @@ class Installer:
while self._service_state('archlinux-keyring-wkd-sync.service') not in ('dead', 'failed', 'exited'): while self._service_state('archlinux-keyring-wkd-sync.service') not in ('dead', 'failed', 'exited'):
time.sleep(1) 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: def _verify_boot_part(self) -> None:
""" """
Check that mounted /boot device has at minimum size for installation Check that mounted /boot device has at minimum size for installation
@ -1755,6 +1758,28 @@ class Installer:
self._helper_flags['bootloader'] = 'refind' 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( def _config_uki(
self, self,
root: PartitionModification | LvmVolume, root: PartitionModification | LvmVolume,
@ -1807,10 +1832,7 @@ class Installer:
error('Error generating initramfs (continuing anyway)') error('Error generating initramfs (continuing anyway)')
def add_bootloader( def add_bootloader(
self, self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False, plymouth: PlymouthTheme | None = None
bootloader: Bootloader,
uki_enabled: bool = False,
bootloader_removable: bool = False,
) -> None: ) -> None:
""" """
Adds a bootloader to the installation instance. Adds a bootloader to the installation instance.
@ -1824,6 +1846,7 @@ class Installer:
:param bootloader: Type of bootloader to be added :param bootloader: Type of bootloader to be added
:param uki_enabled: Whether to use unified kernel images :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 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(): for plugin in plugins.values():
@ -1859,6 +1882,9 @@ class Installer:
warn(f'Bootloader {bootloader.value} lacks removable support; disabling.') warn(f'Bootloader {bootloader.value} lacks removable support; disabling.')
bootloader_removable = False bootloader_removable = False
if plymouth is not None:
self._install_plymouth(plymouth)
if uki_enabled: if uki_enabled:
keep_initramfs = ( keep_initramfs = (
bootloader == Bootloader.Grub bootloader == Bootloader.Grub

View File

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

View File

@ -60,15 +60,48 @@ class Bootloader(Enum):
return cls(bootloader) 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 @dataclass
class BootloaderConfiguration(SubConfig): class BootloaderConfiguration(SubConfig):
bootloader: Bootloader bootloader: Bootloader
uki: bool = False uki: bool = False
removable: bool = True removable: bool = True
plymouth: PlymouthTheme | None = None
@override @override
def json(self) -> dict[str, Any]: 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 @override
def summary(self) -> list[str]: def summary(self) -> list[str]:
@ -78,6 +111,8 @@ class BootloaderConfiguration(SubConfig):
out.append(tr('UKI enabled')) out.append(tr('UKI enabled'))
if self.removable: if self.removable:
out.append(tr('Removable')) out.append(tr('Removable'))
if self.plymouth is not None:
out.append(tr('Plymouth "{}"').format(self.plymouth.value))
return out return out
@ -86,14 +121,16 @@ class BootloaderConfiguration(SubConfig):
bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot) bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot)
uki = config.get('uki', False) uki = config.get('uki', False)
removable = config.get('removable', True) 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 @classmethod
def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self: def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self:
bootloader = Bootloader.get_default(uefi, skip_boot) bootloader = Bootloader.get_default(uefi, skip_boot)
removable = uefi and bootloader.has_removable_support() removable = uefi and bootloader.has_removable_support()
uki = uefi and bootloader.has_uki_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: def preview(self, uefi: bool) -> str:
text = f'{tr("Bootloader")}: {self.bootloader.value}' text = f'{tr("Bootloader")}: {self.bootloader.value}'
@ -112,4 +149,7 @@ class BootloaderConfiguration(SubConfig):
removable_string = tr('Disabled') removable_string = tr('Disabled')
text += f'{tr("Removable")}: {removable_string}' text += f'{tr("Removable")}: {removable_string}'
text += '\n' text += '\n'
if self.plymouth is not None:
text += f'{tr("Plymouth")}: {self.plymouth.value}'
text += '\n'
return text return text

View File

@ -4,8 +4,8 @@ from collections.abc import Callable
from pathlib import Path from pathlib import Path
from archinstall.lib.command import SysCommand from archinstall.lib.command import SysCommand
from archinstall.lib.exceptions import RequirementError from archinstall.lib.exceptions import RequirementError, SysCallError
from archinstall.lib.log import error, info, warn from archinstall.lib.log import debug, error, info, warn
from archinstall.lib.pathnames import PACMAN_CONF from archinstall.lib.pathnames import PACMAN_CONF
from archinstall.lib.plugins import plugins from archinstall.lib.plugins import plugins
from archinstall.lib.translationhandler import tr from archinstall.lib.translationhandler import tr
@ -53,15 +53,45 @@ class Pacman:
def sync(self) -> None: def sync(self) -> None:
if self.synced: if self.synced:
return return
self.ask(
'Could not sync a new package database', try:
'Could not sync mirrors', self.run('-Syy')
self.run, except SysCallError as err:
'-Syy', if b'GPGME' in err.worker_log or b'keyring' in err.worker_log.lower():
default_cmd='pacman', 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 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: def strap(self, packages: str | list[str]) -> None:
self.sync() self.sync()
if isinstance(packages, str): if isinstance(packages, str):

View File

@ -1,9 +1,6 @@
import hashlib
import importlib.util import importlib.util
import os import os
import sys import sys
import urllib.parse
import urllib.request
from importlib import metadata from importlib import metadata
from pathlib import Path from pathlib import Path
@ -34,23 +31,6 @@ def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
plugins[f.__name__] = f 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: def _import_via_path(path: Path, namespace: str | None = None) -> str:
if not namespace: if not namespace:
namespace = os.path.basename(path) 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: def load_plugin(path: Path) -> None:
namespace: str | None = None namespace: str | None = None
parsed_url = urllib.parse.urlparse(str(path)) info(f'Loading plugin from {path}')
info(f'Loading plugin from url {parsed_url}')
# The Profile was not a direct match on a remote URL if os.path.isfile(path):
if not parsed_url.scheme: namespace = _import_via_path(path)
# 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 namespace and namespace in sys.modules: if namespace and namespace in sys.modules:
# Version dependency via __archinstall__version__ variable (if present) in the plugin # Version dependency via __archinstall__version__ variable (if present) in the plugin

View File

@ -1,6 +1,7 @@
import secrets import secrets
import string import string
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path
from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT
from archinstall.lib.utils.format import as_columns 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 # remove whitespaces on each row
text = '\n'.join(t.strip() for t in text.split('\n')) text = '\n'.join(t.strip() for t in text.split('\n'))
return text 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" msgid "Install to removable location"
msgstr "" msgstr ""
msgid "Plymouth"
msgstr ""
msgid "Will install to /EFI/BOOT/ (removable location, safe default)" msgid "Will install to /EFI/BOOT/ (removable location, safe default)"
msgstr "" msgstr ""
msgid "Will install to custom location with NVRAM entry" msgid "Will install to custom location with NVRAM entry"
msgstr "" 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?" msgid "Would you like to use unified kernel images?"
msgstr "" msgstr ""
@ -248,6 +265,9 @@ msgstr ""
msgid "UEFI is not detected and some options are disabled" msgid "UEFI is not detected and some options are disabled"
msgstr "" msgstr ""
msgid "Select Plymouth theme"
msgstr ""
msgid "The specified configuration will be applied" msgid "The specified configuration will be applied"
msgstr "" msgstr ""
@ -888,6 +908,10 @@ msgstr ""
msgid "Removable" msgid "Removable"
msgstr "" msgstr ""
#, python-brace-format
msgid "Plymouth \"{}\""
msgstr ""
msgid "Use a best-effort default partition layout" msgid "Use a best-effort default partition layout"
msgstr "" msgstr ""

View File

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

View File

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

View File

@ -116,7 +116,7 @@
{"abbr": "ne", "lang": "Nepali", "translated_lang": "नेपाली", "sys_lang": "ne_NP.UTF-8", "default_timezone": "Asia/Kathmandu"}, {"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": "nl", "lang": "Dutch", "translated_lang": "Nederlands", "sys_lang": "nl_NL.UTF-8", "default_timezone": "Europe/Amsterdam"},
{"abbr": "nn", "lang": "Norwegian Nynorsk"}, {"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": "no", "lang": "Norwegian"},
{"abbr": "ny", "lang": "Nyanja"}, {"abbr": "ny", "lang": "Nyanja"},
{"abbr": "oc", "lang": "Occitan (post 1500)"}, {"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": "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": "uz", "lang": "Uzbek", "translated_lang": "O'zbek", "sys_lang": "uz_UZ.UTF-8", "default_timezone": "Asia/Tashkent"},
{"abbr": "ve", "lang": "Venda"}, {"abbr": "ve", "lang": "Venda"},
{"abbr": "vi", "lang": "Vietnamese"}, {"abbr": "vi", "lang": "Vietnamese", "translated_lang": "Tiếng Việt"},
{"abbr": "vo", "lang": "Volapük"}, {"abbr": "vo", "lang": "Volapük"},
{"abbr": "wa", "lang": "Walloon"}, {"abbr": "wa", "lang": "Walloon"},
{"abbr": "wo", "lang": "Wolof"}, {"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." msgid "No network configuration selected. Network will need to be set up manually on the installed system."
msgstr "Конфігурацію мережі не обрано. Мережу доведеться налаштувати вручну на встановленій системі." 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.args import ArchConfig, ArchConfigHandler
from archinstall.lib.authentication.authentication_handler import AuthenticationHandler from archinstall.lib.authentication.authentication_handler import AuthenticationHandler
from archinstall.lib.bootloader.utils import validate_bootloader_layout 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.filesystem import FilesystemHandler
from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.disk.utils import disk_layouts
from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation 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.bootloader,
config.bootloader_config.uki, config.bootloader_config.uki,
config.bootloader_config.removable, config.bootloader_config.removable,
config.bootloader_config.plymouth,
) )
if config.network_config: if config.network_config:
@ -212,9 +213,8 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
if not arch_config_handler.args.silent: if not arch_config_handler.args.silent:
show_menu(arch_config_handler, mirror_list_handler) show_menu(arch_config_handler, mirror_list_handler)
config = ConfigurationOutput(arch_config_handler.config) arch_config_handler.config.write_debug()
config.write_debug() arch_config_handler.config.save()
config.save()
# Safety net for silent/config-file flow. The TUI menu blocks Install via # Safety net for silent/config-file flow. The TUI menu blocks Install via
# GlobalMenu._validate_bootloader() before reaching this point. # 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: if not arch_config_handler.args.silent:
aborted = False aborted = False
res: bool = tui.run(config.confirm_config) res: bool = tui.run(lambda: confirm_config(arch_config_handler.config))
if not res: if not res:
debug('Installation aborted') debug('Installation aborted')

View File

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

View File

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

View File

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

View File

@ -11,6 +11,8 @@ import sys
import time import time
from argparse import ArgumentParser from argparse import ArgumentParser
from collections.abc import Iterator from collections.abc import Iterator
from functools import cache
from pathlib import Path
from select import EPOLLHUP, EPOLLIN, epoll from select import EPOLLHUP, EPOLLIN, epoll
from shutil import which from shutil import which
from types import TracebackType from types import TracebackType
@ -25,25 +27,32 @@ class ArgumentError(Exception):
pass 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') master_path = pathlib.Path(f'/sys/class/net/{interface}/master')
return master_path.readlink().name if master_path.exists() else None 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' 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' return f'\033[38;5;208m{text}\033[0m'
def red(text): def red(text: str) -> str:
return f'\033[31m{text}\033[0m' return f'\033[31m{text}\033[0m'
sudo_password = None # Gets populated later harddrives: dict[Path, str] = {}
harddrives = {}
username = getpass.getuser() username = getpass.getuser()
groupname = grp.getgrgid(os.getgid()).gr_name 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('--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('--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('--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') hardware.add_argument('--resolution', help="Sets Qemu's VGA resolution", type=str, default='1920x1107')
kernel = parser.add_argument_group('Kernel', '--kernel specific arguments') 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) 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).') 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') 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') 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') raise ArgumentError('Cannot use --bridge-mac without defining --bridge')
elif args.bridge and args.bridge_mac is None: elif cli_args.bridge and cli_args.bridge_mac is None:
args.bridge_mac = '52:54:00:00:00:1' 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. # 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. # 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).')) 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")) print(orange("--bridge* arguments will be ignored since there's no --tap defined"))
elif args.tap and args.tap_mac is None: elif cli_args.tap and cli_args.tap_mac is None:
args.tap_mac = '52:54:00:00:00:2' cli_args.tap_mac = '52:54:00:00:00:2'
class SysCallError(Exception): class SysCallError(Exception):
@ -176,7 +185,7 @@ class SysCommandWorker:
return False 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') last_line = self._trace_log.rfind(b'\n')
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines()) lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
for line in lines: for line in lines:
@ -334,81 +343,82 @@ class SysCommandWorker:
return self._trace_log.decode(encoding) return self._trace_log.decode(encoding)
def ensure_sudo(): @cache
global sudo_password # pylint: disable=global-statement 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: return sudo_password
if (sudo_password := getpass.getpass(f'[sudo] password for {username}: ')) == '':
raise ValueError('Certain commands need sudo to work and no sudo password was given.')
def setup_networking(): def setup_networking() -> None:
if args.tap: if cli_args.tap:
if pathlib.Path(f'/sys/class/net/{args.tap}').exists() is False: if pathlib.Path(f'/sys/class/net/{cli_args.tap}').exists() is False:
print(gray(f'Creating {args.tap} for user {username} and group {groupname}')) print(gray(f'Creating {cli_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 handle, pw_prompted = SysCommandWorker(f'sudo ip tuntap add dev {cli_args.tap} mode tap user {username} group {groupname}'), False
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
if args.bridge: if cli_args.bridge:
if pathlib.Path(f'/sys/class/net/{args.bridge}').exists() is False: if pathlib.Path(f'/sys/class/net/{cli_args.bridge}').exists() is False:
print(gray(f'Creating {args.bridge}')) print(gray(f'Creating {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {args.bridge} type bridge'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {cli_args.bridge} type bridge'), False
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
if args.bridge_mac: if cli_args.bridge_mac:
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} address {args.bridge_mac}'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge} address {cli_args.bridge_mac}'), False
print(gray(f'Setting bridge {args.bridge} MAC address to {args.bridge_mac}')) print(gray(f'Setting bridge {cli_args.bridge} MAC address to {cli_args.bridge_mac}'))
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
if args.bridge_master and get_master(args.bridge) != args.bridge_master: 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 {args.bridge_master} master {args.bridge}'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge_master} master {cli_args.bridge}'), False
print(gray(f'Setting interface {args.bridge_master} master to {args.bridge}')) print(gray(f'Setting interface {cli_args.bridge_master} master to {cli_args}'))
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
print(gray(f'Setting interface {args.tap} master to {args.bridge}')) print(gray(f'Setting interface {cli_args.tap} master to {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} master {args.bridge}'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.tap} master {cli_args.bridge}'), False
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
print(gray(f'Bringing up bridge {args.bridge}')) print(gray(f'Bringing up bridge {cli_args.bridge}'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} up'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.bridge} up'), False
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
print(gray(f'Bringing interface {args.tap} up')) print(gray(f'Bringing interface {cli_args.tap} up'))
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} up'), False handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {cli_args.tap} up'), False
while handle.is_alive(): while handle.is_alive():
if b'password for' in handle and pw_prompted is False: if b'password for' in handle and pw_prompted is False:
ensure_sudo() sudo_pw = get_sudo_password()
handle.write(bytes(sudo_password, 'UTF-8')) handle.write(bytes(sudo_pw, 'UTF-8'))
pw_prompted = True pw_prompted = True
def setup_disks(): def setup_disks() -> None:
if args.harddrive: if cli_args.harddrive:
for harddrive_arg in args.harddrive: for harddrive_arg in cli_args.harddrive:
path, size = harddrive_arg.split(':') path, size = harddrive_arg.split(':')
path = pathlib.Path(path.strip()).expanduser().resolve().absolute() path = pathlib.Path(path.strip()).expanduser().resolve().absolute()
harddrives[path] = size.strip() harddrives[path] = size.strip()
@ -425,7 +435,8 @@ def setup_disks():
setup_networking() setup_networking()
setup_disks() 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() 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}') 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 += ' -object rng-random,filename=/dev/urandom,id=rng0'
qemu += ' -device virtio-rng-pci,rng=rng0' qemu += ' -device virtio-rng-pci,rng=rng0'
qemu += ' -global driver=cfi.pflash01,property=secure,value=on' 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' -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 += ' -device intel-iommu,device-iotlb=on,caching-mode=on'
qemu += f' -m {args.memory}' qemu += f' -m {cli_args.memory}'
if args.bios is False: 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,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}' qemu += f' -drive if=pflash,format=raw,file=./OVMF_VARS.4m.fd.{disk_paths_hash}'
if args.uki: if cli_args.uki:
qemu += f' -kernel {args.uki}' qemu += f' -kernel {cli_args.uki}'
boot_index += 1 boot_index += 1
scsi_index = 0 scsi_index = 0
for scsi_index, hdd in enumerate(harddrives.keys()): 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 \'{{"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 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 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 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' -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 boot_index += 1
# if args.vfio: # if cli_args.vfio:
# qemu += f' -drive file={args.vfio},index=2,media=cdrom' # qemu += f' -drive file={cli_args.vfio},index=2,media=cdrom'
if args.tap: if cli_args.tap:
qemu += f' -device virtio-net-pci,mac={args.tap_mac},id=network0,netdev=network0.0,status=on,bus=pcie.0' 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={args.tap},id=network0.0,script=no,downscript=no' qemu += f' -netdev tap,ifname={cli_args.tap},id=network0.0,script=no,downscript=no'
print(gray(qemu)) print(gray(qemu))

View File

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

View File

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