Merge master into lang-for-target-locale
Resolve the binary base.mo conflict
This commit is contained in:
commit
13a7367ee0
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
2
PKGBUILD
2
PKGBUILD
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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 ""
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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 "サブボリュームのオプションを選択"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Binary file not shown.
|
|
@ -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
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Reference in New Issue