Compare commits

..

1100 Commits

Author SHA1 Message Date
renovate[bot] b96afcc579
Update dependency arch/python-textual to v8.2.7 (#4556)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-25 21:58:49 +10:00
BB 3bc248b47f
add niri DankMaterialShell profile (#4554) 2026-05-25 21:13:56 +10:00
Anton Hvornum af2120c0e9
mkosi for test image builds (#4539)
Added a `mkosi` profile as well as organized test tooling a bit.

---------

Co-authored-by: 0xdeadd <clintdotphillips@gmail.com>
2026-05-23 13:49:54 +02:00
renovate[bot] d92a98d3bf
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.14 (#4552)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-22 12:39:28 +10:00
renovate[bot] af58671e88
Update dependency ruff to v0.15.14 (#4551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-22 12:39:11 +10:00
utuhiro78 e17e036f54
Update Japanese translation (#4550) 2026-05-21 07:25:23 +10:00
Daniel Girtler ac4bc442de
Refactor logging and formatting logic (#4542)
* Refactor logging and formatting

* Refactor logging and formatting

* Refactor logging and formatting

* Updaet
2026-05-19 17:48:03 +02:00
Clinton Phillips 5fd2f9b627
fix: restrict EFI partition permissions with fmask/dmask=0077 (#4506)
* fix: restrict EFI partition permissions with fmask/dmask=0077

Mount the ESP with fmask=0077 and dmask=0077 to prevent world-readable
files like /efi/loader/random-seed.

Closes #4241

* fix(efi): collapse fmask/dmask dedup to dict.fromkeys one-liner

Per @Torxed's review feedback. Same semantics as the previous loop
(dedupe by exact-string match) but shorter. dict.fromkeys preserves
insertion order, where set() would not.

* fix(efi): drop defensive list wrap per review

The list() copy on line 378 was load-bearing only if options were
mutated downstream, but the EFI branch reassigns options via
dict.fromkeys() (line 381) and the non-EFI branch passes through
to mount() without mutating. Drop the copy.
2026-05-19 13:36:28 +10:00
Softer dccd0c8ccf
Add translation CI validation (#4519)
* Fix broken localization: tr(f-string) never matches translation catalog

tr(f'Invalid configuration: {error}') evaluates the f-string before
tr() runs, so xgettext extracts the literal placeholder as the msgid
while runtime passes the formatted string - the two never match.
Switch to tr('...{}').format(...) and update msgid in base.pot.

* Add CI validation for translations and pot_tools dev utility

Add translation-check workflow with two jobs:
- validate-po: msgfmt --check on changed .po files, .mo sync warning,
  tr(f-string) anti-pattern grep on changed .py files
- validate-pot: verify all tr() strings exist in base.pot when .py
  files change

Workflow only triggers on .py/.po/.pot file changes.

Add scripts/pot_tools.py developer utility (stats, list, add_missing)
for managing base.pot.

* Fix code style: use tabs and reformat xgettext arguments

Align check_pot_freshness.py and pot_tools.py with project
indentation (tabs) and ruff format requirements.

Sorry :-)

* Replace custom PO parser with msgcmp, drop pot_tools.py

Address review feedback: use standard gettext msgcmp instead of
hand-rolled parser for base.pot freshness check. Remove pot_tools.py
that duplicated locales_generator.sh functionality.

* Move translation checks into locales_generator.sh, simplify CI workflow

Use msgcmp instead of diff for base.pot validation to avoid failing on
legacy stale entries - the same cascading breakage that killed the
original workflow (disabled 2023, removed in #4483).

* Fix broken .po files: duplicate msgid in Hindi, missing format args in Finnish
2026-05-18 20:10:56 +10:00
renovate[bot] 22bf6e3c35
Update dependency arch/python-textual to v8.2.6 (#4546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-18 15:36:33 +10:00
Matteo b5d323762b
Update Italian translation + fix a string (#4545)
* Update Italian translation

* Added new strings
* Improved existing translations

* Fix wrong string + Italian translation

* Mispelled "respository" -> "repository"
2026-05-17 11:26:30 +10:00
utuhiro78 42d9113611
Update Japanese translation (#4544) 2026-05-16 09:29:27 +10:00
Daniel Girtler 516a61d8af
Enhance log sharing capability (#4526) 2026-05-15 21:38:17 +10:00
renovate[bot] e48ca45b0b
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.13 (#4541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-15 16:30:33 +10:00
renovate[bot] 74a1066661
Update dependency ruff to v0.15.13 (#4540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-15 13:58:00 +10:00
HADEON b81fe955f0
Add IWD standalone option to network configuration + fix NM_IWD (#4528)
* Add iwd standalone option to network configuration

Adds NicType.IWD as a third option alongside NM and NM_IWD: install
iwd, write /etc/iwd/main.conf with EnableNetworkConfiguration=true and
NameResolvingService=systemd, enable iwd.service + systemd-resolved.service.
iwd handles DHCP itself and resolved picks up its DNS via the symlink, so
no NetworkManager pulled in.

Also fills in parse_arg cases for NM_IWD and IWD so config files
round-trip both nic types.

Assisted-By: Flint

* wire up resolv stub and networkd service

* exclude virtual devices, dont harcode iface name instead match 'ether'
similar pattern to .network files shipped in /etc/systemd/network

* use dedent and rename menu option
2026-05-15 13:57:31 +10:00
renovate[bot] 13944f3cca
Update pre-commit hook pre-commit/mirrors-mypy to v2.1.0 (#4538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-13 08:54:05 +10:00
renovate[bot] b95321d38c
Update dependency mypy to v2.1.0 (#4535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-13 08:33:42 +10:00
renovate[bot] 3dec5025c3
Update dependency arch/python-pydantic to v2.13.4 (#4534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-13 08:21:44 +10:00
stinga11 f7a6f70fc8
Replace terminal and file manager in Budgie profile (#4533)
I think it was a mistake to have made the previous changes to KDE Apps. In retrospect, it seemed like a good idea since the Budgie developer had done it that way in Fedora, which is the distro the developer uses as a reference for Budgie. When you start using it, you realize there's no bridge between the desktop and the KDE Apps, and things like Dolphin recognizes icons and themes, requiring some rather annoying manual configurations. Then, if you want to change them again, you have to change those configurations again. Files don't open by default with the apps either; you have to configure them for that to work.

At first, I thought the Budgie packager for Arch had forgotten some stray dependency, but with some free time, I tested it with Fedora 44, and to my surprise, it has exactly the same problems, which is completely unacceptable for a final stable release. I suppose he'll make the necessary changes in the near future, but right now, it's a disaster.
2026-05-13 08:21:15 +10:00
Daniel Girtler 7fc33c2507
Enhance config types and summary (#4532)
* Enhance config types and summary

* Update
2026-05-10 22:35:53 +02:00
Lena Pastwa 2de7254b21
Update Polish translation (#4529) 2026-05-08 11:28:36 +10:00
renovate[bot] af106eb238
Update dependency mypy to v2 (#4523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-07 21:57:10 +10:00
renovate[bot] 4265be6e43
Update pre-commit hook pre-commit/mirrors-mypy to v2 (#4525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-07 21:56:30 +10:00
Softer b0b7983af2
Fix bspwm black screen: add provision() delegation and default configs (#4518)
* Fix bspwm black screen: add provision() delegation and default configs

* Move TYPE_CHECKING imports to top level in bspwm profile
2026-05-07 10:20:09 +10:00
Softer ef1cfe7b56
Add share-log subcommand to upload install.log to paste.rs (#4511)
* Add --share-log flag to upload install.log to paste.rs

* Apply ruff-format to share_log.py

* Rework share-log per review: subcommand, TUI confirmation, truncate large logs

* Replace curl/SysCommand with urllib.request, remove defensive try/except

Per review: use stdlib urllib instead of shelling out to curl,
drop unnecessary try/except around TUI confirmation,
remove tempfile (content is passed directly as bytes).

* Fix TUI imports after ui module was pulled one level up (#4515)

* Decouple share_install_log from TUI module

* Add unit tests for share_install_log function

* Update docs to use share-log subcommand syntax
2026-05-07 10:18:56 +10:00
Softer 7b5dddf34b
Fix truncated package metadata in additional packages preview (#3580) (#4510)
* Fix truncated package metadata in additional packages preview (#3580)

* Simplify package info fix: use rstrip and CSS wrap instead of env var plumbing

* Apply preview text wrap conditionally via wrap_preview parameter

* Add missing wrap-preview CSS to OptionListScreen and pass parameter through SelectMenu
2026-05-07 10:17:13 +10:00
renovate[bot] dcc38fe9aa
Update dependency arch/python-cryptography to v48 (#4520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-06 15:25:45 +10:00
Softer 11607851c4
Keep standalone initramfs for grub-btrfs when UKI is enabled (#4513)
* Keep standalone initramfs for grub-btrfs when UKI is enabled

Fixes #4505

* Move keep_initramfs logic into add_bootloader()

The grub-btrfs snapshot detection was in guided.py, forcing custom
script authors to replicate it. Since Installer already holds
disk_config, the check belongs inside add_bootloader().
2026-05-06 14:26:59 +10:00
Daniel Girtler ba7dbeadfc
Refactor profile seat access selection (#4457)
* Refactor profile seat access selection

* Update
2026-05-04 14:45:43 +10:00
aronmr-1 b5501d9507
Improved Finnish translation further (#4516) 2026-05-04 08:16:16 +10:00
renovate[bot] 528c27fbe1
Update dependency arch/python-textual to v8.2.5 (#4517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-04 08:14:56 +10:00
Daniel Girtler b18cc57216
Pull `ui` module one level up (#4515)
* Pull the UI module one level up

* Update
2026-05-03 23:54:42 +02:00
Softer dd34954011
Remove dead Cutefish desktop profile (#4514)
The cutefish package was never in the official Arch repos (AUR only)
and the upstream project is abandoned. Installing this profile would
always fail with a pacman "not found" error.
2026-05-03 10:33:48 +10:00
utuhiro78 76629ecc15
Update Japanese translation (#4504) 2026-04-30 17:37:37 +10:00
Softer dc7d9cf0a2
Improve Ukrainian translation (#4502) 2026-04-30 09:23:08 +10:00
correctmost b936fa11e3
Fix some inconsistent-return-statements Pylint warnings (#4503) 2026-04-30 09:22:45 +10:00
aronmr-1 6fefa3b6f5
Improved Finnish language (#4501) 2026-04-30 07:43:01 +10:00
Softer 3b026cb603
Fix broken localization in U2F, LUKS, and gfx driver preview (#4500) 2026-04-30 07:33:21 +10:00
correctmost 05560d29af
Add basic Pyrefly configuration to pyproject.toml (#4494)
This makes it easier to use the Pyrefly type checker with various
IDEs.
2026-04-29 21:54:59 +10:00
correctmost 3223ae4212
Remove the old curses-based TUI code (#4497)
The new Textual-based TUI shipped in the April .iso.
2026-04-29 17:08:24 +10:00
Steen Rabol 468e33d54f
Danish translation (#4493)
* Danish translation

* updated base.po and  created base.mo

---------

Co-authored-by: Steen Rabol <steen@rabol.dev>
2026-04-29 16:06:10 +10:00
utuhiro78 ccbc53d576
Update Japanese translation (#4498) 2026-04-29 13:44:11 +10:00
correctmost c4d68855cb
Replace time.time with time.monotonic to avoid clock drift issues (#4492)
Using time.time() can be inaccurate if the system clock gets
updated in between calls.
2026-04-28 13:34:33 +02:00
Softer 8a929f603f
Fix sway+nvidia confirmation dialog (#4481) (#4485)
* Fix sway+nvidia confirmation dialog (#4481)

Two bugs in the Sway+Nvidia driver confirmation:

1. The boolean was inverted - confirming "yes, I'm okay with issues"
   reverted the driver to the previous choice instead of keeping it.

2. The warning triggered for any Nvidia driver, including the
   open-source nouveau driver which is officially supported by Sway.

Add GfxDriver.is_nvidia_proprietary() and is_nvidia_nouveau() methods
so the warning fires only for nvidia-open-dkms (proprietary userspace).

* Address review feedback (#4485)

- Drop is_nvidia_nouveau() helper. It is not called anywhere yet; can
  be re-added when a consumer lands.
- Collapse the Sway+Nvidia confirmation result handling into a single
  expression now that allow_skip=False guarantees a boolean answer.
2026-04-28 21:02:32 +10:00
Softer e1efa34d8a
Install mkinitcpio explicitly in base packages (#4484)
Prevents pacstrap from picking a different initramfs provider
(dracut, booster) when running from a non-Arch host.

Fixes #4368
2026-04-28 21:01:05 +10:00
Softer cb0f3a6eba
Warn when no network configuration is selected (#4408)
* Make network configuration mandatory with explicit "No network" option

* Add network configuration recommendation hint and disable sorting

* Warn when network selection was skipped instead of making it mandatory

Replace the mandatory network_config menu requirement with a yellow
warning on the final confirmation screen, shown only when the user
skipped the network menu entirely. Explicitly picking "No network
configuration" is treated as a conscious choice and does not trigger
the warning.

- Drop mandatory=True from the network_config global menu item
- Rename NicType.NONE menu label from "No network" to
  "No network configuration" (an installed system still has an NIC;
  only the install-time configuration is absent)
- Add ConfigurationOutput.get_install_warnings() and render them in
  confirm_config when show_install_warnings=True
- guided.py and minimal.py enable the warning on the confirm screen
- Reuse the existing "No network configuration" msgid in .pot; add the
  warning string; update Ukrainian translations accordingly

* Drop NicType.NONE from this PR

The NONE option introduced earlier in this branch is being removed per
review feedback (#4408). Whether to add explicit "None" options across
multiple menus (greeter, gfx driver, network, bootloader) is now being
discussed in #4464 and should land as a separate change there.

This PR keeps only the warning-on-confirm behaviour: when the user
skipped the network menu entirely (network_config is None), a yellow
warning is shown on the final confirmation screen.
2026-04-28 20:17:40 +10:00
Hassaan Amin 1b7a32a3b3
Fix typos in known issues documentation (#4488)
First Edit :
- "effort" is singular so use has not have

Second Edit :
- we use "an" before vowel sounds (an old )


Third Edit :  
- use Sometimes because sometimes means occasionally and Some times means  some period or era 

Fourth Edit :
- Plural of ISO is ISOs not ISO's
2026-04-28 20:03:49 +10:00
Softer 08cba236f6
Show install summary when configuration is valid (#4475)
* Show install summary when configuration is valid

Previously the Install menu preview was empty when everything was valid,
leaving the user with no "ready" signal. Now show "Ready to install"
plus a two-column summary of the current configuration.

- New _install_summary() composes an aligned key/value table with rows
  for disks+FS+LUKS, bootloader, kernel, profile, greeter, package
  count, network, locale and timezone. Column width adapts to the
  longest translated label so translations keep the alignment.
- Rows whose underlying config is not set are skipped rather than
  rendered as empty.
- base.pot / uk base.po: add new msgids for summary labels.

* Move install summary into ConfigurationOutput

The summary helper that renders the install preview's two-column
configuration overview lived in GlobalMenu. Move it to
ConfigurationOutput.as_summary() so it sits alongside the JSON
output methods that share the same role. The preview now syncs
menu state to ArchConfig and delegates rendering.
2026-04-28 19:22:01 +10:00
Franco Castillo 8cc35f41d8
Update Spanish translation (#4490)
Signed-off-by: Franco Castillo <castillofrancodamian@gmail.com>
2026-04-28 17:08:39 +10:00
codefiles 9010ccf9eb
Add kernel enum (#4489) 2026-04-28 17:08:19 +10:00
Anton Hvornum 074dfbb178
Adding a reference to why AUR is not bundled as an option in archinstall (#4468) 2026-04-27 08:44:14 +02:00
Softer 008d303aaf
Remove disabled translation-check workflow (#4483)
The workflow has been fully commented out since #2119 (2023-09-28) when
translation handling was reworked. Because the file has no `on:` triggers,
GitHub Actions creates a failed workflow run for every push, polluting
the Actions tab without affecting PR check-runs.
2026-04-27 09:35:34 +10:00
utuhiro78 3795bfc21a
Update Japanese translation (#4482) 2026-04-27 09:32:10 +10:00
Softer 210329ed80
Add console font selection to Locales menu (#4469)
* Add console font selection to Locales menu

Add a 4th menu item "Console font" to the Locales configuration,
allowing users to select a console font for the target system.
The selected font is written to /etc/vconsole.conf.

If a terminus font (ter-*) is selected, the terminus-font package
is automatically installed on the target system.

* Switch list_console_fonts to pathlib and add @lru_cache

Address svartkanin's review on #4469. Replace os.listdir +
chained removesuffix with Path.glob('*.gz') + split('.')[0],
and cache the result via lru_cache - the kbd consolefonts
directory is static at runtime so re-scanning on every menu
reopen was wasted I/O.
2026-04-27 09:30:20 +10:00
Softer 9e05260df5
Fix argv injection in _create_user and gpasswd loop (#4473)
* Fix argv injection in _create_user and gpasswd loop

Use argv list with run() instead of f-string interpolation into
SysCommand, add debug logging on failure.

* Extract _chroot_argv helper and harden user/file ops

Address svartkanin's review on #4473: factor the
['arch-chroot', '-S', str(self.target), ...] boilerplate into a
private Installer._chroot_argv() helper, and migrate the seven
existing argv-form call sites to it (useradd, gpasswd, chpasswd,
chsh, chown, snapper-create-config, grub-install).

Two related hardening tweaks while in the area:

- Raise gpasswd failure log from debug() to warn(). The group-add
  loop has no return-False feedback channel for the caller, so a
  silent debug() means a half-configured user looks like a
  successful install.

- Add `--` end-of-options separator for useradd and chown so a
  username or path starting with `-` cannot smuggle flags. The TUI
  validates usernames, but parse_arguments() in models/users.py
  does not, so config.json is the residual hole; this closes it
  for these two sites at zero cost.
2026-04-27 09:24:51 +10:00
Softer d836ab0a66
Extend validate_bootloader_layout with UEFI-dependent checks (#4474)
* Extend validate_bootloader_layout with UEFI-dependent checks

Add is_uefi parameter and three new validations: Systemd-boot, Efistub
and rEFInd require UEFI; Efistub additionally requires a FAT boot
partition. Move the rEFInd UEFI-only check out of GlobalMenu so
guided.py and Installer silent-install paths get the same coverage.

* Encapsulate UEFI-only flag in Bootloader enum

Replace module-level _UEFI_ONLY_BOOTLOADERS tuple with an
is_uefi_only() method on the Bootloader enum, mirroring the
existing has_uki_support() / has_removable_support() pattern.

* Drop is_uefi parameter from validate_bootloader_layout

The UEFI flag is a constant system fact for the run, so the
validator retrieves it via SysInfo.has_uefi() directly instead
of having every caller pass it in. Updates all three call sites
in global_menu.py, installer.py and guided.py, and removes the
now-unused SysInfo import from guided.py.
2026-04-26 19:14:23 +10:00
renovate[bot] 3c4c87bdd6
Update dependency arch/python-cryptography to v47 (#4478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-26 19:03:16 +10:00
renovate[bot] efda78cad5
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.12 (#4477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-26 18:36:10 +10:00
renovate[bot] c8e5c85e20
Update dependency ruff to v0.15.12 (#4476)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-26 18:35:31 +10:00
Softer bb46e295d1
Fix OVMF paths in README QEMU examples (#4472)
All three qemu-system-x86_64 examples in README pointed both -drive
if=pflash entries at the same file (/usr/share/ovmf/x64/OVMF.4m.fd).
OVMF needs CODE for the first pflash and VARS for the second; as
written, EFI NVRAM is not initialized correctly.

The path /usr/share/ovmf/x64/ is also stale - the ovmf package has
been replaced by edk2-ovmf, which installs under /usr/share/edk2/x64/.

Fix both in the three examples (testimage loop section, base Boot ISO
block, espeakup variant).
2026-04-24 12:19:01 +02:00
Thierry M de43019094
Update base.po (#4467) 2026-04-23 20:07:54 +10:00
renovate[bot] 6e222bcdd8
Update dependency pre-commit to v4.6.0 (#4459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 11:08:51 +10:00
renovate[bot] 6652197e35
Update pre-commit hook pre-commit/mirrors-mypy to v1.20.2 (#4463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 11:06:09 +10:00
renovate[bot] 80ae85cc8b
Update dependency mypy to v1.20.2 (#4458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-22 21:41:09 +10:00
correctmost 00834f9c6e
Use bools instead of time values for SysCommandWorker attributes (#4462)
Only the truthiness of the time values was being evaluated. This
also reduces the usage of time.time(), which makes the codebase
more resilient against clock drift issues.
2026-04-22 21:33:35 +10:00
Softer e7d38d0e82
Fix Limine install with ESP mounted outside /boot (#4442)
* Fix Limine install with ESP mounted outside /boot

Place limine.conf next to the EFI binary on the ESP so it is found
regardless of ESP mountpoint, and block unbootable layouts (non-UKI
Limine with ESP not at /boot and no separate /boot partition) in
GlobalMenu validation, guided.main() and _add_limine_bootloader().

Fixes #4333

* Extract bootloader layout validation into lib/bootloader/utils

* Consolidate Limine layout validation in bootloader utils

Move the boot-partition FAT check from GlobalMenu into
validate_bootloader_layout so all three call sites (GlobalMenu,
guided.py, Installer._add_limine_bootloader) share one function.

Return a BootloaderValidationFailure dataclass (kind + description)
instead of str | None, so callers can match on the failure kind and
the description is built where partition context is in scope.

* Encapsulate FAT filesystem detection in FilesystemType.is_fat()
2026-04-22 21:32:41 +10:00
correctmost d1765323d8
Use staticmethod for PasswordStrength methods (#4461)
The methods don't need access to the class, so they don't need to
be classmethods. This change reduces the number of 'bad return'
warnings seen when running Pyright, Pyrefly, and ty.
2026-04-22 21:31:06 +10:00
javier-anton-ordonez 094798b496
ES Language updated (#4456)
* ES langauje updated and added diferent translations

* Update archinstall/locales/es/LC_MESSAGES/base.po

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Cerrar -> Ocultar

Github copilot suggested

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix Spanish translations and accents in base.po

Github suggestions

* ./locales_generator.sh generated

* Accent updated and ./locales_generator.sh generated

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 10:34:41 +10:00
stinga11 fa54c9d6c9
Change Budgie display server and update packages (#4450)
Some changes consistent with other distros.
2026-04-22 10:23:09 +10:00
renovate[bot] b842e54091
Update dependency arch/python-textual to v8.2.4 (#4448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-21 19:52:17 +10:00
Softer 03b245a77a
Set console font automatically when selecting language (#4356)
* Set console font automatically when selecting language

Add console_font field to languages.json and Language dataclass.
When a language is activated, setfont is called automatically,
falling back to default8x16 on error or for languages without
a custom font. Also activate translation when loading language
from config file.

* Support FONT environment variable for console font override

When FONT env var is set, use it as the console font instead of the language-specific font mapping from languages.json. The font is applied at startup and preserved across language switches.

On exit (success or failure), restore the console font to default8x16.

* CI checks fix

* Try to restore original console font with setfont -O

* Fix for pylint

* Restore console font before Textual exits application mode

Move font set/restore into Textual lifecycle to prevent color
artifacts from 256/512 glyph transitions. Apply FONT env var
and language font in on_mount, restore in _on_exit_app.

Skip font change when loading language from config (set_font=False)
to defer it until TUI starts.

* Fall back to language font mapping when FONT env var is invalid

Add _using_env_font flag to skip mapping only when FONT was
successfully applied. Show info message after TUI exits if FONT
could not be set.

* Use tempfile for console font backup files

* Fix linter errors: use mkstemp, close fds, add @override

* Fix ruff formatting

* Move font state from module singleton into TranslationHandler

* Refactor font handling per review feedback

* Make font methods members of TranslationHandler, skip on non-ISO

* ci: trigger tests

* Move running_from_iso import to module level

No circular dep, simpler than per-method local imports.

* Add explicit ISO guard to restore_console_font

Matches the existing guards in _set_font and save_console_font.
Behaviour was already safe implicitly via _font_backup=None, but
the explicit check makes intent obvious at the call site.

* Skip apply_console_font off-ISO

* Use list form for setfont SysCommand calls
2026-04-21 18:36:29 +10:00
Dylan M. Taylor 7d10f9e08b
ci: Strip Arch pkgrel from Repology versions in Renovate config (#4453)
* ci: Strip Arch pkgrel from Repology versions in Renovate config

* ci: Use .+ instead of .* in extractVersionTemplate

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>

---------

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>
2026-04-21 15:18:25 +10:00
Anton Hvornum d80bdb4fad
Bumping version to: 4.3 (#4452) 2026-04-20 22:31:22 +02:00
Anton Hvornum 68d137cdce
Bumping version to: 4.3 2026-04-20 22:28:35 +02:00
Softer 82a46c66ca
Fix shell injection in user_set_shell and chown (#4443)
Use argv list with run() instead of sh -c with f-string interpolation,
fix mutable default argument in chown, add debug logging on failure.
2026-04-20 18:36:49 +10:00
Dhruv 86dc1bbc12
locale(hindi): Completed the hindi translation locale (#4446)
* locale: ran generate_locale before making change

* locale: first 1000 confirmed everything is fine

* locale(hi): fixed rest of the fuzzy and empty strings

* locale(hi): ran locale generator
2026-04-20 18:29:26 +10:00
codefiles cd62eff4a7
Fix copying into target directory (#4441) 2026-04-18 12:05:00 +10:00
Softer e8ea33c41c
Enable power management services after package installation (#4440) 2026-04-18 12:04:14 +10:00
Softer 4fcef35af0
Add optional "Additional fonts" selection to Applications menu (#4420)
* Add Fonts application with multi-select for emoji and CJK packages

* Rename to Additional fonts and add package descriptions

* Add noto-fonts to font package selection

* Move font descriptions into FontPackage enum method

* ci: trigger tests

* Add ttf-liberation and ttf-dejavu to font selection
2026-04-18 12:02:47 +10:00
codefiles 9fdd7eb12e
Refactor EncryptionType (#4438)
* Use UPPER_CASE for EncryptionType

* Use StrEnum for EncryptionType
2026-04-17 10:08:23 +10:00
codefiles 8fe8d4e35f
Pin linkify-it-py to Arch Linux package version (#4437) 2026-04-17 10:07:57 +10:00
renovate[bot] 02faf9fbb9
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.11 (#4436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-17 10:07:09 +10:00
renovate[bot] 9b9f9d6ac0
Update dependency ruff to v0.15.11 (#4435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-17 10:06:51 +10:00
Dylan M. Taylor 98d72a6b57
ci: Update renovate to track Arch Linux repos using repology (#4434) 2026-04-16 19:17:07 +10:00
Daniel Girtler 06488dfb9a
Fix table column error (#4406) 2026-04-16 07:30:38 +02:00
Daniel Girtler 29588fadee
Fix encrypted partition selection (#4407) 2026-04-16 07:29:53 +02:00
codefiles 3cabe860ae
Refactor LsblkInfo field validators (#4428) 2026-04-16 14:06:23 +10:00
codefiles 7ea171369a
Refactor PartitionType (#4432)
* Use UPPER_CASE for PartitionType

* Use StrEnum for PartitionType
2026-04-16 14:05:08 +10:00
Anton Hvornum 8b3be842ca
Bumping version to: 4.2 (#4433) 2026-04-15 20:47:13 +02:00
codefiles 30f50d9719
Revert pydantic update due to upstream bug (#4427) 2026-04-15 10:16:34 +10:00
tom c1771cf615
locale(german): extend and fix translation (#4421)
* chore(locale): Fix and improve German translation strings

* chore(locale): edit incorrect translations
2026-04-14 17:40:15 +10:00
Ian Dieb H. Rebouças c0d3c14b6e
Update Brazilian Portuguese translation (#4426)
Co-authored-by: Ian Dieb <idbreboucas@gmail.com>
2026-04-14 17:39:23 +10:00
correctmost 113cc7fd94
Fix urllib.parse import to avoid warnings with ty and Pyright (#4425) 2026-04-14 15:39:08 +10:00
renovate[bot] 88366ba575
Update dependency pydantic to v2.13.0 (#4424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 15:38:14 +10:00
renovate[bot] 728bb450c7
Update pre-commit hook pre-commit/mirrors-mypy to v1.20.1 (#4423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-14 15:37:56 +10:00
codefiles 2b27e565ae
Refactor ModificationStatus (#4422)
* Use UPPER_CASE for ModificationStatus member names

* Use StrEnum for ModificationStatus

* Replace LvmVolumeStatus with ModificationStatus
2026-04-14 15:37:14 +10:00
renovate[bot] a52bfc3446
Update dependency mypy to v1.20.1 (#4419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 18:49:04 +10:00
renovate[bot] 5dd94e80d2
Update astral-sh/ruff-action action to v4 (#4416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 18:48:17 +10:00
codefiles 335a7b792c
Refactor FilesystemType (#4417)
* Use UPPER_CASE for FilesystemType member names

* Use StrEnum for FilesystemType

* Use FilesystemType member values
2026-04-13 18:41:59 +10:00
Matteo 5553cb9eae
locale: Update Italian translation (#4415)
-Translated new strings
-Fixed a couple of old translations
2026-04-13 18:38:24 +10:00
Softer 80da3f14a1
Add Pacman settings submenu with Color and ParallelDownloads (#4404)
* Add Pacman settings submenu with Color and ParallelDownloads

Replace the standalone Parallel Downloads menu item with a Pacman
submenu containing ParallelDownloads (default 5) and Color (default on).
Settings are applied to both live and target system pacman.conf.
Hidden behind --advanced flag.

Backward compatible with old configs using "parallel_downloads" key.

* Skip _apply_to_live when user exits Pacman menu without changes

* Use TypedDict for PacmanConfiguration serialization

* Show Pacman menu by default, keep ParallelDownloads behind --advanced
2026-04-13 18:37:51 +10:00
codefiles bf9b9cb7c1
Remove invalid container PATH lookup (#4413) 2026-04-12 14:48:51 +10:00
Softer 9a2d546882
Add translation support for TUI help groups and binding descriptions (#4363)
* Add translation support for TUI help groups and binding descriptions

Help group names shown via F1 (General, Navigation, Selection, Search) and key binding descriptions in the Textual footer (Down, Up, Cancel, Confirm, etc.) were hardcoded in English and never went through the translation system.

* ruff_format_check and mypy fixes

* Refactor _translate_bindings to accept BindingsMap instead of Any

Add TApp.translate_bindings() to avoid exporting private functions
across modules.

* Revert deprecated curses help.py change

* Move TApp import to module level in global_menu

* Change translate_bindings from staticmethod to member method
2026-04-12 14:24:46 +10:00
codefiles d6de03ab76
Use auto() for eligible StrEnum members (#4411) 2026-04-11 22:51:43 +10:00
Koutheir Attouchi 51600ecd2a
Update Arabic translation (#4410) 2026-04-11 18:14:14 +10:00
Softer 60bcded743
Replace tab with spaces in preview package lists (#4409) 2026-04-11 17:48:26 +10:00
codefiles 934407414e
Add constant for mirrorlist (#4403) 2026-04-11 10:17:29 +10:00
renovate[bot] 1a4eedf868
Update actions/upload-artifact digest to 043fb46 (#4402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-11 10:16:58 +10:00
Daniel Girtler b2f413124b
Allow granular plasma configuration (#4389)
* Flexible plasma profile selection

* Update

* Update

* Update

* Update
2026-04-10 07:07:28 +02:00
renovate[bot] 101f647319
Update dependency ruff to v0.15.10 (#4400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-10 07:24:30 +10:00
renovate[bot] 9059a18dfe
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.10 (#4401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-10 07:24:01 +10:00
correctmost d6987b4e9d
Fix del-attr-with-constant ruff warnings (#4399) 2026-04-09 10:19:02 +10:00
codefiles 1bf87c6f4a
Use Path.copy() (#4398) 2026-04-08 15:18:09 +02:00
codefiles 6505e17f34
Refactor sync_log_to_install_medium() (#4397) 2026-04-08 14:35:35 +02:00
Daniel Girtler 78987d75fe
Revert enter behavior on multi-select (#4386) 2026-04-08 09:17:38 +02:00
renovate[bot] b906a34a5d
fix(deps): update dependency pytest to v9.0.3 (#4391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-08 17:05:33 +10:00
correctmost 18c77b94f2
Remove unnecessary 'unused-ignore' mypy directives (#4395)
The directives were added because a PR branch accidentally lagged
behind master and did not contain the mypy python_version bump from
commit 0175949ca.

This fixes commit d70e03fa3.
2026-04-08 08:42:49 +02:00
codefiles 3ef0848ffe
Refactor copy_iso_network_config() (#4394) 2026-04-08 08:13:14 +02:00
codefiles a92cd50aec
Remove deprecated __future__ imports (#4393) 2026-04-08 08:11:05 +02:00
Crystal eda9ee338b
Update German translation for base.po (#4334)
* Update German translations in base.po

Updated German translations for various prompts and messages in the base.po file.

* Apply suggestions from code review

Co-authored-by: Luca Zeuch <l-zeuch@email.de>

* Removed the # fuzzy´s from the already translated sentences.

---------

Co-authored-by: Luca Zeuch <l-zeuch@email.de>
2026-04-07 23:23:22 +10:00
Ghosted Owl caf285a6dc
Czech translation (#4370)
* Complete Czech translation

* Complete Czech translation
2026-04-07 23:21:52 +10:00
codefiles 7609a1634e
Bump ruff target-version to Python 3.14 (#4390)
* Bump ruff target-version to Python 3.14

* Update exception syntax to pass ruff format
2026-04-07 23:09:14 +10:00
Daniel Girtler f37ae8b282
Handle empty mountpoint input (#4388) 2026-04-07 13:54:18 +02:00
codefiles d57709cb2f
Remove quotes from type annotation (#4384) 2026-04-07 12:21:51 +10:00
Lena Pastwa 9e8cbd0181
Update Polish translation (#4383) 2026-04-07 12:21:19 +10:00
HADEON 3ba29f1193
Patch plasma profile (#4358)
* add essentials #4336

* rem unrelated change
2026-04-06 22:43:06 +10:00
codefiles a8d265184c
Add constant for archiso mountpoint (#4377) 2026-04-06 22:41:33 +10:00
codefiles 37159dcb6d
Add LPath execute permissions methods (#4378) 2026-04-06 09:16:21 +02:00
codefiles e2bd7b3405
Remove is_subpath() and use Path.is_relative_to() (#4372) 2026-04-06 16:42:45 +10:00
codefiles 56bec01979
Add constant for /etc/pacman.conf (#4375) 2026-04-05 23:18:22 +02:00
Ghosted Owl 41858db832
Russian translation (#4367)
* Сomplete russian translation

* Сomplete russian translation
2026-04-03 23:41:28 +11:00
renovate[bot] 5a4b6131c1
fix(deps): update dependency ruff to v0.15.9 (#4366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-03 09:50:16 +11:00
renovate[bot] c91e19e534
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.9 (#4365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-03 09:41:45 +11:00
Dylan M. Taylor 6c6c8d8000
Stop installing xorg packages for Wayland profiles (#4348)
* Add WaylandProfile to avoid installing xorg packages for Wayland compositors

* Refactor: use composition (is_wayland) instead of WaylandProfile inheritance

* Fix X11 profiles to inherit xorg packages from XorgProfile

* Style: use consistent multi-line super().__init__ for Wayland profiles

* Refactor: replace is_wayland with DisplayServerType enum

Add DisplayServerType enum (Xorg/Wayland) to Profile. All profiles
now inherit Profile directly with an explicit display_server param.
desktop.py installs xorg-server and xorg-xinit for Xorg profiles.
XorgProfile remains for standalone Xorg selection.

* Remove unnecessary super().packages from desktop profiles
2026-04-03 09:38:02 +11:00
Julio Napurí 6c9f66265a
i18n: Updated Spanish translation (#4357) 2026-04-02 18:21:49 +11:00
Softer 5544abd380
Improved translation into Ukrainian (#4355) 2026-04-02 14:40:40 +11:00
renovate[bot] deb8080322
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.20.0 (#4351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-02 07:52:30 +11:00
correctmost d70e03fa3b
Update mypy to 1.20.0 (#4352)
* Update mypy to 1.20.0

This commit also removes a cast that is no longer needed after
https://github.com/python/mypy/pull/20602

* Ignore os.system deprecation warnings from mypy to fix CI
2026-04-02 07:51:47 +11:00
Dylan M. Taylor 29b73302ae
Don't write encryption keyfiles to an unencrypted root partition (#4349) 2026-04-01 15:35:33 +11:00
Dylan M. Taylor a3c6bd5d45
Use nvidia-open instead of nvidia-open-dkms for mainline kernels (#4347) 2026-04-01 15:24:11 +11:00
Anton Hvornum 0175949cab
Bumping version to: 4.1 (#4346) 2026-03-31 23:02:14 +02:00
Dylan M. Taylor 179d9c7b48
Remove Nvidia proprietary driver option (nvidia-dkms no longer in repos) (#4343)
* Remove Nvidia proprietary driver option (nvidia-dkms no longer in repos)

* Remove libva-mesa-driver (now provided by mesa)
2026-03-31 22:51:57 +02:00
Odyssey bdf6f92991
Update catalan locales (#4344) 2026-04-01 07:33:34 +11:00
Dylan M. Taylor ef4d7cfc3a
Move auth config helpers to AuthenticationConfiguration class (#4340) 2026-03-31 11:29:26 +11:00
Dylan M. Taylor 603c432e18
Warn when a desktop profile's greeter can't log in without a regular user (#4331) 2026-03-31 10:48:01 +11:00
Anton Hvornum e3e9563aa6
Bumping version to: 4.0 2026-03-30 23:35:51 +02:00
renovate[bot] 398d6cfdbe
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.8 (#4329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 14:33:13 +11:00
renovate[bot] c76fd90074
fix(deps): update dependency ruff to v0.15.8 (#4328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 14:32:55 +11:00
Daniel Girtler 4b9087e2f4
Fix AwaitComplete runtime error (#4325) 2026-03-26 13:04:43 +01:00
Daniel Girtler dc64e15327
Handle enter on filter confirm (#4324) 2026-03-26 13:04:09 +01:00
Daniel Girtler dd0da34ed9
Move luks into disk module (#4296) 2026-03-21 18:36:00 +11:00
Daniel Girtler e763c1109c
Refactor interactive module (#4315)
* Refactor interactive module

* Refactor interactive module

* Update
2026-03-21 18:35:44 +11:00
Daniel Girtler 1764b4971d
Refactor mirrors (#4274) 2026-03-21 17:57:33 +11:00
Daniel Girtler 8d6c56ca2b
Allow skip on password confirmation (#4273) 2026-03-21 17:57:17 +11:00
Daniel Girtler e06dd6299f
Zram - remove custom size in config (#4272) 2026-03-21 17:56:59 +11:00
Daniel Girtler 8e34303175
Fix iwd network installation and setup (#4271)
* Fix 4223 - IWD

* Update

* Update
2026-03-21 17:56:41 +11:00
renovate[bot] cfdcebcacd
fix(deps): update dependency ruff to v0.15.7 (#4312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-21 12:35:25 +11:00
renovate[bot] b21ac742c2
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.7 (#4311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-21 12:34:40 +11:00
Rosy 594cf54c8a
Update Turkish translations and refresh locale template (#4310) 2026-03-19 08:36:23 +11:00
Piyush Singh e74a72f482
fix: reintegrate PasswordStrength into password prompt (#4111) (#4291) 2026-03-18 22:48:05 +11:00
Daniel Girtler b186fb3f64
Async menu (#4308)
* Move to async TUI

* Update

* Update
2026-03-16 10:39:36 +11:00
renovate[bot] ffa130fe99
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.6 (#4303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 15:51:48 +11:00
amode e718a12b42
Removed unnecessary signal for multi-line comment. (#4298) 2026-03-13 15:01:52 +11:00
renovate[bot] 28f4904a3c
fix(deps): update dependency ruff to v0.15.6 (#4304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 15:01:20 +11:00
Valerii bace59aef8
better Ukrainian 1.4.2 (#4301)
* Add files via upload

* Add files via upload
2026-03-13 14:59:26 +11:00
Atharv Singh Negi 8b65bd9a6a
Add nepali translation (#4295)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend

* Create base.po

* Update base.po

* Update base.po

* Update languages.json

* Update base.po

* Complete Nepali translation and generated .mo file

* Update languages.json

* Update languages.json

* Update Nepali translations with system and user strings

* Add translations for Partitioning, Bootloader, and Network

* Add disk and configuration translations

* Reach 500+ lines: User management, NTP, and BTRFS subvolumes

* Reached 700+ strings: Desktop profiles, BTRFS setup, and final installation prompts

* Fix syntax error in base.po and update translations
2026-03-09 21:22:53 +11:00
Atharv Singh Negi a4bfd379f5
Add nepali translation (#4294)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend

* Create base.po

* Update base.po

* Update base.po

* Update languages.json

* Update base.po

* Complete Nepali translation and generated .mo file

* Update languages.json

* Update languages.json

* Update Nepali translations with system and user strings

* Add translations for Partitioning, Bootloader, and Network

* Add disk and configuration translations

* Reach 500+ lines: User management, NTP, and BTRFS subvolumes

* Reached 700+ strings: Desktop profiles, BTRFS setup, and final installation prompts
2026-03-09 12:31:36 +11:00
Atharv Singh Negi 2b63d573af
Locales: Add Nepali (ne) translation (Initial 300+ strings) (#4288)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend

* Create base.po

* Update base.po

* Update base.po

* Update languages.json

* Update base.po

* Complete Nepali translation and generated .mo file

* Update languages.json

* Update languages.json

* Update Nepali translations with system and user strings

* Add translations for Partitioning, Bootloader, and Network

* Add disk and configuration translations

* Reach 500+ lines: User management, NTP, and BTRFS subvolumes
2026-03-07 16:57:37 +11:00
renovate[bot] 33b0131e4e
fix(deps): update dependency ruff to v0.15.5 (#4287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 12:06:53 +11:00
renovate[bot] 8638726e7a
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.5 (#4286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 12:06:33 +11:00
codefiles 6d87a062fd
Move all LVM helpers to dedicated module (#4283) 2026-03-05 09:13:04 +11:00
Atharv Singh Negi e133cb0d28
Add nepali translation (#4282)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend

* Create base.po

* Update base.po

* Update base.po

* Update languages.json

* Update base.po

* Complete Nepali translation and generated .mo file

* Update languages.json

* Update languages.json

* Update Nepali translations with system and user strings

* Add translations for Partitioning, Bootloader, and Network
2026-03-05 09:11:45 +11:00
codefiles 452abe4277
Move udev_sync() to disk.utils (#4281) 2026-03-04 12:43:47 +11:00
codefiles 813b9b34b3
Refactor arch_config_handler to use DI (#4280) 2026-03-03 20:11:22 +11:00
codefiles 64567a748a
Remove support for NTFS root file system (#4279)
* Remove support for NTFS root file system

* Fix rootfstype value
2026-03-03 12:21:31 +11:00
codefiles b809c84ad5
Use os.sync() instead of sync command (#4278) 2026-03-02 07:48:22 +11:00
codefiles 501759d979
Remove obsolete GRUB package install (#4277) 2026-03-02 07:04:32 +11:00
codefiles 839e6bfc55
Move get_version() to version module (#4270) 2026-03-01 11:31:13 +11:00
codefiles 74a230dae9
Enable if-key-in-dict-del ruff rule (#4269) 2026-03-01 08:06:49 +11:00
codefiles f19f8d195f
Replace Installer with path parameter in Boot (#4268) 2026-03-01 08:06:15 +11:00
codefiles 76d6f08841
Remove unnecessary post-init (#4267) 2026-02-28 17:30:59 +11:00
codefiles 390b4959bd
Refactor ISO check (#4264) 2026-02-28 11:20:02 +11:00
correctmost d413c01fde
Fix a reportUnnecessaryIsInstance Pyright warning (#4263) 2026-02-27 17:39:46 +11:00
renovate[bot] d781c9d857
fix(deps): update dependency ruff to v0.15.4 (#4259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 15:39:03 +11:00
renovate[bot] 33f3709c31
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.4 (#4261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 15:38:49 +11:00
renovate[bot] ec8bd01ced
chore(deps): update actions/upload-artifact action to v7 (#4260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 12:14:25 +11:00
codefiles 3cd85c023e
Remove unneeded chmod (#4262) 2026-02-27 12:13:59 +11:00
renovate[bot] b6ce255b36
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.3 (#4258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 06:59:38 +11:00
Daniel Girtler e4faba2de0
Convert imports to absolute imports (#4196)
* Enable ruff absolute import check

* Convert all relative imports to absolute

* Update

* Update
2026-02-26 20:17:48 +11:00
Anton Hvornum c92a457142
Revert "Locale timezone python chmod (#4210)" (#4257)
This reverts commit 01d3af9fbb.
2026-02-25 22:48:03 +01:00
Favilances 01d3af9fbb
Locale timezone python chmod (#4210)
* Set timezone during minimal install

* Clean up conflicting boot entries

* Revert "Clean up conflicting boot entries"

This reverts commit e673301b99.
2026-02-25 22:47:25 +01:00
Daniel Girtler 683996c44f
Add missing linkify dependency (#4208)
* Add missing linkify dependency

* Update PKGBUILD
2026-02-25 22:45:28 +01:00
Daniel Girtler 4f87ccba28
Refactor general utility functions (#4212) 2026-02-25 22:45:21 +01:00
renovate[bot] 3447d2f47d
fix(deps): update dependency pylint to v4.0.5 (#4253)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-24 08:45:24 +11:00
codefiles d86daa531a
Add provision() to configure users for profiles (#4254) 2026-02-22 07:53:58 +11:00
renovate[bot] a9c939cb86
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.2 (#4251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 17:34:04 +11:00
renovate[bot] 415fefef26
fix(deps): update dependency ruff to v0.15.2 (#4252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 17:33:41 +11:00
codefiles 2d18b55fa7
Remove unused advanced member from Profile (#4248) 2026-02-19 12:32:57 +11:00
nubprogrammer bb76f96bd5
locales: Add Nepali (ne) translation (#4222)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend

* Create base.po

* Update base.po

* Update base.po

* Update languages.json

* Update base.po

* Complete Nepali translation and generated .mo file

* Update languages.json

* Update languages.json

* Update Nepali translations with system and user strings
2026-02-19 07:22:43 +11:00
Mário Victor Ribeiro Silva 377b3bee97
feat: add `plasma-login-manager` (#4247) 2026-02-19 07:20:13 +11:00
codefiles 618c0bd5dc
Remove arch_config_handler from installer (#4246) 2026-02-19 07:18:53 +11:00
codefiles bd35473b5d
Move LVM helpers to dedicated module (#4245) 2026-02-18 15:57:10 +11:00
codefiles f2c17c6341
Move get_parent_device_path() to disk.utils (#4243) 2026-02-18 09:12:47 +11:00
HADEON f736b8c4ec
install gfx before DE/WM/Server (#4238) 2026-02-17 13:19:11 +11:00
codefiles 8edab9fafd
Move get_unique_path_for_device() to disk.utils (#4242) 2026-02-17 13:15:07 +11:00
codefiles 083a73eab1
Move unlock_luks2_dev() (#4234) 2026-02-17 07:35:25 +11:00
codefiles 35c2ff3ef5
Move swapon() to disk.utils (#4233) 2026-02-16 14:43:10 +11:00
HADEON 8148b1d9bf
fix efistub bootloader to use backslashes (#4231)
* fix efistub to use backslashes

* comment

* cf1

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>

* cf2

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>

---------

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>
2026-02-16 13:10:52 +11:00
codefiles 3b6f7db942
Move mount() to disk.utils (#4232) 2026-02-16 13:08:59 +11:00
codefiles 9bd2131792
Refactor SysInfo.has_uefi() to DI for bootloader (#4230) 2026-02-15 12:34:40 +11:00
renovate[bot] ec9ee80062
fix(deps): update dependency ruff to v0.15.1 (#4224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-15 09:58:02 +11:00
renovate[bot] cfe61c0993
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.15.1 (#4225)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-15 09:57:47 +11:00
Franco Castillo 6ce0556c2c
Update Spanish translation (#4228) 2026-02-15 08:40:19 +11:00
correctmost a61d4e5314
Revert "Handle whole-disk LUKS root params (#4205)" (#4229)
This reverts commit 0dddc7308d.
2026-02-15 08:38:55 +11:00
Mário Victor Ribeiro Silva 21358f77dd
feat: update `pt_BR` translations (#4221) 2026-02-12 12:30:14 +11:00
Matteo a7901d172d
Translation: Update Italian (#4219)
- Added new strings
- Improved old translation
2026-02-06 07:49:01 +11:00
utuhiro78 c0d47db75d
Update Japanese translation (#4217) 2026-02-04 19:34:31 +11:00
renovate[bot] 66e0dd119f
Update dependency ruff to v0.15.0 (#4214)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 20:49:30 +00:00
renovate[bot] 13b7c7194a
Update pre-commit hook astral-sh/ruff-pre-commit to v0.15.0 (#4215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 07:48:52 +11:00
renovate[bot] 3dab440cbf
Update actions/checkout digest to de0fac2 (#4213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 07:06:48 +11:00
Daniel Girtler 1d8352b466
Use TypeAdapter for Json serialization (#4183) 2026-02-03 22:27:51 +11:00
Favilances 0dddc7308d
Handle whole-disk LUKS root params (#4205)
Co-authored-by: favilances <favilances@proton.me>
2026-02-03 22:03:22 +11:00
Daniel Girtler 4a8604ac88
Make list manager run private (#4192) 2026-02-03 11:22:56 +01:00
Daniel Girtler aac6c896d9
Add missing galician mo file (#4209) 2026-02-03 11:18:11 +01:00
gaelgnz f8304b0bd1
Added Galician language support (#4202)
* Added Galician language support

* Delete archinstall/locales/gl/LC_MESSAGES/base.mo
2026-02-03 11:35:33 +11:00
codefiles 0394a735a7
Fix typo locale -> local (#4201) 2026-02-03 11:34:53 +11:00
codefiles 12bd83ca8a
Refactor skip_boot to use dependency injection (#4200) 2026-02-03 11:34:27 +11:00
correctmost b5f710425f
Remove an unused milliseconds member from Installer (#4199) 2026-02-01 22:48:30 +11:00
correctmost 331634fd80
Measure install time with monotonic clock instead of system clock (#4198)
Using time.time() can be inaccurate if the system clock gets
updated in between calls.
2026-02-01 22:48:09 +11:00
correctmost 173c64c847
Avoid union syntax in an isinstance check (#4197)
This is a partial revert of commit 44f4bc86. The UP038 ruff rule
was removed and using union syntax is no longer recommended.
2026-02-01 22:47:39 +11:00
renovate[bot] 47a4caf163
Update astral-sh/ruff-action action to v3.6.1 (#4195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-31 09:40:51 +11:00
Daniel Girtler 449c1bfb36
Rename ask* functions to select* (#4191) 2026-01-31 08:17:11 +11:00
renovate[bot] e2ba6cbc54
Update astral-sh/ruff-action action to v3.6.0 (#4193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-30 21:47:52 +11:00
Federico Berrueta 97dd099c0e
Fix: Resolve fuzzy strings and add note for 'Seat access translation' (#4186) 2026-01-29 09:49:16 +11:00
HADEON 26014aa092
Add sponsors button (#4056)
* Add archlinux funding file

* Fix formatting of custom funding URL
2026-01-28 22:01:56 +01:00
nubprogrammer 6d89770e7a
Update Hindi translations for archinstall (#4185)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po

* Update Hindi translations in base.po

* Update translation for Network Manager iwd backend
2026-01-29 07:56:59 +11:00
codefiles 2cb81d8758
Reuse MirrorListHandler instance (#4184) 2026-01-29 07:54:18 +11:00
Daniel Girtler 5612325dc3
Refactor code to reduce circular dep (#4175)
* Refactor to reduce circular dep

* Update

* Update
2026-01-28 13:43:57 +01:00
correctmost 8f104bc829
Remove archinstall.svg from project root (#4180)
The file was accidentally added in commit bde544ca.
2026-01-28 11:43:28 +11:00
codefiles 18105fff22
Use main() as script entrypoint (#4179) 2026-01-28 11:42:51 +11:00
codefiles 76284b601b
Remove superfluous __future__ import (#4178) 2026-01-28 07:23:25 +11:00
codefiles 8ffc20b6c1
Refactor script list (#4177) 2026-01-27 17:47:36 +01:00
Daniel Girtler bde544caca
Refactor syscommand (#4176)
* Refactor SysCommand

* Refactor syscommand
2026-01-27 13:31:33 +01:00
Daniel Girtler 316251f6e0
Refactor network config installer (#4143)
* Refactor network config installer

* Update

* Move network menu

* Remove singleton
2026-01-27 19:39:45 +11:00
Daniel Girtler 5fe42bd2f5
Refactor user menu (#4141) 2026-01-27 18:50:09 +11:00
correctmost 15460e4a88
Fix useless-return warnings reported by Pylint (#4173) 2026-01-27 16:40:38 +11:00
codefiles d7bcd431a7
Refactor application_handler to use DI (#4172) 2026-01-27 16:39:42 +11:00
codefiles ef3b6ab853
Refactor auth_handler to use dependency injection (#4171) 2026-01-27 12:46:31 +11:00
Daniel Girtler 2421e538a8
Move Pacman out of init (#4150)
* Move pacman out of init

* Update archinstall/lib/pacman/pacman.py

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>

* Update archinstall/lib/pacman/pacman.py

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>

---------

Co-authored-by: codefiles <11915375+codefiles@users.noreply.github.com>
2026-01-27 12:34:48 +11:00
codefiles 28f7aec2af
Refactor mirror_list_handler to use DI (#4170) 2026-01-27 11:23:01 +11:00
Daniel Girtler a025c79ac3
Delete dead code (#4144) 2026-01-27 11:22:03 +11:00
nubprogrammer 93736349a2
Improve Hindi translations and resolve all fuzzy entries (#4167)
* Improve Hindi translations (base.po)

* Improve all Fuzzy and more hindi translations

* Update base.po

* Update base.po
2026-01-26 23:35:32 +01:00
Daniel Girtler 9bd16e998c
Fix - Handle cancel from list manger properly (#4160) 2026-01-26 23:34:58 +01:00
Daniel Girtler 7a0b4c2a30
Fix - disk partitioning menu (#4158) 2026-01-26 23:34:23 +01:00
Daniel Girtler 63e3825756
Remove obsolete package functions (#4145) 2026-01-26 23:33:21 +01:00
Daniel Girtler a57bb801c0
[README update] Clarify archinstall upgrade steps (#4138)
* Clarify upgrade steps on Live ISO

* Update

* Update

* Update

* Update README.md

Spelling fix

---------

Co-authored-by: Anton Hvornum <torxed@archlinux.org>
2026-01-26 23:30:27 +01:00
codefiles 2ed6182575
Refactor wifi_handler to use dependency injection (#4161) 2026-01-23 14:40:08 +11:00
renovate[bot] b07bbeecd3
Update pre-commit hook astral-sh/ruff-pre-commit to v0.14.14 (#4166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 13:13:08 +11:00
renovate[bot] 2c2b17b8a6
Update dependency ruff to v0.14.14 (#4165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 13:12:47 +11:00
nubprogrammer 7fb8fb57ee
Improve Hindi translations (base.po) (#4163) 2026-01-23 06:44:23 +11:00
renovate[bot] 3500bc3b41
Update actions/setup-python digest to a309ff8 (#4157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 18:32:02 +11:00
codefiles 192aff0b94
Replace sys.exit calls with return values (#4156) 2026-01-22 13:58:40 +11:00
codefiles 4d2864ba76
Refactor error message (#4153) 2026-01-21 16:39:49 +11:00
codefiles 78969c58c7
Move script entry point (#4151) 2026-01-21 12:10:19 +11:00
nubprogrammer 7cf1566902
Improve Hindi translations in base.po (#4152)
* Improve Hindi translations in base.po

This PR improves and fixes Hindi translations in base.po.
It also corrects previously incorrect fuzzy translations.
No functional code changes.

* Update base.po

* removed fuzzy from the things i fixed in translation
2026-01-21 12:08:43 +11:00
codefiles c7dbf0105e
Rework has_default_btrfs_vols (#4147) 2026-01-20 11:18:22 +11:00
codefiles 38462db2db
Enable GRUB UKI menu entries (#4139) 2026-01-19 15:46:02 +11:00
Daniel Girtler 0aca992ac5
Migrate UI to textual (#3997)
* Footer textual

* Linting

* Revert pre-commit for pylint

* Add missing textual

* Reinstate example

* Linting
2026-01-19 07:01:40 +11:00
codefiles 9a38b73baf
Enable quoted-annotation ruff rule and fixes (#4137) 2026-01-18 08:19:07 +11:00
codefiles a150a8d9f7
Remove quotes from Installer type annotations (#4136) 2026-01-17 18:52:18 +11:00
codefiles 7b45613996
Use Self for remaining occurrences (#4135) 2026-01-17 18:51:44 +11:00
renovate[bot] e1d9935f30
Update pre-commit hook astral-sh/ruff-pre-commit to v0.14.13 (#4133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 10:50:45 +11:00
renovate[bot] 65190a253e
Update dependency ruff to v0.14.13 (#4131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 10:50:19 +11:00
Kenraaliskuutteri 7995d6e815
Updated Finnish translation in base.po (#4129)
Updated Finnish translations for various messages in base.po.
2026-01-16 06:36:36 +11:00
codefiles 2c154245cb
Use Self for Tui (#4130) 2026-01-16 06:36:11 +11:00
codefiles 2c85b5eab0
Remove storage module (#4128) 2026-01-15 14:09:35 +11:00
codefiles 972e278555
Remove leftover code from unattended script (#4127) 2026-01-15 12:18:47 +11:00
codefiles e5a14c0cfe
Use Self in tui (#4126) 2026-01-15 07:05:43 +11:00
Hannan c4de093122
Add header and update urdu translations (#4122)
* add header

* update urdu translations
2026-01-14 19:54:33 +11:00
codefiles 90d1b08628
Remove unused installation_hooks property (#4118) 2026-01-14 19:53:43 +11:00
codefiles 20460e1c29
Use Self in default_profiles (#4125) 2026-01-14 16:03:32 +11:00
codefiles 2e9d5e4829
Refactor Boot (#4124) 2026-01-14 13:29:22 +11:00
codefiles 5811f81e59
Use Self for parameters to dunder methods (#4123) 2026-01-14 13:24:56 +11:00
codefiles 8f7d59b718
Replace __enter__ return type with Self (#4121) 2026-01-13 15:45:21 +01:00
Mattyan89 b779345a5b
fixes (#4093) 2026-01-13 20:59:02 +11:00
codefiles f6eca309ac
Use sys.exit instead of exit (#4120) 2026-01-13 07:51:08 +11:00
codefiles 82512eed0e
Remove superfluous list creation in join calls (#4119) 2026-01-12 12:56:55 +11:00
codefiles db6c11345a
Remove superfluous __future__ imports (#4117) 2026-01-12 10:20:07 +11:00
codefiles 3374b47d50
Use Self for return instances of cls (#4116) 2026-01-12 09:06:19 +11:00
codefiles bde3b0ed6e
Change staticmethods to classmethods (#4115) 2026-01-12 07:17:21 +11:00
codefiles b1290672bb
Change classmethods to staticmethods (#4114) 2026-01-11 17:40:43 +11:00
codefiles 43509d8ce1
Use instance method for type_to_text (#4113) 2026-01-11 11:29:39 +11:00
codefiles efd57870d0
Use instance method for _load_config (#4112) 2026-01-11 11:29:01 +11:00
codefiles cb4b7e3db0
Update classmethods to use cls (#4110) 2026-01-11 10:29:32 +11:00
codefiles 01bee60cd1
Fix typos (#4109) 2026-01-11 10:28:53 +11:00
Tobias Stoeckmann e0c1b869a6
Fix typos in comments (#4107)
Typos found with codespell.

No functional change.
2026-01-10 10:17:50 +11:00
correctmost d07a9b4a92
Disable the non-empty-init-module ruff rule (#4106)
This allows ruff check --preview to pass again.
2026-01-10 10:17:00 +11:00
HADEON ef9f704761
hotfix: Firewall (#4100)
* Needed to have default files + enabled status

* Modify conf file directly

* nl

* more thnx to codefiles

* Fix previews in menu
2026-01-10 10:02:35 +11:00
Benjamin Smith 5fbe1deea4
Add missing spanish translations (#4098)
* added some missing Spanish translations

* added missing translations, fixed typos

* added a bunch of missing translations, fixed some too
2026-01-09 15:54:56 +11:00
renovate[bot] 7292a9e449
chore(deps): update dependency ruff to v0.14.11 (#4095)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 15:54:10 +11:00
renovate[bot] 85d38f063b
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.11 (#4096)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 15:53:52 +11:00
HADEON 62edc56a52
hotfix: Revert Grub changes (#4099)
* Hotfix grub

* revert changes
2026-01-09 11:30:12 +11:00
correctmost 84c36adb1f
Avoid a reportPossiblyUnboundVariable warning with Pyright (#4092) 2026-01-07 18:49:07 +11:00
correctmost e2e158973f
Remove unused pytest-mock dependency (#4091) 2026-01-07 12:01:10 +11:00
correctmost ef3369b1c5
Add node_modules to .gitignore and Pylint's ignore list (#4089)
This makes it easier to use Pyright locally.
2026-01-07 11:54:54 +11:00
correctmost bb13139339
Fix an invalid-type-form error reported by ty (#4088)
The 'bytes' annotation for the 'bytes' property was actually a
forward reference to the property instead of builtins.bytes:
https://github.com/astral-sh/ty/issues/1747#issuecomment-3609042917
2026-01-07 11:54:15 +11:00
correctmost 0470495cb1
Migrate pytest configs to native TOML and enable strict mode (#4087) 2026-01-07 11:52:46 +11:00
correctmost 8e0ff2d2a9
Update comment in _final_warning (#4086)
Device paths are no longer mentioned as of adbadbf6.
2026-01-07 10:05:58 +11:00
correctmost cec29d123b
Use serialization TypedDicts to reduce Any instances (#4085) 2026-01-07 10:05:13 +11:00
Valerii f2e40742bb
Better Ukrainian v1.4 (#4084) 2026-01-07 09:57:35 +11:00
HADEON 0188459917
fix: bootloader changes (#4073)
* Bootloader changes:

	-> GRUB: Support for UKI
		- Disable 10_linux or breaks at kernel updates
		- Create 09_custom entry
	-> rEFInd:
		- Remove fallback entry similar to other bootloaders
		- With UKI still one dead entry can be hidden with DEL key
	-> All bootloaders:
		- Default to UKI on if supported, if using no UKI and /efi
		Causes systemd boot to not load, because it needs a XTLDRBOOT part
		Safer default for modern setups and simpler sec boot compat

* Add new models

* Modify based on grub-2.14-rc1 -> No need to use chainload
Thanks to codefiles for the heads-up

* Simplify has_uki_support

* Tab

* checks
2026-01-06 20:04:01 +11:00
HADEON cc6e247dcf
State libfido2 dependency (#4082) 2026-01-06 19:55:58 +11:00
HADEON 450664cdc4
feat: firewalls (#4074)
* Firewall Init

* Enable service
2026-01-05 20:04:43 +11:00
HADEON e590277e69
Another small readme change for clarity (#4077) 2026-01-05 12:35:42 +11:00
summoner 9488233a5a
Translation: Update hungarian po/mo (#4078)
Fix typos and mistakes
2026-01-05 12:35:08 +11:00
HADEON 146a2352bc
Change plasma-meta -> -desktop. (#4076)
This avoids to pull in sddm-kcm (to respect user's choice of login manager)
And to not pull in discover and related tools that should also be up to the user.
2026-01-04 19:37:58 +11:00
summoner 5eb3307efd
Translation: Update hu.po (#4071)
Translate new strings
fix typos
fix wording
2026-01-04 19:22:08 +11:00
HADEON d1a74edf9d
fix pre-commit hook (#4070) 2026-01-02 13:33:24 +11:00
walken fd143af05b
Czech localization update (#4068) 2026-01-02 13:29:11 +11:00
scrypt-kitty 4582d60f13
Do not mount btrfs partitions unless required, tested working and solves issue #3689 (#3992) 2026-01-02 13:27:53 +11:00
Luna Jernberg 7889a5417f
Update Arch Installer translation before Jan 2026 release (#4065)
* Update base.po

Update Swedish for January 2026 iso

* Update Swedish translations before release today

Updated Swedish translations for various messages in base.po.
2026-01-01 20:21:49 +11:00
Daniel Girtler bff715ddab
Add instructions for booting Arch ISO in a VM (#4041)
* Add instructions to boot ISO in VM

* Update

---------

Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2026-01-01 10:10:24 +01:00
utuhiro78 051352e218
Update Japanese translation (#4064) 2026-01-01 10:14:31 +11:00
Abdullah Koyuncu 9433f15573
Update Turkish translation (#4062) 2026-01-01 10:13:28 +11:00
Matteo d5b554be1e
Update Italian translation (#4060)
- Translated new strings
- Improved some string
2026-01-01 10:12:37 +11:00
Anton Hvornum aabf6ae19e
Bumping version to: 3.0.15 (#4058)
* Bumping version to: 3.0.15

* ruff formatting
2025-12-31 13:02:10 +01:00
justbispo cb6fe6b34b
Add support for rEFInd boot manager (#3707)
* Add support for rEFInd boot manager

* Fix ruff formatting complaints

* Added support for different mountpoints for /efi and /boot

Also fixed issue where if /boot is located in a BTRFS root partition, the initrd path wasn't including the subvol name.

* Fix ruff formatting complaints

* Replace SysCommand with self.arch_chroot call

* Fix ruff formatting complaints

---------

Co-authored-by: Diogo Bispo <gpg.jta36@slmail.me>
2025-12-31 15:24:22 +11:00
Mariya 446d23c59d
feat(applications): add support for power-profiles-daemon/tuned as a power management daemon (#4015)
* fix(profiles): install power-profiles-daemon by default in the desktop
profile

* fix: only install power-profiles-daemon if a battery is detected

* chore: clean up has_battery method

* fix: make power management daemon a configurable application

* fix: make linter happy after merge

* fix: fix merge issues

* fix: give has_battery a return type to make linter happy

* chore: add locale msgids for power management related strings

* fix: changes requested in review

* fix: cache has_battery result

* fix: changes requested in review

* fix: just return none directly

* fix: add selected power management daemon to applications menu preview
2025-12-31 13:22:27 +11:00
HADEON 83c9bf06b2
Modify archinstall language display to be handled like other sections. (#4048)
* Modify archinstall language display to be handled like other sections.
This fixes a bug where language names that were too long would break curses menu.

* Revert types change
2025-12-31 10:46:46 +11:00
HADEON eb5e88f317
Mirrors sort #4046 (#4050)
* Mirrors sort #4046

* Change logic to be just sorted on bitrate
2025-12-30 15:20:45 +01:00
Vincent Dahmen 9f4f29bd29
lib/network: adds symlink to configure systemd-resovled properly (#4052)
* lib/network: adds symlink to configure systemd-resovled properly

* lib/network: adds overwrite mechanism to enforce resolved symlink

---------

Co-authored-by: Vincent Dahmen <wahrwolf@wolfpit.net>
2025-12-30 16:01:01 +11:00
HADEON 2954e4397b
Feat: Zram algorithm config (#4042)
* Add configuration for swap algorithm. Backward compatible implementation

* Fix interaction to default to Yes and show (default)

* Fix mypy error

* Any -> str, str

* feedback Enums

* test file

* line length warning

* Renames

* Fix default values in TUI menu for display

* Address feedback

* More feedback, really appreciate it.

* Adapt to use same | None = None pattern

* Pytests

* Add missing import for Zram
2025-12-30 15:52:35 +11:00
HADEON 747385a883
Lvm2/LUKS fixes/Mirror Logic (#4047)
* Lvm hotfix attempt

* Use --force and --yes flags

* Changed mirror behavior and more lvm testing

* Handle properly LvmOnLuks to only export in one fo the scenarios

* Idek

* Remove dead block

* Use -f flag and wipefs to remove any existing headers avoid "has signatures"

* oops

* Revert mirror change
2025-12-29 17:35:24 +01:00
HADEON ac984b7622
This commit changes the order of the main menu. (#4043)
Since in the next NVIDIA update, the kernel choice will have an impact on profiles.
The order this way makes it more logical: Bootloader => Kernel => Hw drivers
2025-12-28 22:20:17 +11:00
HADEON 4e52dc7493
README clarifications/corrections (#4038)
* A smaller readme tweak to stop answering the same questions :D

* rem
2025-12-28 11:28:40 +11:00
HADEON 79313c4942
Fix mirrors hang when /status endpoint is down (#4031)
* add explicit _fetched_remote bool

* Attempt 2

* Adds about 15 seconds time-out to fetch_data_from_url with 3 retries (4, 5, 6)
Then fallsback to fully local list

* Feedbacks: 20 -> 30
Do not return early
Add debug
Remove new flag
60 second timeout for reflector

* Clean up install logs by hiding mirror scores behind --verbose
2025-12-28 11:10:03 +11:00
HADEON c1eae10e93
Enable IWD to be used as back-end in network selection (#4025)
* feedback

* feedback2

* Refactor for less duplicate code and more conscise logic
Group NM types and handle configurations appropriatly
	- For IWD -> Copy from ISO + Disable standalone and configure back-end
	- For standard -> Install wpa_supplicant
	- For both install applet only when Desktop profile

Added comments for clearer logic

* Rem comments

* Rem copy to ISO

* the one commit to rule them all
2025-12-28 11:09:37 +11:00
Mintsuki 42a4ee8472
Make removable location the default for bootloader installation (#4030)
Also update the option's description to make it clear that it being enabled is the sane default.
2025-12-27 14:38:14 +01:00
Daniel Girtler 6968a33508
Fix kurdish translation files (#4004)
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-12-27 12:41:02 +01:00
utuhiro78 4d01d1dbbb
Update Japanese translation (#4040) 2025-12-27 22:37:11 +11:00
Daniel Girtler 7635474772
Explicitly allow selecting any additional repository (#3973)
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-12-27 09:25:15 +01:00
HADEON a4ad1b3724
Remove whitespace in timestamp (#4039) 2025-12-27 17:38:03 +11:00
HADEON 5fcea379b9
Fix LVM creation/ info (#4024)
* use pull and udev sync

* 30s -> 5min
2025-12-25 13:07:26 +11:00
HADEON e5ccdb0c1c
Adds a timer to post install screen (#4028)
* Add timer to end screen of guided

* ruff check
2025-12-25 11:06:03 +11:00
HADEON 9e7a5f6931
Do not install base-devel by default (#4022)
* replace base-devel with sudo and add disclaimer texts

* revert basepot and add rem comments
2025-12-25 11:00:34 +11:00
HADEON 9412f97771
Set up Zram dynamically based on best practice (#4027)
* Use the total available RAM / 2 for swap size dynamically.

* ws

* - Systems with 8 GB RAM or less will get 4096 MB zram
- Systems with more than 8 GB RAM will get half their RAM as zram

* Add debug print
2025-12-25 10:59:03 +11:00
Luis Antonio 1faac77c0d
Update Portuguese translations (pt and pt_BR) and metadata. (#4021)
* Revise Portuguese translations in base.po

Updated translations for various messages in Portuguese.

* Update Brazilian Portuguese translations in base.po

Updated translations and metadata in the Brazilian Portuguese locale file.

* Update Last-Translator in Brazilian Portuguese locale
2025-12-22 22:08:02 +11:00
HADEON 1227babd8c
Change LVM /root def to adapt dynamically (#4005)
* Change def size from 20 -> 32
Rest is still calculated from available - root.

* Use the existing process_root_partition_size():
For LVM just like regular disk best-effort.
This is only for single disk layouts.
2025-12-22 22:05:30 +11:00
Mariya ba5f924540
fix: enable the cosmic-greeter service (#4023) 2025-12-22 09:07:47 +11:00
HADEON f639290c20
ly.service -> ly@tty1.service (#4006)
* ly -> ly@tty1

* ly -> ly@tty1 with disable getty

* ws

* ws2

* ws3

* ws4

* conditionally disable and define
2025-12-21 21:37:06 +11:00
Luna Jernberg 043cd68a7c
Update base.po (#4020)
Update Swedish for January 2026 iso
2025-12-21 21:36:02 +11:00
Mariya 9876558f74
chore: cosmic is stable now, don't hide it behind --advanced flag (#4017)
* chore: cosmic is stable now, don't hide it behind --advanced flag

* fix: remove --advanced check in GreeterType

* chore: remove unused import to make linter happy
2025-12-21 14:45:51 +11:00
Mariya d7b559c67e
feat(applications): add CUPS installation support (#4013)
* feat(applications): add CUPS installation support

* fix: use translation for print service preview_action

* fix: incorrect action for print service menu item

* chore: refactor naming, printer -> print service

* fix: commit untracked file

* chore: fix formatting to make linter happy
2025-12-21 14:30:11 +11:00
Mariya cf31148e46
fix: fix fido warning message being spammed (#4016) 2025-12-21 14:16:06 +11:00
Mariya dee2a84057
fix: fix translation for bluetooth preview_action (#4014) 2025-12-21 13:57:29 +11:00
HADEON 17dc001857
Cache property of graphics_devices (#4007)
* On horrible hardware this makes it so that the "Graphics drivers" section loads directly.
By initializing it with launch instead of on the fly.

* Init after logs

* This cache the property of graphics drivers properly.
Instead of trying to hack early init.
2025-12-21 12:22:59 +11:00
renovate[bot] 6f768ea87c
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.10 (#4012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 18:36:04 +11:00
renovate[bot] e2cdf42690
chore(deps): update dependency ruff to v0.14.10 (#4011)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 18:35:50 +11:00
Odyssey b579773421
Update catalan locales (#4010) 2025-12-19 15:51:21 +11:00
renovate[bot] d13882fca1
chore(deps): update dependency pre-commit to v4.5.1 (#4003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 15:50:37 +11:00
HADEON 6b23eff422
H2T (Host-to-target) installs and prevent host pollution (#3978)
* Add host-to-target (H2T) installation mode detection

- Add running_from_host() function to detect if running from installed system vs ISO
- Function checks for /run/archiso existence (ISO mode) vs host mode
- Add clear logging of installation mode on startup
- Skip keyboard layout changes when running from host system
- Fix Pyright type error in jsonify() by using Any instead of object
- Update README to mention installation from existing system

This enables archinstall to be run from an existing Arch installation
to perform host-to-target installs on other disks/partitions.

* match existing style

* rem debug

* info -> debug
2025-12-16 10:59:04 +11:00
renovate[bot] 810a50e46c
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.19.1 (#3998)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 08:26:52 +11:00
renovate[bot] 582c54f4e2
chore(deps): update dependency mypy to v1.19.1 (#3996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 16:54:57 +11:00
CYAXXX bc738a48be
Add Kurdish translation (#3994)
* Add Kurdish translation

* Update base.po
2025-12-15 16:34:12 +11:00
CYAXXX 0a6fd6a07e
Delete archinstall/locales/ku (#3995) 2025-12-15 14:23:48 +11:00
scrypt-kitty 20718ead79
Remove /tmp/archlive before building. Otherwise latest changes are not built. (#3993) 2025-12-15 11:23:01 +11:00
CYAXXX fff783ed93
Add Kurdish language (#3991)
* Add Kurdish language

This pull request introduces full Kurdish language integration into archinstall, allowing Kurdish speaking users to navigate and use the installer entirely in their native language.

* Fix translation for timezone selection prompt
2025-12-15 11:20:51 +11:00
renovate[bot] f65cc5f6c7
chore(deps): update actions/upload-artifact action to v6 (#3985)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 11:18:54 +11:00
Jesper Otten cffe47a369
Adding new Dutch translations (#3987) 2025-12-15 11:14:26 +11:00
renovate[bot] 0117205f73
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.9 (#3983)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 11:12:21 +11:00
renovate[bot] 1f1daf1afc
chore(deps): update dependency ruff to v0.14.9 (#3982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 11:11:46 +11:00
walken c9b0b171b3
Czech localization update (#3980) 2025-12-10 22:46:58 +11:00
HADEON 326232098a
Full disclosure for bootloaders/keymaps (#3979)
* Clarify LUKS2 encryption and keyboard layout context in locales README

* ws

* clarity

* final
2025-12-10 22:46:21 +11:00
HADEON 6b50815eb6
Vconsole.conf KEYMAP= FONT=
_base mkinitcpio.conf v40 error (#3928)

* pr1

* pr2

* pr2-2

* pr2-3

* Revert genfstab and pacstrap command changes

* mistake
2025-12-08 19:25:20 +11:00
renovate[bot] 57bd613679
chore(deps): update dependency pytest to v9.0.2 (#3972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-07 17:07:39 +11:00
renovate[bot] 8872f98c8d
chore(deps): update dependency mypy to v1.19.0 (#3956)
* chore(deps): update dependency mypy to v1.19.0

* Update

* Update

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-12-06 21:40:37 +11:00
renovate[bot] 601e033188
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.8 (#3969)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 20:23:52 +11:00
Gabriel A Hernandez 07ab6bf4a0
Menu now filters items and sorts using priority, improving UX. (#3967)
* Menu now filters and sorts using priority, improving UX.

* Refactor: improve logic, removed redundancy

* Refactor: improve logic, removed redundancy

* Improved logic when getting view items in menuItems

* Improved logic when getting view items in menuItems
2025-12-06 20:10:01 +11:00
Gabriel A Hernandez 7398e2785d
Typo and grammar fixes (#3970)
* Menu now filters and sorts using priority, improving UX.

* Refactor: improve logic, removed redundancy

* Refactor: improve logic, removed redundancy

* Typo and grammar fixes

* Typo and grammar fixes

* Fix comment formatting in .gitlab-ci.yml

* Fix comment

* Removed code from separate pull request

* Update menu_item.py

* removed white space

* Remove unnecessary blank lines in menu_item.py

>:(
2025-12-06 20:06:07 +11:00
Mintsuki d176b9514c
Remove GRUB removable fallback path (#3971)
As mentioned by @svartkanin on #3950, given we now have a way for the user to
explicitly specify if they want to install to the removable location, having a
fallback like this seems undesirable.

On top of that, as mentioned by @correctmost on the same PR, the code that said
PR introduced was bugged and would always raise an exception anyways.
2025-12-05 20:05:24 +11:00
renovate[bot] 66fb49617a
chore(deps): update dependency ruff to v0.14.8 (#3968)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 20:03:05 +11:00
renovate[bot] dbfa60bfa8
chore(deps): update actions/checkout digest to 8e8c483 (#3965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-05 20:02:24 +11:00
Abdullah Koyuncu e7442bf5c6
Translation: Update the Turkish base.po/base.mo (#3964) 2025-12-03 12:26:09 +11:00
renovate[bot] 627530fd82
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.19.0 (#3959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 12:24:06 +01:00
renovate[bot] 94f22fcc55
chore(deps): update dependency pylint to v4.0.4 (#3962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 18:58:26 +11:00
summoner 8387c8f589
Translation: Update hungarian base.po/base.mo (#3961)
Translate new strings
Fix translation
2025-12-01 18:57:59 +11:00
Anton Hvornum 3290175084
Bumping version to: 3.0.14 (#3960) 2025-11-29 22:19:59 +01:00
renovate[bot] 0221099398
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.7 (#3958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-29 11:55:23 +11:00
renovate[bot] 29cb454fda
chore(deps): update dependency ruff to v0.14.7 (#3957)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-29 11:55:05 +11:00
Mintsuki 6fc0bc3671
Fix GRUB fallback-from-removable logic introduced in c095eb56d8 (#3950)
Commit c095eb56d8 was supposed to introduce logic
such that if the `grub-install` command failed with a `--removable` flag, then
another attempt would be made with such flag removed.

This was broken because the `--removable` flag was kept in both cases (likely a
copy-paste mistake). This has been an issue since, in all future iterations of
the code.

What this commit does is fix this logic, but also invert the cases tested:
first test without `--removable`, then add it should that case fail, as this is
the most sensible thing to do.
2025-11-29 11:10:04 +11:00
Gabriel A Hernandez 7732d50016
Improved regex in _validate_value() that checks user input for partition value and unit. (#3952)
- Allows for white space in between groups, aligning better with displayed example.
- Removed unneeded | symbol, which was checking as literal rather than working as "or %"
2025-11-29 11:04:39 +11:00
Mintsuki d7e5dc3692
Do not unconditionally set `prompt` to `None` in list_manager.py (#3955)
May address issue #3954.
2025-11-29 11:04:12 +11:00
Mintsuki 70a6c3499a
Do not create BLS and Limine entries for fallback initramfs (#3949)
* Do not create BLS and Limine entries for fallback initramfs

Fallback initramfs seem to no longer be built by default.

* Remove initramfs variant logic altogether
2025-11-29 11:00:43 +11:00
renovate[bot] b751ad5dab
chore(deps): update dependency pydantic to v2.12.5 (#3951)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 21:23:38 +11:00
renovate[bot] 147447face
chore(deps): update actions/setup-python digest to 83679a8 (#3946)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 10:31:55 +11:00
renovate[bot] 60302060e3
chore(deps): update dependency pre-commit to v4.5.0 (#3944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-23 09:33:20 +11:00
correctmost 5f0742a87a
Use dummy variables instead of unused-awaitable suppressions (#3943) 2025-11-22 21:16:11 +11:00
correctmost 73ceb0c99d
Use ClassVar to avoid mutable-class-default (RUF012) warnings (#3942) 2025-11-22 21:15:41 +11:00
renovate[bot] 2ee66a059e
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.6 (#3941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 08:36:34 +11:00
renovate[bot] a94d94a8e1
chore(deps): update dependency ruff to v0.14.6 (#3940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 08:36:14 +11:00
Mintsuki eb815d817f
Add dialog to install EFI bootloader to removable location (#3932)
* Add dialog to install EFI bootloader to removable location

This is just for GRUB and Limine for now.

* Move bootloader removable and UKI selections to bootloader submenu

* Update ask_for_bootloader_removable() prompt for ease of translation

* Fix issue where removable and UKI options were always enabled at first

* Minor cosmetic fixes to bootloader removable code

* Add has_removable_support to Bootloader

* Validate UKI and removable options in installer

* Use has_removable_support() where appropriate

* Fix potential AttributeError when bootloader_config is None

* Set default value for bootloader configuration menu item

* Update documentation after EFI removable/Limine changes

* Update limine.conf and non-removable location paths (as per Wiki)

* Do not create fallback boot menu entries when using UKIs on Limine

* Remove useless ask_* wrappers in bootloader_menu

* Improve bootloader menu previews

* Make bootloader menu __init__.py empty
2025-11-21 11:41:24 +11:00
renovate[bot] 840c1c0f7e
chore(deps): update actions/checkout action to v6 (#3939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 07:41:57 +11:00
renovate[bot] bf3784fc0c
chore(deps): update actions/checkout digest to 93cb6ef (#3937)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 21:14:16 +11:00
utuhiro78 4c8fe60a28
Update Japanese translation (#3938) 2025-11-20 21:13:51 +11:00
HADEON 46072132a1
Readme Tweak (#3929)
* pr1

* pr2

* pr3

* pr3-2

* pr3-3

* pr3-4

* pr4

* Revert hardware.py to original state on snapshots branch

* readme

* Revert installer.py to original state on readme branch

* match base branch

* Revert genfstab and pacstrap command changes

* readme tweaks2
2025-11-17 08:50:35 +11:00
HADEON c3aba06a01
Fix f-string in snapshot debug installation (#3934)
* pr1

* pr2

* pr3

* pr3-2

* pr3-3

* pr3-4

* pr4

* Revert hardware.py to original state on snapshots branch

* Revert genfstab and pacstrap command changes

* f string
2025-11-17 06:51:35 +11:00
HADEON 375d64a600
Snapshots Fix Snapper-Grub (#3930)
* pr1

* pr2

* pr3

* pr3-2

* pr3-3

* pr3-4

* pr4

* Revert hardware.py to original state on snapshots branch

* Revert genfstab and pacstrap command changes
2025-11-16 21:40:38 +11:00
HADEON 6bdb756650
Add --needed to pacstrap to prevent re-installs 2025-11-16 21:30:47 +11:00
renovate[bot] 803ff4236e
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.5 (#3927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 12:54:53 +11:00
renovate[bot] 117b9d9a5d
chore(deps): update dependency pylint to v4.0.3 (#3923)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 11:03:01 +11:00
Benjamin Smith e6bbd05121
added some missing Spanish translations (#3925) 2025-11-15 10:40:42 +11:00
renovate[bot] 3da27570f7
chore(deps): update dependency pytest to v9.0.1 (#3922)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 10:38:53 +11:00
renovate[bot] 566dcb51c3
chore(deps): update dependency pytest to v9 (#3912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 08:36:02 +01:00
renovate[bot] b77f03e94a
chore(deps): update dependency pre-commit to v4.4.0 (#3911)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-09 13:55:03 +11:00
Luna Jernberg 811c1d95b5
Update Swedish translations for wifi (#3910)
Update Swedish translations for wifi
2025-11-09 13:54:40 +11:00
Matteo 165636b2e6
Update Italian translation (#3904)
- Translated new strings
2025-11-07 20:56:41 +01:00
summoner e7553c114c
Translation: Update hungarian base.po/base.mo (#3908)
Translate new strings
2025-11-07 15:45:32 +01:00
renovate[bot] b2fccb0b3b
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.4 (#3907)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 09:09:59 +01:00
renovate[bot] 83aab9cd4f
chore(deps): update dependency ruff to v0.14.4 (#3906)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 09:09:23 +01:00
damachine a996670a60
Fix plural form in device selection message (#3905)
Corrected the plural form in the German translation for device selection.
2025-11-07 09:03:12 +01:00
renovate[bot] 240919826f
chore(deps): update dependency pydantic to v2.12.4 (#3900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 20:23:08 +00:00
Anton Hvornum c131269c08
Bumping version to: 3.0.13 (#3903) 2025-11-06 20:02:49 +01:00
Anton Hvornum a3d85c1c58
Fixed an issue where re-writing a smaller mkinitcpio.conf than previously existed, the 'no' part at the end became a trailing 'o' causing syntax issues. (#3902) 2025-11-06 15:26:44 +01:00
Rachel bf8f72cc54
Add missing --script option in documentation for pre-programmed scripts (#3895) 2025-11-03 11:21:26 +11:00
summoner 996566291d
Translation: Update Hungarian transaltion (#3894)
* Translation: Update Hungarian transaltion

Translate new string
Fixing translation

* Translation: Fixing typos in hungarian translation

Translation: Fixing typos and wording in hungarian translation
2025-11-02 10:50:33 +01:00
Luna Jernberg 2808442f8b
Update with November 2025 Swedish translations (#3893)
Update with November 2025 Swedish translations
2025-11-01 14:44:55 +01:00
Daniel Girtler 76ab9482e9
Wifi connection menu with textual (#3879)
* Wifi connector

* Update

---------

Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-11-01 13:55:58 +01:00
Anton Hvornum 7af94c8fe5
Bumping version to: 3.0.12 (#3892) 2025-11-01 13:08:48 +01:00
Anton Hvornum c2d3daeb25
Added the new -S flag for arch-chroot (#3891)
* Added the new -S flag for arch-chroot which does: Run in systemd mode.

* Fixed some formatting issues, and removed unused *args and **kwargs for run_command()

* Formatting issue

* Formatting issue
2025-11-01 12:48:20 +01:00
Klaus Zipfel 1174800cca
Skip bootloader config check when 'No Bootloader' is selected as bootloader (#3875)
* Allow installation via TUI when 'No Bootloader' is selected as bootloader (--skip-boot)

* Update global_menu.py

Fixed ruff formatting issue

---------

Co-authored-by: Anton Hvornum <anton.feeds+github@gmail.com>
2025-11-01 11:59:24 +01:00
Daniel Girtler adbadbf606
Fix 3863 (#3880)
* Modify formatting warning text to be less scary

* Update

---------

Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-11-01 11:55:54 +01:00
Tertle950 ed67e9fd67
replace 'leafpad' with 'l3afpad' (#3890)
seemed like the most obvious replacement but maybe another one would suffice
2025-11-01 11:55:02 +01:00
Daniel Girtler bf6245815b
Save and load UKI setting (#3783)
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-11-01 11:54:31 +01:00
Daniel Girtler 4ceddacb4e
Only install applications when enabled (#3861)
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-11-01 11:54:09 +01:00
renovate[bot] 00c90d9e45
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.3 (#3888)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 21:46:16 +11:00
renovate[bot] a03e9bcdc6
chore(deps): update dependency ruff to v0.14.3 (#3887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 21:45:56 +11:00
renovate[bot] 552c7b7dd9
chore(deps): update actions/upload-artifact action to v5 (#3884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 16:21:30 +11:00
renovate[bot] 772652bc69
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.2 (#3883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 15:38:12 +11:00
renovate[bot] b0679974d5
chore(deps): update dependency ruff to v0.14.2 (#3882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-25 15:37:52 +11:00
renovate[bot] 90bf24b3ce
chore(deps): update dependency pylint to v4.0.2 (#3877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 18:22:59 +11:00
renovate[bot] e0ea072032
chore(deps): update dependency pydantic to v2.12.3 (#3874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 13:04:32 +11:00
correctmost 1fa8f2013b
Remove an unneeded Pylint suppression (#3873)
The related bug has been fixed in Pylint 4.0.1.
2025-10-18 13:04:09 +11:00
renovate[bot] 6505c073c9
chore(deps): update dependency pylint to v4.0.1 (#3864)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 17:13:45 +11:00
renovate[bot] a84a97fdc7
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.1 (#3872)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 15:31:20 +11:00
renovate[bot] 0a032b04d5
chore(deps): update dependency ruff to v0.14.1 (#3871)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 15:30:59 +11:00
renovate[bot] 46380f5da3
chore(deps): update dependency pydantic to v2.12.2 (#3869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-15 07:34:22 +11:00
renovate[bot] 6c43a8c6d3
chore(deps): update dependency pydantic to v2.12.1 (#3868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 16:34:18 +11:00
Vasiliy Stelmachenok 53e8cb3a8c
Do not install Btrfs module and binary in mkinitcpio (#3865)
* Do not install Btrfs module and binary in mkinitcpio

This is what btrfs hook already does.

Signed-off-by: Vasiliy Stelmachenok <ventureo@cachyos.org>

* Remove unused properties from FilesystemType

They were only needed for Btrfs

Signed-off-by: Vasiliy Stelmachenok <ventureo@cachyos.org>

---------

Signed-off-by: Vasiliy Stelmachenok <ventureo@cachyos.org>
2025-10-14 07:33:45 +11:00
Vasiliy Stelmachenok daab8e6ff6
Do not re-order amdgpu and radeon modules in mkinitcpio (#3866)
This is not necessary as kms hook for mkinitcpio already takes care
of adding amdgpu and radeon modules.

Signed-off-by: Vasiliy Stelmachenok <ventureo@cachyos.org>
2025-10-13 09:12:41 +00:00
correctmost 8fb83a7beb
Upgrade Pylint to 4.0.0 (#3867)
This commit also removes the pylint-pydantic plug-in, which is not
yet compatible with Pylint v4.
2025-10-13 09:03:00 +00:00
renovate[bot] b0b87400c2
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.14.0 (#3859)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 07:58:29 +00:00
renovate[bot] 97790145c4
chore(deps): update dependency ruff to v0.14.0 (#3858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 16:48:13 +11:00
renovate[bot] d7f74b4b32
chore(deps): update dependency pydantic to v2.12.0 (#3856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 16:47:57 +11:00
Muhammadyoqub 472ffd4e6a
Add Uzbek (uz) language support (#3854)
* Add Uzbek (uz) language support

* Add Uzbek (uz) language support
2025-10-07 21:11:42 +11:00
Pierre Ambroise 68d8af3df3
Update french translation (#3853)
* Update french translation

* Add mo file
2025-10-07 09:56:48 +11:00
renovate[bot] 88266f5c74
chore(deps): update dependency pylint to v3.3.9 (#3851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 09:19:56 +11:00
renovate[bot] a3e219a2e4
chore(deps): update dependency pydantic to v2.11.10 (#3850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-05 09:31:15 +11:00
renovate[bot] 5777880d18
chore(deps): update dependency ruff to v0.13.3 (#3848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 10:25:20 +00:00
renovate[bot] c8429aee23
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.13.3 (#3849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 10:25:01 +00:00
damachine 60e70f507f
Fix German translations for consistency and clarity (#3844)
* Fix German translations for consistency and clarity

Corrected capitalization and punctuation in German translations.

* Update German translations for disk configuration messages

sounds better

* Update German translations for clarity and consistency

* Update German translations in base.po

* Update German translation for object selection message

much more understandable

* Update German translations in base.po

friendlier

* Refine German translations in base.po

SORRY for the many changes. I only want the best.

* Update German translation for Btrfs subvolumes

* Update German translation for btrfs subvolume message

correct translation o)
2025-10-01 07:25:11 +10:00
Valerii b03948bbfe
Better Ukrainian v1.3.4 (#3843) 2025-10-01 07:24:41 +10:00
damachine 21f9972040
Fix typo in German translation for network interface (#3842)
sorry. another one has sneaked in.
2025-09-30 13:04:02 +10:00
damachine e0ce0bdd45
Fix typo in German translation for bootable partition (#3841)
small typo fix o)
2025-09-30 11:56:43 +10:00
summoner c86b0be79f
Translation: Update hu_HU.po (#3839)
Translate new strings
Fixing translation
2025-09-28 17:50:02 +10:00
renovate[bot] 853309d311
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.13.2 (#3838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 17:49:24 +10:00
renovate[bot] 2f1b8cd692
chore(deps): update dependency ruff to v0.13.2 (#3837)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 17:49:06 +10:00
Luna Jernberg e1f7a314a7
Bumping version to: 3.0.11 (archlinux#3835) (Swedish Translation) (#3836)
* Update base.po (Swedish)

Update Swedish Translation

* Update base.po (Swedish)

Updated Swedish translation
Uppdaterad svensk översättning

* Bumping version to: 3.0.11 (archlinux#3835)

Bumping version to: 3.0.11 (archlinux#3835)
2025-09-25 17:20:43 +10:00
Anton Hvornum ebeb4c7daa
Bumping version to: 3.0.11 (#3835) 2025-09-24 23:51:08 +02:00
Matteo 88753b1f40
Update Italian translation (#3832)
Translated new strings
2025-09-24 07:14:37 +10:00
utuhiro78 ca85f39b71
Update Japanese translation (#3831) 2025-09-24 07:14:20 +10:00
renovate[bot] 5f96390719
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.18.2 (#3823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 20:38:59 +10:00
renovate[bot] fceee34dfa
chore(deps): update dependency ruff to v0.13.1 (#3810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 17:43:37 +10:00
renovate[bot] 49ead6fa38
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.13.1 (#3811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 16:44:30 +10:00
Bruno Rosa 792db9e8b8
quick fix to run archinstall and enable systemd services (#3815) 2025-09-19 15:52:30 +10:00
renovate[bot] df3b5d7593
chore(deps): update dependency mypy to v1.18.2 (#3816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 15:45:43 +10:00
Daniel Girtler d256e70480
Fix 3785 - F2FS parameters require 'extra_attr' (#3796)
Co-authored-by: Daniel Girtler <dgirtler@atlassian.com>
2025-09-17 10:32:04 +02:00
Monochrome de05dcb7e0
changed version of cryptography package to 45.0.7 (#3795)
Co-authored-by: QuotientParadoxx <117741113+QuotientParadox@users.noreply.github.com>
2025-09-16 10:25:43 +00:00
Rémy Marquis 9ddd41da56
Remove outdated link to video demo installer (#3790) 2025-09-16 10:25:24 +00:00
renovate[bot] cec68b6e9b
chore(deps): update dependency pydantic to v2.11.9 (#3791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 08:38:27 +10:00
renovate[bot] c60d9bd232
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.18.1 (#3792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 08:37:45 +10:00
renovate[bot] ad02f5bffc
chore(deps): update dependency mypy to v1.18.1 (#3788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 07:29:10 +10:00
renovate[bot] 3c416943e2
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.13.0 (#3787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 07:28:46 +10:00
renovate[bot] e846a0ec58
chore(deps): update dependency ruff to v0.13.0 (#3786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 07:28:30 +10:00
BlocksumoGuys d40d77ce25
Finalized French Translation (with improvments) (#3784)
The french translation is hopefully finished, I also improved in some sectors of the translations
2025-09-09 17:03:48 +10:00
veprogames de50a31609
Fundamentally improve German translations (#3780)
* Get rid of all warnings that poedit displayed

Bring translations closer to the English original, also removing some extraneous information. Get rid of all warnings poedit displayed. Correct translation that didn't match at all.

* Fix locales_generator.sh detection

xgettext was not recognizing tr() invocations. Following https://stackoverflow.com/a/11901925 fixed the issue

* Add more German translations

Improve consistency with some translations. Add translations for messages that were just detected in the previous commit. Add translations for Graphics Drivers

* Add more translations

Look for untranslated strings in the source files and add make them recognized by gettext

* Improve conistency of German translations and correct typos

* formatting

* Remove translations from enum members

* More translation tweaks
2025-09-09 17:02:24 +10:00
Dee 1ef52f56cb
gracefully return "undefined" if DMI is not in sysfs (#3771)
* gracefully return "undefined" if DMI is not in sysfs

fixes #3770, in theory

* None works too and is consistent with other behaviour

* None doesn't *just* work
2025-09-08 10:51:28 +00:00
BlocksumoGuys bca3f4b660
Update French Language inside of base.po (#3782)
Some error found inside the base.po file
2025-09-08 19:06:20 +10:00
renovate[bot] 9bb7ced10e
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.12 (#3779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 21:34:33 +10:00
renovate[bot] b8baf096dd
chore(deps): update dependency ruff to v0.12.12 (#3778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 21:34:11 +10:00
renovate[bot] 0ad513a8e5
chore(deps): update dependency pytest to v8.4.2 (#3777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 21:33:54 +10:00
renovate[bot] ed8b6ac045
chore(deps): update actions/setup-python action to v6 (#3776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 18:39:12 +10:00
veprogames fd2c20a900
Add missing German translations (#3774) 2025-09-04 16:51:11 +10:00
Anton Hvornum f87ece0b95
Bumping version to: 3.0.10 (#3773) 2025-09-02 12:33:50 +02:00
renovate[bot] 4949bb4c2d
chore(deps): update dependency ruff to v0.12.11 (#3767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-30 19:02:21 +10:00
renovate[bot] 5208dfd539
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.11 (#3768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-30 19:02:05 +10:00
Daniel Girtler 6a228aa70a
Handle empty menu when focusing item (#3742) 2025-08-27 10:30:19 +02:00
Daniel Girtler ea8d64a6d3
Fix upgrade version header (#3738)
* Fix header new version display

* Update
2025-08-27 10:29:23 +02:00
Daniel Girtler d342c50aec
Remove beta label from LVM (#3737) 2025-08-27 10:28:45 +02:00
Daniel Girtler e4e30b31d4
Disable btrfs snapshots when no subvolumes defined (#3736) 2025-08-27 10:28:17 +02:00
Daniel Girtler 663e46f86e
Remove unattended script (#3733) 2025-08-27 10:27:25 +02:00
Daniel Girtler 7d94210cd3
Fix parsing of systemd package version (#3732) 2025-08-27 10:23:03 +02:00
Daniel Girtler a4324ec5f1
Setup grub-btrfs correctly for timeshift (#3728) 2025-08-27 10:21:47 +02:00
Luna Jernberg f0f882f68b
Update base.po (Swedish) (#3758)
Update Swedish Translation
2025-08-26 09:45:44 +10:00
renovate[bot] 08b6b617a7
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.10 (#3755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 08:24:30 +10:00
renovate[bot] d09d4a3acd
chore(deps): update dependency ruff to v0.12.10 (#3754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 08:23:22 +10:00
Valerii af54095405
Better Ukrainian v.1.3.3 (#3749)
* Better Ukrainian v.1.3.2

Now, the translation is complete.

* Better Ukrainian v1.3.3
2025-08-22 07:56:34 +10:00
Guillaume BOEHM 0cebaa00f7
Fix documentations and samples mentioning `custom-commands` instead of `custom_commands` (#3745) 2025-08-16 11:59:18 +10:00
renovate[bot] 6910b7a872
fix(deps): update dependency ruff to v0.12.9 (#3740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 12:33:08 +10:00
renovate[bot] 0394eba63c
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.9 (#3739)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 12:32:50 +10:00
renovate[bot] 982f10b9d7
chore(deps): update astral-sh/ruff-action action to v3.5.1 (#3730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 07:55:14 +10:00
renovate[bot] bf808110f1
chore(deps): update actions/checkout action to v5 (#3727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-12 11:18:09 +00:00
renovate[bot] c9d28e0cc8
chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v6 (#3721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 06:08:53 +00:00
renovate[bot] 5bb21b69ea
fix(deps): update dependency pylint to v3.3.8 (#3718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 06:08:26 +00:00
neko_0xff d5b8c66d3b
Update base.po (#3723) 2025-08-10 06:08:09 +00:00
Guilherme Licodiedoff 05c25f10ea
Improve pt_BR translation flow (#3722) 2025-08-10 06:07:34 +00:00
renovate[bot] f6697f22ee
fix(deps): update dependency pre-commit to v4.3.0 (#3720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 06:06:54 +00:00
Odyssey c6674ee8e4
Update catalan locales (#3716) 2025-08-10 06:05:50 +00:00
renovate[bot] c172c78012
fix(deps): update dependency ruff to v0.12.8 (#3715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 06:05:30 +00:00
renovate[bot] da8c16400f
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.8 (#3714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 06:05:13 +00:00
correctmost b0ede3c165
Fix a type annotation for list_available_packages (#3711)
The repositories parameter is a variable-length tuple instead of a
one-element tuple.
2025-08-06 07:02:01 +00:00
Andreamp0 5e96448f37
Fixing 3 lines in the italian translation (#3710) 2025-08-05 12:55:30 +00:00
EK 4c17b75134
move additional packages down (#3709) 2025-08-04 19:45:38 +02:00
renovate[bot] 2f05481329
fix(deps): update dependency mypy to v1.17.1 (#3704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 11:49:08 +00:00
Hugo Carvalho 1e841a1816
Translation: Update pt.po (#3702) 2025-07-31 11:48:38 +00:00
renovate[bot] d09a512946
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.17.1 (#3703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 11:48:17 +00:00
summoner 6cca6f9866
Translation: Update HU.po (#3701)
Fix translation wording
2025-07-31 06:59:25 +00:00
Daniel Girtler ab1793a4f4
Add version to test (#3699) 2025-07-31 04:52:09 +00:00
summoner 11e33ed9f3
Translation: Update Hu.po (#3698)
Translate new strings
Fix sentences
2025-07-30 17:34:56 +02:00
Anton Hvornum 0cd3f2ea26
Bumping version to: 3.0.9 (#3697) 2025-07-30 10:22:31 +02:00
renovate[bot] 1cd6792bba
fix(deps): update dependency ruff to v0.12.7 (#3696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 07:05:52 +00:00
renovate[bot] 34489d4834
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.7 (#3695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 07:05:30 +00:00
Anton Hvornum 3607029d59
Reverted profiles and application installation order in order to not break DE's vs audioserver setups (#3694) 2025-07-30 08:16:36 +02:00
Daniel Girtler 1ba9d0371a
Correctly populate version in config (#3693) 2025-07-29 20:30:17 +00:00
Daniel Girtler 91609c16b5
Fix circular bootloader dependency (#3686) 2025-07-29 19:48:23 +02:00
renovate[bot] dd5a86165c
fix(deps): update dependency ruff to v0.12.5 (#3682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 18:21:42 +00:00
renovate[bot] d799812f63
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.5 (#3681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 18:21:15 +00:00
Simon Skoczylas 436a28b34c
Update documentation to also mention the --config-url option (#3683)
* Add missing config-url option

* Update example in documentation
2025-07-24 17:19:27 +02:00
VENOIRE ccebb6882e
Fix boot detection to use PartitionFlag.BOOT instead of mountpoint (#3659)
Co-authored-by: VENOIRE <venoire@protonmail.com>
Co-authored-by: Torxed <torxed@archlinux.org>
2025-07-24 16:11:18 +02:00
correctmost b77a43b201
Remove unneeded None annotation from Bootloader.get_default (#3680) 2025-07-23 20:00:21 +02:00
Anton Hvornum 5355c7141a
Add --skip-boot to allow for bypassing installation of a bootloader (#3677)
* Adding the option to skip boot loader

* Fixed a variable complaint

* Fixed ruff and mypy

* Fixed mypy

* Fixed mypy

* Fixed import recursion

* Fixed ruff

* Fixed circular imports

* Fixed ruff

* Hiding the menu option for bootloader when --skip-boot is given. Still setting it default to None to avoid it sneaking into the config file or being set behind the scenes causing if statements to trigger.

* Created an Enum None type for Bootloader called NO_BOOTLOADER

* Fixed ruff

* Spelling error

* Hiding NO_BOOTLOADER if --skip-boot is omitted

* Fixed TUI error when bootloader was missing

* Fixed mypy complaints

* Fixed ruff complaint

* Made the Bootloader option visible during --skip-boot and set the default value to NO_BOOTLOADER when --skip-boot is present
2025-07-23 16:52:01 +02:00
Luiz Felipe ee6dcbe2b2
Improve PT‑BR translation and adjust punctuation (#3679)
Co-authored-by: LuizWT <xluiz452@gmail.com>
2025-07-23 11:10:02 +00:00
Daniel Girtler 3e99cfbba7
Move users menu into authentication submenu (#3678)
* Move users menu into authentication submenu

* Tests

* Update

* Update
2025-07-22 08:57:39 +02:00
Daniel Girtler 725c3fed09
Rename network_configuration -> network (#3641) 2025-07-21 19:26:15 +00:00
renovate[bot] b0908b88c5
fix(deps): update dependency ruff to v0.12.4 (#3674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-19 11:48:00 +02:00
renovate[bot] 1da69cec18
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.4 (#3673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-19 00:15:26 +00:00
Daniel Girtler 1e058df92b
Fix imports (#3675) 2025-07-18 21:59:32 +00:00
Daniel Girtler e0c3bb3869
Move root password menu into authentication menu (#3650) 2025-07-18 05:53:31 +00:00
correctmost 8736926fb2
Update mypy to 1.17.0 (#3671)
This commit also removes a type ignore comment that is no longer
needed after python/mypy#18976.
2025-07-17 12:37:06 +10:00
utuhiro78 f059d897fb
Update Japanese translation (#3670) 2025-07-16 10:16:55 +10:00
renovate[bot] 9f83d67c9e
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.17.0 (#3669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 10:16:27 +10:00
Daniel Girtler 231530d5f9
Handle no U2F device available (#3648) 2025-07-15 09:35:54 +02:00
Daniel Girtler 47f4e63006
Rename profile_model -> profile (#3640) 2025-07-15 09:30:15 +02:00
Daniel Girtler 96aaf5b6ac
Rename module device_model -> device (#3639) 2025-07-15 09:29:58 +02:00
renovate[bot] 703c9548c0
chore(deps): update astral-sh/ruff-action action to v3.5.0 (#3667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 08:06:55 +10:00
renovate[bot] b73aa79415
fix(deps): update dependency ruff to v0.12.3 (#3664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-12 12:22:35 +10:00
renovate[bot] 725b95c5a6
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.3 (#3663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-12 12:22:04 +10:00
Muhammad Salman eb5512f5cd
Install inotify-tools when using grub with btrfs config (#3656) 2025-07-08 20:12:57 +10:00
renovate[bot] 9b0b5e8af2
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.2 (#3652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-05 08:32:21 +10:00
renovate[bot] b0038eb277
fix(deps): update dependency ruff to v0.12.2 (#3653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 18:49:11 +10:00
Matteo 30f0693ce7
Update Italian translation (#3651) 2025-07-03 08:42:30 +10:00
correctmost 3652dbd578
Replace an Any instance in U2FLoginConfiguration.parse_arg (#3649) 2025-07-01 08:54:46 +10:00
correctmost 2bb98bc06e
Remove an unused member variable from AuthenticationHandler (#3644) 2025-07-01 07:55:04 +10:00
correctmost 2072c29745
Use union syntax instead of typing.Optional (#3643) 2025-07-01 07:54:41 +10:00
correctmost c22c6c7399
Remove unneeded container code from ruff format workflow (#3642) 2025-07-01 07:54:09 +10:00
Daniel Girtler 55a383764f
Add support for U2F authentication (#3638)
* Add U2F login support

* Update

* Update

* Update

* Update
2025-06-30 21:51:45 +10:00
Haouari haitam Kouider b3b00aa00f
Add interface to change LUKS iteration time (#3634)
* Added User Interface to change iteration time for LUKS encryption

* removing unneessary try catch and imports

* used the same constant in luks.py file

* fixed issue with error firing in default value

* fixed ruf preview warnings

* preview even if its default value. (iter_time)

* check encryption type is not non before showing iter_time

* using _real_input with input_vp instead of _current_text

* proper check for enc_type

* added Interation time to outer menu preview

* removed (ms) from title. so that we don't need to translate "Iteration time" and "iteration time (ms)".

* a comment slipped in. this was not supposed to be in this pull

* fixed comparison str with EncryptionType
2025-06-30 11:11:24 +10:00
Daniel Girtler 19459731ad
Make type field in lsblk optional (#3633) 2025-06-27 12:31:04 +02:00
Anton Hvornum 9dd92321a5
Added temporary hold on bootctl's --variables=BOOL usage, as it's notin systemd main yet (#3625)
* Added temporary hold on bootctl's --variables=BOOL usage, as it's not in systemd main yet

* Fixed ruff format check

* Fixed mypy type check issue

* Spelling error

* Fixed flake8 complaint
2025-06-27 12:29:35 +02:00
renovate[bot] 9d0e1e5576
fix(deps): update dependency ruff to v0.12.1 (#3632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 11:53:03 +10:00
renovate[bot] 8cb899ef39
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.1 (#3631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 11:52:47 +10:00
correctmost 572b4b6c29
Remove a ruff check auto-fix from the ruff format workflow (#3628)
Running an auto-fix in CI can cause the format check to pass, even
though the same format check fails locally.
2025-06-24 21:29:06 +10:00
correctmost 85f83214f7
Fix ruff format for local builds (#3626) 2025-06-24 13:30:04 +10:00
Anton Hvornum 63e559e502
Changed quotation marks (#3624) 2025-06-22 17:10:28 +02:00
DarkXero 677f4349be
Add missing xdg-user-dirs (#3484)
* Update cosmic.py

* Reverted incorrect Python logic surrounding the greeter type.

---------

Co-authored-by: Torxed <torxed@archlinux.org>
2025-06-22 15:17:25 +02:00
summoner001 7f189727a2
Update hungarian translation (#3622)
fix wording
2025-06-22 14:17:45 +02:00
Anton Hvornum 7fdfede270
Fixed ruff quotation marks (#3623) 2025-06-22 14:16:27 +02:00
Korbin Bickel 46550221d1
Update bootctl command for new --variables= option (#3396)
Co-authored-by: Anton Hvornum <torxed@archlinux.org>
2025-06-22 13:55:38 +02:00
Daniel Girtler 43963a1d8f
Fix script docs (#3613) 2025-06-22 12:47:00 +02:00
renovate[bot] fbbc3edac2
fix(deps): update dependency flake8 to v7.3.0 (#3618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-21 08:19:45 +10:00
renovate[bot] 526eb3fb18
chore(deps): update pre-commit hook pycqa/flake8 to v7.3.0 (#3617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-21 08:05:49 +10:00
Daniel Girtler 9769786ad6
Fix app config (#3614) 2025-06-20 23:59:10 +02:00
Daniel Girtler 77558e2be5
Fix 3598 - Utilize script field from config when present (#3611)
* Fix 3598 - Utilize script field from config when present

* Update

* Update
2025-06-20 15:58:14 +10:00
Daniel Girtler 37b3985625
Move audio configuration to application config (#3612) 2025-06-20 07:27:23 +10:00
renovate[bot] 63848f645f
fix(deps): update dependency pytest to v8.4.1 (#3608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 22:06:02 +10:00
Daniel Girtler 27f53717b0
Add bluetooth support (#3604)
* Add bluetooth option
2025-06-18 09:57:54 +02:00
renovate[bot] f0cb3ad77a
fix(deps): update dependency ruff to v0.12.0 (#3607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 07:38:42 +10:00
renovate[bot] fa5f9cfaf3
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.12.0 (#3606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 07:38:24 +10:00
renovate[bot] bf8379a2aa
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.16.1 (#3605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 07:36:33 +10:00
Screamnox 973f4af1f5
fix(lvm): align offset to PE size to avoid LV allocation failure (#3603) 2025-06-17 22:26:16 +10:00
renovate[bot] 4d0a6ceb62
fix(deps): update dependency mypy to v1.16.1 (#3602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 07:38:35 +10:00
Serdar Sağlam dd75371fc1
Turkish language current (#3597)
It was a very bad and wrong translation, but it is good now.
2025-06-16 09:53:20 +10:00
renovate[bot] c800a0357d
fix(deps): update dependency pydantic to v2.11.7 (#3595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-14 23:13:43 +10:00
renovate[bot] aa5a13b732
fix(deps): update dependency pydantic to v2.11.6 (#3594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-13 22:46:46 +10:00
Anton Hvornum 867fc7c691
Bumping version to: 3.0.8 (#3593) 2025-06-12 21:27:28 +02:00
Anton Hvornum 2038e68560
Removed xf86-video-vmware as it's been dropped: https://gitlab.archlinux.org/archlinux/packaging/packages/xf86-video-vmware/-/issues/3 (#3590) 2025-06-12 08:48:21 +02:00
Daniel Girtler 12c8a5c62e
Mount partitions in minimal script before starting installation (#3585) 2025-06-12 07:14:54 +10:00
Daniel Girtler 063b9643b4
Add new version indicator in the title of the main menu (#3587)
* Add new version indicator in the title of the main menu

* Update
2025-06-12 07:14:30 +10:00
Daniel Girtler 7b3b7c9ebf
Fix recursive call in scripts (#3586) 2025-06-11 19:38:28 +02:00
Anton Hvornum a91a922ef4
Spelling error on extras package (#3582) 2025-06-09 13:27:24 +02:00
renovate[bot] d0f1fdf4b4
fix(deps): update dependency ruff to v0.11.13 (#3578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 07:33:18 +10:00
renovate[bot] ee94cd9d4f
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.13 (#3577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 07:32:57 +10:00
correctmost 1e7a4831ae
Use a generic instead of Any in MenuHelper (#3574)
This commit also removes an unused _get_table_header method.
2025-06-04 11:55:07 +10:00
correctmost 2843d01600
Enable stricter mypy definition checks in archinstall/lib/ (#3573) 2025-06-04 07:30:14 +10:00
correctmost 557e1cac0d
Honor the sort_items parameter on MenuItemGroup.from_enum (#3572) 2025-06-03 23:17:42 +10:00
correctmost fafb180ffe
Remove unused arguments from internal functions (#3571)
This makes it easier to run Pylint's unused-argument checks on the
codebase.
2025-06-03 23:17:10 +10:00
renovate[bot] fe9b853d25
fix(deps): update dependency pytest to v8.4.0 (#3570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 08:15:27 +10:00
Daniel Girtler 18ef716b0f
Add sync after installation completed (#3569) 2025-06-02 13:50:34 +02:00
Daniel Girtler 92b384d1d3
Fix restore edit content on help menu close (#3568)
* Fix restore edit content on help menu close

* Update
2025-06-02 10:17:17 +02:00
correctmost fef97bed32
Fix return values used with a Textbox.edit callback in curses (#3566) 2025-06-01 11:43:01 +02:00
correctmost a580da2bbd
Enable the bad-exit-annotation ruff rule and fix related warnings (#3564) 2025-06-01 11:41:34 +02:00
Anton Hvornum 55fd59d94e
Moved python-cryptography from makedepends to depends (#3558) 2025-05-31 13:19:37 +02:00
codefiles dd686b291c
Use PEP 639 license expression (#3556) 2025-05-31 08:25:29 +02:00
correctmost a1b83ad29f
Remove unused _DeferredTranslation methods and references (#3557) 2025-05-31 16:04:23 +10:00
correctmost e245a11463
Remove return value check for Installer.minimal_installation (#3555)
The minimal_installation method always returns None.
2025-05-31 08:20:54 +10:00
renovate[bot] 48c3f6367b
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.16.0 (#3551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 23:33:58 +02:00
lubuoren 47e6d82ee5
Update installer.py (#3540)
Fix the bug of installation failure caused by incorrect service name
2025-05-30 17:04:04 +02:00
codefiles 1dccfe6c33
Rework install log (#3550) 2025-05-30 23:08:02 +10:00
correctmost a5d995b546
Remove underscore from _FrameDim because it's used externally (#3547)
This commit fixes a reportUnusedClass warning from Pyright.
2025-05-30 18:42:00 +10:00
codefiles 16405ab435
Refactor command logs (#3549) 2025-05-30 18:39:20 +10:00
correctmost c2ea6ffe9c
Add type annotations to global storage dictionary (#3548)
This will help catch issues like #3530.
2025-05-30 09:18:17 +02:00
correctmost b689656547
Remove some unnecessary None checks from desktop profile code (#3546)
This commit fixes some warnings reported by Pyright's
reportUnnecessaryComparison checker.
2025-05-30 09:12:49 +02:00
correctmost bd36c0c5d3
Remove workaround for mypy 1.15.0 crash (#3545)
This reverts commit ae38e92100.
2025-05-30 09:12:05 +02:00
correctmost 1b39c7dbad
Remove strict_bytes from the mypy config (#3544)
In mypy 1.16.0, strict mode automatically enables strict_bytes
checks.
2025-05-30 07:32:25 +02:00
Daniel Girtler c2443e8902
Reference guided.py script instead of symlink from README (#3543) 2025-05-30 07:31:54 +02:00
renovate[bot] 5805ea4a3f
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.12 (#3538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 21:36:00 +02:00
renovate[bot] 7b9a658c7b
fix(deps): update dependency ruff to v0.11.12 (#3539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 21:35:35 +02:00
renovate[bot] 46f18baca7
fix(deps): update dependency mypy to v1.16.0 (#3541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 21:33:34 +02:00
Daniel Girtler 8bed035e21
Fix 3530 (#3535)
* Fix 3530

* Update
2025-05-29 13:54:28 +02:00
Daniel Girtler d4b16cb406
Fix 3534 (#3537) 2025-05-29 13:53:22 +02:00
Daniel Girtler 3359779594
Fix 3626 (#3536) 2025-05-29 13:52:34 +02:00
codefiles 7316b56274
Fix empty username (#3533) 2025-05-29 12:17:18 +10:00
codefiles e6a9105df4
Fix empty password (#3531) 2025-05-29 12:13:07 +10:00
correctmost 3da78fcb49
Enable truthy-bool checks in mypy and fix related warnings (#3528) 2025-05-29 12:11:32 +10:00
codefiles b2b36a2cef
Refactor default Btrfs subvolumes (#3525) 2025-05-28 22:45:56 +10:00
codefiles 728fee4a7c
Fix root partition device mapper name (#3524) 2025-05-28 22:02:00 +10:00
correctmost 426650a3c4
Fix invalid-annotation warnings reported by Pyrefly (#3523) 2025-05-28 22:00:52 +10:00
correctmost 26c6812827
Remove extraneous None type annotations (#3522) 2025-05-28 21:52:43 +10:00
Daniel Girtler 52face9254
Fix 3513 (#3520) 2025-05-28 20:47:56 +10:00
correctmost c2f4d92fd9
Enable unreachable code checks in lib/ with mypy (#3521) 2025-05-28 20:47:40 +10:00
Anton Hvornum 2cf7c85728
Bumping version to: 3.0.7 (#3519) 2025-05-28 11:15:59 +02:00
correctmost 5c8721a3a8
Enable unreachable code checks in tui/ with mypy (#3518) 2025-05-28 16:23:06 +10:00
Farhan Ghani 50747a5a71
Updated translation and fixed errors in last translation. (#3515)
* still updating

* Updated urdu translation

Because it is very hard to display urdu fonts in consol. I changed urdu
writing to latin script which is also known as "Roman Urdu". For more
information check out https://en.wikipedia.org/wiki/Roman_Urdu.

* Updated errors and translation

As it is mentioned in #3463 the errors has been rectified and more
translation is added.
2025-05-28 16:19:40 +10:00
correctmost 5aa1c90b12
Enable mypy's show_traceback option in pyproject.toml (#3514)
This makes it easier to debug mypy crashes when running 'mypy'
in the project directory.
2025-05-28 16:19:03 +10:00
codefiles df8cb4fb39
Fix home partition device mapper name (#3516) 2025-05-28 07:27:07 +02:00
Luiz Felipe 7a455cb625
adjust PT-BR translations and remove fuzzy markers (#3511)
* adjust PT-BR translations and remove fuzzy markers

* Add "Luiz Felipe" to pt-BR translators list in archinstall
2025-05-27 07:51:47 +02:00
codefiles c8f1f78679
Remove duplicate LUKS unlock check (#3510) 2025-05-27 13:08:45 +10:00
codefiles 2623039fff
luks: rework is_unlocked() (#3509) 2025-05-27 09:36:23 +10:00
renovate[bot] 9f110849e6
chore(deps): update astral-sh/ruff-action action to v3.4.0 (#3508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 09:35:35 +10:00
Daniel Girtler 8c12d15a41
Fix disk encryption emnu (#3506) 2025-05-26 21:21:30 +10:00
Anton Hvornum 6c7260fa33
Added a more humane error message on no initial network (#3501)
* Added a more humane error message on no initial network

* Fixed ruff complaints
2025-05-25 13:30:47 +02:00
Daniel Girtler 6d1a450440
Fix 2304 (#3503) 2025-05-25 21:25:15 +10:00
Daniel Girtler cdb1debe2e
Move disk encryption into disk config menu (#3502) 2025-05-25 21:23:37 +10:00
Daniel Girtler 5a54902935
Add support for Btrfs snapshots (#3500)
* Add btrfs snapshot support

* Update

* Update

* Update
2025-05-25 17:22:41 +10:00
Daniel Girtler d3f32f308c
Update ruff formatter (#3496)
* Update ruff formatter

* Update
2025-05-24 07:58:42 +00:00
Omrifo4 9af23218c4
Generate empty string on empty custom_servers config (#3498) 2025-05-24 09:11:45 +10:00
Daniel Girtler 4c065c3698
Symlink example files to actual scripts (#3495) 2025-05-23 13:09:42 +02:00
renovate[bot] 1308897d79
fix(deps): update dependency ruff to v0.11.11 (#3494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-23 19:15:59 +10:00
renovate[bot] aef3fd6690
fix(deps): update dependency pydantic to v2.11.5 (#3493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-23 19:15:43 +10:00
renovate[bot] ec5a7e6264
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.11 (#3492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-23 19:15:28 +10:00
Daniel Girtler 3e2f792280
Fix qemu command to boot image (#3491) 2025-05-22 11:25:29 +02:00
Anton Hvornum c9b0c9f7b5
Fix pypi auto release (#3490)
* Updated pypi release by also removing the pyparted hardcoded github version link
2025-05-22 00:22:22 +02:00
Anton Hvornum 79a4c7c0ef
Bumping version to: 3.0.6 (#3488) 2025-05-21 19:34:02 +02:00
Anton Hvornum 5a4773cdd3
Added --skip-wkd to skip waiting for the arch linux keyring wkd sync (#3483)
* Added --skip-wkd to skip waiting for the arch linux keyring wkd sync

* Package spelling error

* Forgot to add argument to Arguments()

* Added missing --skip-wkd arg to arg tester

* Corrected help text for --skip-wkd
2025-05-20 11:38:40 +02:00
correctmost 3e5f879d52
Remove remaining bare _ translation references (#3485)
This helps simplify the linter configs and improves compatibility
with type checkers.
2025-05-20 18:44:25 +10:00
correctmost a945606834
Add some missing type annotations for return values (#3486) 2025-05-20 07:37:35 +10:00
Anton Hvornum 97eeef8c76
Fixed missing archlinux container usage (#3480) 2025-05-19 16:31:40 +02:00
Daniel Girtler 57bd2451e9
Simplify translating of strings (#3479)
* Simplify translation fucntion

* Simplify translation fucntion
2025-05-19 22:33:02 +10:00
Daniel Girtler 790a7a2be9
Fix 3474 (#3476)
* Fix 3474 - Parse mountpoint correctly

* Fix 3474 - Parse mountpoint correctly

* Update
2025-05-19 10:59:42 +02:00
Daniel Girtler f7f6b27a8a
Fix refression for sudoers file (#3475) 2025-05-19 17:00:57 +10:00
Luna Jernberg 363a96a0a2
Update base.po - Swedish translation for 3.0.5 (#3471)
Swedish translation for 3.0.5
2025-05-16 22:00:42 +10:00
renovate[bot] f2136e55ed
fix(deps): update dependency ruff to v0.11.10 (#3470)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 07:36:30 +10:00
renovate[bot] 7decef1dc3
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.10 (#3469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 07:36:12 +10:00
Daniel Girtler c67ac97f18
Ruff formatting (#3459)
* Update

* Update
2025-05-15 14:26:41 +02:00
Anton Hvornum f71e91c85e
Bumping version to: 3.0.5 (#3463) 2025-05-15 08:50:34 +02:00
Anton Hvornum 83a45e8f9f
Fixes --silent in conjuction with --config-url (#3465) 2025-05-14 20:30:38 +02:00
Farhan Ghani b062271b2c
Updated urdu translation (#3462)
* Updated urdu translation

Because it is very hard to display urdu fonts in consol. I changed urdu
writing to latin script which is also known as "Roman Urdu". For more
information check out https://en.wikipedia.org/wiki/Roman_Urdu.

* More translation

Some messages were not translated.
2025-05-14 11:58:18 +10:00
walken ffb9697499
Czech localization update (#3461) 2025-05-13 08:36:50 +10:00
Daniel Girtler e61b966575
Ruff use tabs for formatting (#3458) 2025-05-12 21:07:55 +10:00
Daniel Girtler e18c00c4d9
Convert bytes to str (#3457) 2025-05-12 21:07:36 +10:00
Valerii fd9f2893e6
UWSM for Hyprland (#3455)
UWSM
2025-05-12 08:10:36 +10:00
Lena a291857bdc
Update Polish translation (#3454) 2025-05-12 08:09:25 +10:00
renovate[bot] d3f40a217e
fix(deps): update dependency ruff to v0.11.9 (#3453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 13:59:04 +10:00
renovate[bot] 9facf6811c
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.9 (#3452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 13:58:46 +10:00
utuhiro78 3dccbc1247
Update Japanese translation (#3451) 2025-05-10 13:58:24 +10:00
correctmost 5cd5d714e9
Disable the loop-iterator-mutation rule in ruff (#3450)
This allows ruff check --preview to succeed again.
2025-05-09 07:28:56 +10:00
Odyssey 583945028f
Update catalan locales (#3447) 2025-05-09 07:27:54 +10:00
codefiles 2e4603e464
Fix btrfs subvolume creation (#3446) 2025-05-08 15:39:43 +02:00
Valerii 1228a0c75e
Better Ukrainian v1.3.1 compiled .mo file (#3445) 2025-05-08 21:29:35 +10:00
renovate[bot] 2bf2b665e6
chore(deps): update astral-sh/ruff-action action to v3.3.1 (#3444)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 09:00:38 +10:00
ncdm-stldr 4dc7cb9ee6
[fix] Sway: dmenu is no longer the default application launcher. It was changed for wmenu. (#3442)
I couldn't start the application launcher with mod+d after a fresh installation using archinstall, where I selected sway as a desktop.
The reason is that the default application launcher for sway changed (see ab9b164e52 and b44015578a).
The fix is simply to install this new application launcher wmenu instead of dmenu.
2025-05-08 09:00:10 +10:00
Valerii a3c1e20ea0
Better Ukrainian v1.3 (#3441)
* Add files via upload

* Add files via upload
2025-05-06 17:56:40 +10:00
summoner001 6f2fe45ce7
Update hungarian translation (#3440)
Translate new strings
2025-05-06 17:56:20 +10:00
Valerii 3adfc21c69
Add files via upload (#3439) 2025-05-06 09:57:34 +10:00
Daniel Girtler 9a98a1500b
Revert wayfire (#3432) 2025-05-05 10:08:42 +02:00
correctmost 781760a157
Break an import cycle between lib/luks and lib/disk/device_handler (#3438) 2025-05-05 17:04:16 +10:00
correctmost 4ed6d0da9b
Remove unused pydantic-settings dependency from pre-commit config (#3437) 2025-05-05 08:25:01 +10:00
renovate[bot] 38213a02bf
fix(deps): update dependency pylint to v3.3.7 (#3433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 08:24:42 +10:00
correctmost ae38e92100
Add an inline annotation to avoid a crash with mypy 1.15.0 (#3434)
The annotation prevents intermittent crashes when running mypy
with a clean cache:

./archinstall/tui/curses_menu.py:723: error: INTERNAL ERROR

RuntimeError: Partial type "<partial list[?]>" cannot be checked with "issubtype()"
2025-05-05 07:04:18 +10:00
Matteo 1e2f9704c8
Another Italian translation update (#3436)
- Translated new strings
- Run locales_generator for all files, so all base.po have been updated
2025-05-05 07:03:09 +10:00
Matteo 6fe680f0ec
Update Italian translation (#3428) 2025-05-04 07:44:10 +10:00
Daniel Girtler 4023cfe2ca
Fix 3412 - Take optional repositories into account (#3427) 2025-05-03 16:06:27 +02:00
correctmost 9a939d8378
Enable tracebacks when running mypy (#3425)
This will make it easier to diagnose intermittent mypy crashes
2025-05-03 08:20:47 +10:00
renovate[bot] 914ff2002f
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.8 (#3422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 15:45:40 +10:00
renovate[bot] c776c46eb9
fix(deps): update dependency ruff to v0.11.8 (#3423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 13:37:36 +10:00
renovate[bot] 9ac2a29ff5
fix(deps): update dependency pydantic to v2.11.4 (#3420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-30 15:28:03 +02:00
correctmost 9c633924ba
Remove extraneous inline type annotations (#3418) 2025-04-30 08:31:28 +10:00
correctmost e8fd1de006
Avoid using generics with EditMenu (#3415)
This is a follow-up to commit 0de90bd55.
2025-04-29 07:12:32 +10:00
correctmost 437bb9c439
Remove a reference to twine (#3416)
This is a follow-up to commit a29a48f45.
2025-04-28 21:20:38 +02:00
Daniel Girtler 4c20331633
Fix 3376 - header alignment (#3414) 2025-04-28 14:04:15 +02:00
renovate[bot] c161d6fbd7
chore(deps): update astral-sh/ruff-action action to v3.3.0 (#3413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 20:57:55 +02:00
utuhiro78 e04ee8a18f
Update Japanese translation (#3410) 2025-04-27 13:03:22 +10:00
Celestial.y 557fef5bfc
Update zh-CN translation, fix locales/README.md (#3409)
* Update zh-CN translation

* Fix locales/README.md
2025-04-27 13:02:02 +10:00
correctmost 0de90bd55b
Specify menu return types using generics instead of Any (#3400) 2025-04-27 13:01:17 +10:00
Daniel Girtler fae210dfea
Add optional file encryption for user credentials configuration (#3391)
* Optional encryption of user credentials configuration file

* Update README

* Update

* Update
2025-04-26 20:25:43 +10:00
renovate[bot] b8923807c7
fix(deps): update dependency ruff to v0.11.7 (#3403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-26 20:02:08 +10:00
renovate[bot] f8a478d59a
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.7 (#3402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-26 20:01:06 +10:00
Anton Hvornum 16e9bbe583
Removed the @.snapshots volume in order to support both Snapper and Timeshift #3363 (#3408) 2025-04-26 11:57:36 +02:00
Celestial.y fd92873ff7
Improve, fix and complete zh-CN translation (#3404) 2025-04-26 19:57:23 +10:00
renovate[bot] d389db4a54
chore(deps): update actions/setup-python digest to a26af69 (#3401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-26 19:57:00 +10:00
Franco Castillo 1756fe56da
Update archinstall/locales/es/LC_MESSAGES/base.mo & archinstall/locales/es/LC_MESSAGES/ (#3405) 2025-04-26 19:56:06 +10:00
Anton Hvornum a29a48f452
Swapping to python-uv for building and distribution (#3407)
* Swapping to python-uv for building archinstall

* Tweaked UV parameters to not use a venv

* Tweaking uv to not resolve/install any dependencies during installation.

* Added remaining dependencies to the build runner

* Swapped to uv for publishing, using pypi 'trusted publisher' instead of token access

* Installing uv and dependencies for publishing

* Swapped to uv in the building of the test ISO
2025-04-26 11:55:19 +02:00
renovate[bot] 935cda7a05
fix(deps): update dependency ruff to v0.11.6 (#3398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 10:46:06 +10:00
renovate[bot] cb29369d26
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.6 (#3397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 10:45:49 +10:00
James Knight 3f06d2b058
Add niri package to profile (#3395) 2025-04-17 11:09:43 +10:00
Daniel Girtler 2f18b8d2fe
Fix 1747 | Additional post-installation menu options (#3393)
* Provide more post installation options

* Update
2025-04-16 07:31:49 +10:00
correctmost 130d1a6ff8
Split out unicode_ljust and unicode_rjust to break import cycle (#3388)
* Split out unicode_ljust and unicode_rjust to break import cycle

Previously, there was an import cycle between tui.menu_item and
lib.output.

* Move unicode.py from lib/ to lib/utils/
2025-04-13 15:47:33 +10:00
correctmost e281c2fa6a
Split out Result and ResultType to break import cycle (#3387)
Previously, there was an import cycle between tui.menu_item and
tui.types.
2025-04-13 15:06:48 +10:00
Thierry M 57a63053cd
Add files via upload (#3386) 2025-04-13 15:05:40 +10:00
codefiles bb87cfacaf
Fix GPT end (#3390) 2025-04-13 15:01:42 +10:00
Daniel Girtler a842fb48a2
Fix 3362 (#3377) 2025-04-11 06:58:12 +02:00
neko_0xff bf944a7459
i18n(zh-TW): re-Update base.po (20250411) (#3384)
* Update base.po

* Update base.po

* Update base.po
2025-04-11 14:09:32 +10:00
renovate[bot] 2db14ca2d1
fix(deps): update dependency ruff to v0.11.5 (#3383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 10:59:52 +10:00
renovate[bot] 0dcc4862e2
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.5 (#3382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 10:57:31 +10:00
correctmost a9fd14fee1
Remove some unnecessary type unions (#3378) 2025-04-09 14:16:23 +02:00
correctmost 3dd8679734
Replace an Any instance with a TypedDict (#3375) 2025-04-09 18:47:53 +10:00
correctmost d35ac2a235
Remove unused SysCommand and SysCommandWorker fields (#3374) 2025-04-09 18:47:30 +10:00
correctmost 0028a572e8
Remove some Any instances from the codebase (#3372) 2025-04-09 15:22:02 +10:00
Matteo 20bd845fbe
Update Italian translation (#3371)
Translated new strings
2025-04-09 07:15:59 +10:00
renovate[bot] dc9d86c4b3
fix(deps): update dependency pydantic to v2.11.3 (#3370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 21:31:43 +02:00
summoner001 409be2196d
Update hungarian translation (#3369)
Update hungarian translation
2025-04-08 21:08:55 +02:00
correctmost d5c210f933
Remove unused variable reported by Pyright (#3368) 2025-04-08 15:14:49 +02:00
correctmost a67b2dea60
Remove unused imports reported by Pyright (#3367) 2025-04-08 22:28:06 +10:00
utuhiro78 ffb2230f33
Update Japanese translation (100%) (#3366) 2025-04-08 22:27:40 +10:00
Valerii a7985233e9
Better Ukrainian v1.1 (#3365)
Ill add more with time
2025-04-08 14:19:35 +02:00
Valerii 03e19c714f
Better Ukrainian (#3364) 2025-04-08 19:29:07 +10:00
Daniel Girtler 4f1d1b4739
Store password as hash instead of plaintext (#3276)
* Rework user password to be hash

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Generate yescrypt hash

* Update

* Update

* Update

* Update
2025-04-08 08:53:18 +02:00
Daniel Girtler bae0e29e18
Fix #3330 (#3360)
* Fix #3330

* Update
2025-04-08 08:46:19 +02:00
Daniel Girtler a3fc658c90
Fix 3350 (#3358) 2025-04-08 08:45:47 +02:00
Daniel Girtler 00e87eb15f
Fix 3334 (#3359) 2025-04-08 08:45:21 +02:00
Daniel Girtler c1de65e5b3
Fix 3313 translation file (#3361) 2025-04-08 08:44:50 +02:00
correctmost 873e7b2c64
Remove unused description fields from profiles (#3357) 2025-04-07 14:26:17 +10:00
killertofus 390f4f15f4
added some more profiles (#3323)
* gave descriptions to profiles

* added some more profiles

* removed the descriptions for all of them and fixed the class name

* made some fixes

* removed the reference to seat

* forgot a comma

* forgot this seat reference

* rewrote river.py

* forgot to include river

* removed lightdm and added upercase X

* added some more fixes

* forgot to add labwc as a dep
2025-04-07 13:48:40 +10:00
codefiles 63b2f986c3
Move system info logging to function (#3356) 2025-04-07 13:19:18 +10:00
codefiles d7a5a59342
Fix disk config validation regression (#3355) 2025-04-07 12:24:17 +10:00
codefiles 5b06ec7c37
Remove check for sphinx and pylint modules (#3353) 2025-04-07 12:23:11 +10:00
Daniel Girtler f985da6f9a
Case insensitive sorting of profiles (#3329)
* Case insensitive sorting of profiles

* Update
2025-04-06 22:37:51 +02:00
mintsuki de3416f55b
Start the boot partition at 1MiB by default on MBR too (#3344) 2025-04-06 22:34:20 +02:00
Matteo da29b315a5
Update Italian translation (#3347)
Translated new strings
2025-04-05 22:55:23 +11:00
renovate[bot] 0ea1a48ead
fix(deps): update dependency ruff to v0.11.4 (#3346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 22:55:04 +11:00
renovate[bot] 790051d17a
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.4 (#3345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 22:54:50 +11:00
Daniel Girtler f5a16307bd
Add ruff auto sort imports hook (#3342) 2025-04-04 13:46:32 +02:00
correctmost 6cdf9d2889
Use curses.window instead of curses._CursesWindow in annotations (#3341) 2025-04-04 18:50:55 +11:00
correctmost 16d8b209fc
Enable possibly-undefined warnings in mypy (#3340)
This commit also fixes the two remaining possibly-undefined
warnings.
2025-04-04 18:50:29 +11:00
renovate[bot] bdc260e7e8
fix(deps): update dependency ruff to v0.11.3 (#3339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 10:15:11 +11:00
renovate[bot] 34881fb860
fix(deps): update dependency pydantic to v2.11.2 (#3338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 10:14:34 +11:00
renovate[bot] 3c9eca8ef4
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.3 (#3337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 10:12:35 +11:00
mintsuki fa581021eb
Improve partition type detection, assignment, and misconfig handling (#3336) 2025-04-04 10:03:02 +11:00
mintsuki e6b0ebb5f3
Actually prevent installing if the config is invalid (#3335)
This also makes use of the previously unused _is_config_valid() method.
2025-04-04 09:46:55 +11:00
mintsuki 0f90d5991d
Use default EFI boot app path for Limine if installing to USB (#3331) 2025-04-04 09:43:07 +11:00
correctmost 1678fb5192
Use assert_never to fix some possibly-undefined mypy warnings (#3333) 2025-04-03 11:59:12 +11:00
wszqkzqk 5af2d2bb07
fix(installer): use platform.machine() for target architecture in grub-install (#3320)
Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2025-04-02 19:29:34 +11:00
EK ed0e9bd3c4
replace GTK theme package for Budgie build with an active one (#3327)
* replace GTK theme package with an active one

* add gtk theme to optdepends
2025-04-02 09:54:17 +02:00
utuhiro78 02c6fec0fc
Force to make pipewire symlinks (#3328) 2025-04-02 09:47:31 +02:00
utuhiro78 790a662ce3
Update Japanese translation (#3326) 2025-04-02 12:21:18 +11:00
neko_0xff 28f764fed1
i18n(zh-TW): re-Update base.po (20250401) (#3324)
* Update base.po

* Update base.po
2025-04-02 09:58:10 +11:00
Daniel Girtler 294eea0a1c
Fix 3298 - Add package gruops to selection (#3322) 2025-04-01 07:34:20 +11:00
summoner001 7513ef1cc8
Update hungarian translation (#3321)
Update hungarian translation
*Translating new strings
2025-03-31 22:09:41 +11:00
Anton Hvornum dc104e6967
Bumping version to 3.0.4 (#3319) 2025-03-31 09:51:40 +02:00
Luna Jernberg 5899a82cf2
Update base.po - Swedish (#3318)
Update Swedish translation for the new release
2025-03-31 09:02:05 +02:00
Daniel Girtler f837387d49
Fix 3315 - package loading (#3317) 2025-03-31 07:44:53 +02:00
Anton Hvornum bab3653144
Updated all the locales in prep for release (#3314) 2025-03-30 21:29:56 +02:00
Anton Hvornum f0159e1278
Fixed root_password and users[superuser=True] check (#3312) 2025-03-30 21:06:42 +02:00
Anton Hvornum 1fbb21f38a
Made sure to update the package databases before running --info (#3311) 2025-03-30 20:53:41 +02:00
renovate[bot] da6e2f3618
chore(deps): update pre-commit hook pycqa/flake8 to v7.2.0 (#3310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-30 10:32:58 +02:00
renovate[bot] b4bec7149d
fix(deps): update dependency flake8 to v7.2.0 (#3309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-30 09:53:49 +02:00
Lisichka d616de25f6
fix(hardware): Improve Nvidia driver installation (#3302)
Fixes a few issues related to the installation of Nvidia drivers.

1. No longer install the redudant nvidia-open package as it's provided
by the nvidia-open-dkms package.
2. Install vulkan-nouveau when selecting the open-source nouveau driver.
3. Install the libva-nvidia-driver package for hardware accelerated
video decoding.
2025-03-30 09:53:17 +02:00
mintsuki 9f7c3bab0f
Detect boot partition regardless of boot flags if mounted on /boot (#3303) 2025-03-29 21:17:33 +01:00
renovate[bot] c4bea107b0
fix(deps): update dependency pydantic to v2.11.1 (#3306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 11:17:15 +11:00
mintsuki ea94535e26
Improve Limine without boot partition unsupported message (#3305) 2025-03-29 11:16:55 +11:00
renovate[bot] 21249ee2c7
fix(deps): update dependency pydantic to v2.11.0 (#3304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-28 11:43:36 +11:00
correctmost 4711f7abad
Fix Pydantic deprecation warnings (#3301)
This commit also enables mypy checks for deprecated calls.
2025-03-26 08:14:11 +11:00
correctmost d43482e585
Move run_custom_user_commands to fix an import cycle (#3254) 2025-03-26 08:13:18 +11:00
renovate[bot] cb42003b2b
chore(deps): update actions/setup-python digest to 8d9ed9a (#3300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 23:05:05 +11:00
Daniel Girtler 1b890492d0
Refactor code (#3247) 2025-03-25 09:49:18 +01:00
Efe TUROĞLU c15dbf0b0d
Added more Turkish translations (#3299) 2025-03-25 08:00:09 +11:00
Daniel Girtler 3d8ecf12ae
Fix 1591 - add user to wheel only when sudo (#3293) 2025-03-24 10:19:52 +01:00
Daniel Girtler 75b6bbaeff
Fix regression for NTP sync (#3294) 2025-03-24 10:18:52 +01:00
Daniel Girtler d3330ff3ab
Only allow ascii chars in input (#3244) 2025-03-24 10:17:37 +01:00
renovate[bot] 9ee9dcec4d
chore(config): migrate config renovate.json (#3297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-23 12:36:41 +11:00
correctmost 0e8d2c139b
Enable config migration PRs from Renovate (#3296)
This will help keep the config file updated with renamed options
and values.
2025-03-23 12:31:36 +11:00
renovate[bot] 4d37212a9a
chore(deps): pin dependencies (#3295)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-23 10:16:59 +11:00
mintsuki c78f78fa9a
More properly support FAT12 and FAT16 ESPs (#3268)
FAT12 and FAT16 are also valid filesystems for ESPs, therefore, try to support them
2025-03-23 10:14:52 +11:00
correctmost 083194f0e8
Pin astral-sh/ruff-action to a specific commit (#3265)
The GitHub docs recommend pinning third-party actions to specific
commits for security hardening purposes.
2025-03-23 10:14:05 +11:00
codefiles 02729f0a50
Do not write passwords to /tmp (#3292) 2025-03-23 09:58:09 +11:00
codefiles 0e71bcff78
Do not write passwords to the command log (#3291) 2025-03-22 15:23:18 +11:00
codefiles 16a84ba662
Disable default credentials save (#3290) 2025-03-22 15:21:43 +11:00
codefiles d326ceff45
Add udev sync after LUKS encrypt (#3289) 2025-03-22 11:16:19 +11:00
renovate[bot] 54e7a94cf8
fix(deps): update dependency ruff to v0.11.2 (#3288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 09:36:14 +11:00
renovate[bot] f6eb99d816
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.2 (#3287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 09:35:34 +11:00
codefiles 6615d18246
Dedent Limine hook (#3286) 2025-03-22 09:35:14 +11:00
neko_0xff edf8bf9b0a
Update base.po (#3285) 2025-03-21 22:12:08 +11:00
codefiles dcce29b91c
Refactor creation of BLS entries (#3283) 2025-03-21 22:08:21 +11:00
neko_0xff 51497bd079
re-update zh_TW base.po (#3282)
* Update base.po

* Update archinstall/locales/zh-TW/LC_MESSAGES/base.po

Co-authored-by: pan93412 <pan93412@gmail.com>

* Update base.po

---------

Co-authored-by: pan93412 <pan93412@gmail.com>
2025-03-21 09:16:43 +11:00
mintsuki 931f47a037
Limine: use UUID for accessing boot partition if not same as ESP (#3267) 2025-03-21 08:35:32 +11:00
renovate[bot] 26194d6b47
fix(deps): update dependency pylint to v3.3.6 (#3279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 08:30:51 +11:00
renovate[bot] 469e0f0a67
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.1 (#3278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 08:30:04 +11:00
codefiles cc6716be99
Disable irrelevant validation in mountpoint prompt (#3280) 2025-03-21 08:29:20 +11:00
renovate[bot] 4efc461b98
fix(deps): update dependency ruff to v0.11.1 (#3281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 08:28:26 +11:00
codefiles c8e2c3eca8
Fix loader configuration location (#3275) 2025-03-20 14:21:06 +11:00
codefiles 4ffff49d5f
Add XBOOTLDR to manual partitioning (#3273) 2025-03-20 14:19:28 +11:00
renovate[bot] 2ba40dd8e3
fix(deps): update dependency pre-commit to v4.2.0 (#3271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 09:24:37 +11:00
Matteo 1891df74da
Update Italian translation (#3270)
-Translated new strings
-Improved some translation
2025-03-20 09:21:24 +11:00
mintsuki 48053fb822
Create proper EFI boot menu entry for Limine (#3263) 2025-03-20 09:18:47 +11:00
mintsuki f046495bda
Put Limine config and BIOS files in a limine/ subdir (#3264) 2025-03-20 09:14:41 +11:00
correctmost 87fb96d249
Fix grammar in existing-session error message (#3262) 2025-03-15 18:59:17 +11:00
correctmost aa444748b9
Replace an Any instance with TypedDict (#3261) 2025-03-15 09:37:32 +11:00
renovate[bot] ae19299e5f
fix(deps): update dependency ruff to v0.11.0 (#3260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 09:36:57 +11:00
renovate[bot] 4899d555eb
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.11.0 (#3259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 09:36:41 +11:00
correctmost 4938d79873
Fix a blanket type ignore comment (#3258)
This commit allows the ignore-without-code mypy warning to be
enabled. It also allows the pygrep-hooks ruff rules to be enabled.
2025-03-14 09:06:36 +11:00
correctmost b6983a2d5a
Enable additional mypy error codes (#3257) 2025-03-14 08:16:27 +11:00
renovate[bot] 933f2c8cd4
fix(deps): update dependency ruff to v0.10.0 (#3256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 08:16:05 +11:00
renovate[bot] 6c9462e9cf
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.10.0 (#3255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-14 08:15:49 +11:00
correctmost 83c3a76197
Enable import checks in __init__.py files with Pylint (#3253) 2025-03-13 15:17:45 +11:00
correctmost f100e5110d
Use __all__ in __init__.py files to fix unused-import warnings (#3252)
This commit allows ruff and flake8 ignores to be removed. It also
allows mypy to be run in strict mode by default.
2025-03-13 14:19:05 +11:00
correctmost e7ca4f56d5
Remove unneeded flake8 ignore for __init__.py files (#3250) 2025-03-13 08:28:44 +11:00
correctmost 057feacf0e
Remove unused stdlib imports from __init__.py files (#3249) 2025-03-13 08:28:24 +11:00
correctmost 269e6f8e54
Enable most flake8-pyi rules in ruff and fix a warning (#3248) 2025-03-13 08:27:53 +11:00
Daniel Girtler e46aa7c1b0
Add timestamp to logs (#3245) 2025-03-12 18:37:13 +11:00
correctmost 7f88e909c6
Remove unused _set_font code (#3243)
This code has been unused since commit dee792658.
2025-03-11 21:26:08 +11:00
correctmost 0b0dc76558
Fix SysCallError import cycle between exceptions.py and general.py (#3242)
This commit updates SysCallError to accept a worker log argument
instead of an entire SysCommandWorker.
2025-03-11 21:25:25 +11:00
correctmost dbf45e23cc
Introduce a string-specific version of clear_vt100_escape_codes (#3241)
This commit helps fix some existing type errors in the code. It
also helps avoid ~280,000 isinstance calls when opening the
additional packages menu.
2025-03-11 12:11:04 +11:00
correctmost 2819ea02b1
Speed up _count_wchars by avoiding east_asian_width calls (#3240)
This commit helps avoid ~390,000 duplicate east_asian_width calls
when opening the additional packages menu.
2025-03-11 12:10:33 +11:00
correctmost a5fcf21a12
Speed up _parse_package_output by caching normalized key strings (#3239)
This commit helps avoid ~260,000 chained strip(), lower(), and
replace() sequences.
2025-03-10 22:04:41 +11:00
correctmost 624143ed96
Speed up clear_vt100_escape_codes by caching an encode() result (#3238)
This commit helps avoid ~280,000 redundant encode() calls when
opening the additional packages menu.
2025-03-10 22:03:29 +11:00
Daniel Girtler 55941cc40e
Fix package text (#3236) 2025-03-10 22:02:40 +11:00
Daniel Girtler 12562f4cae
Add newline after countdown (#3237) 2025-03-10 22:02:26 +11:00
Carlo Teubner 24f479892f
guided.rst: fix formatting & typos (#3235) 2025-03-09 20:57:56 +11:00
renovate[bot] c9ebf3c881
fix(deps): update dependency pylint to v3.3.5 (#3234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-09 20:53:21 +11:00
correctmost 6a90760e67
Clean up most no-implicit-reexport warnings from mypy (#3233)
This makes it easier to run mypy --strict from the command line
and should hopefully make it easier to clean up import cycles in
future commits.
2025-03-09 19:50:56 +11:00
renovate[bot] 01f1cd314f
fix(deps): update dependency ruff to v0.9.10 (#3231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 10:02:29 +11:00
renovate[bot] cdf846eee8
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.10 (#3230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-08 10:02:13 +11:00
Yaron Shahrabani cf75a38a28
Update Hebrew translation. (#3229) 2025-03-06 21:47:33 +11:00
correctmost 54d426e54c
Fix Pyright warning with urllib.parse (#3228) 2025-03-06 18:23:43 +11:00
correctmost 9a3ec27645
Fix repositories typo in debug message (#3227) 2025-03-06 18:23:18 +11:00
correctmost 49347c2f82
Fix falsy-dict-get-fallback ruff warning (#3226) 2025-03-06 13:05:59 +11:00
Daniel Girtler 697ccd1ac5
Fix 2379 - Mirror and region definitions (#3223)
* Fix 2379 - Mirror and repository settings

* Fix alignment
2025-03-05 22:19:50 +11:00
correctmost 5f7b16b152
Remove some Any instances from the codebase (#3221) 2025-03-03 08:24:36 +11:00
renovate[bot] d6ee5a66b2
fix(deps): update dependency pytest to v8.3.5 (#3220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 07:33:59 +11:00
correctmost d9ac33dbe8
Fix a non-pep695-generic-function ruff warning (#3218) 2025-03-02 22:01:56 +11:00
correctmost ddbd465a34
Remove python-simple-term-menu installation from build_iso.sh (#3217)
The dependency is no longer needed as of 0f2e0095.
2025-03-02 10:43:46 +11:00
Alexmelman88 1e830c16a6
Update Russian translation (#3216) 2025-03-02 10:43:10 +11:00
correctmost 70c6a10c3c
Add missing typing.override annotation reported by Pyright (#3215) 2025-03-02 10:41:21 +11:00
correctmost f94e8b8984
Fix Pyright warnings with importlib.util (#3214) 2025-03-02 10:40:53 +11:00
correctmost 1c6085f86c
Fix Pyright warning with curses.textpad (#3213) 2025-03-02 10:40:20 +11:00
renovate[bot] 4f72c0177b
fix(deps): update dependency ruff to v0.9.9 (#3210)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 10:39:37 +11:00
renovate[bot] c2672aaa79
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.9 (#3209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 10:39:09 +11:00
mintsuki 98d604d097
Do not enable UKI at archinstall start if using BIOS (#3208) 2025-03-02 10:38:38 +11:00
mintsuki 645e8d0144
Enable UKI support for the Limine bootloader option (#3207) 2025-03-02 10:33:07 +11:00
mintsuki 0b551d729e
Do not force install GRUB on BIOS when not chosen as bootloader (#3206) 2025-03-02 10:22:27 +11:00
correctmost 142883c9c3
Fix typing syntax for multiple string literals (#3212) 2025-03-01 17:20:42 +11:00
correctmost d6ea7b011f
Fix a used-dummy-variable ruff warning (#3203) 2025-02-26 07:35:25 +11:00
Daniel Girtler 1ddc74af21
Remove debug output for all commands (#3201) 2025-02-25 11:20:43 +01:00
Daniel Girtler 245b84194a
Remove duplicate loading of archinstall (#3200) 2025-02-25 20:31:38 +11:00
Daniel Girtler 74b41dea96
Add additional package selector (#3196) 2025-02-24 17:57:26 +11:00
codefiles 4a477351e0
Fix manual partitioning device wipe status (#3198) 2025-02-24 12:18:14 +11:00
codefiles 4b07f8a3ae
Rework default partition table (#3197) 2025-02-23 21:16:11 +01:00
Daniel Girtler 8b375c97a5
Fix version parsing for local branches (#3190)
* Fix version parsing for local branches

* Fix tests

* Fix tests
2025-02-23 09:44:25 +01:00
codefiles a51475e0ed
Refactor default partition table (#3194) 2025-02-23 18:51:25 +11:00
codefiles d69441a2da
Fix typing for enc_conf parameter (#3195) 2025-02-23 18:50:32 +11:00
codefiles 201968cf2e
Show device wipe status in Info for Partitioning (#3193) 2025-02-23 10:20:37 +11:00
codefiles b83bc79d91
Fix MBR conditional (#3192) 2025-02-23 10:20:06 +11:00
summoner001 c364917324
Update hungarian translation (#3191)
Fix typos with Lokalize and PO-Edit.
2025-02-22 23:02:54 +11:00
correctmost 4c2ec5deba
Enable --strict-bytes checks in mypy (#3189)
Strict-bytes checks will be enabled by default in mypy 2.0, so this
commit will help prevent violations from being introduced into the
codebase.
2025-02-22 14:24:52 +11:00
renovate[bot] 957fa06ade
fix(deps): update dependency ruff to v0.9.7 (#3187)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 08:14:20 +11:00
renovate[bot] 81edc8784a
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.7 (#3186)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 08:14:05 +11:00
Moritz 3f762feced
Update base.po (#3184)
-Translation Update
2025-02-20 07:37:13 +11:00
Daniel Girtler b57f7f91cf
Integrate new arguments data structure (#3167)
* Integrate new args dataclass

* Integrate args

* Update

* Update

* Update

* Update
2025-02-20 07:35:59 +11:00
Celestial.y a9ae064359
Improve, fix and complete zh-CN translation (#3180)
* Minor fixing README for translation.

* Improve/fix/add translation for zh-CN

* Compile translation
2025-02-19 08:04:24 +11:00
renovate[bot] c00f609c1b
fix(deps): update dependency flake8 to v7.1.2 (#3178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 17:06:43 +11:00
renovate[bot] 7347259a4b
chore(deps): update pre-commit hook pycqa/flake8 to v7.1.2 (#3177)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 17:06:29 +11:00
Odyssey f73800c6dd
Update Catalan translation (#3175) 2025-02-14 12:21:39 +11:00
Luna Jernberg 579f2ba498
Update base.po (#3171)
Update Swedish translation
2025-02-11 18:25:20 +11:00
renovate[bot] 604884fa07
fix(deps): update dependency ruff to v0.9.6 (#3170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 18:24:59 +11:00
renovate[bot] ef5f61d5df
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.6 (#3169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 18:24:44 +11:00
Korsil Vladivostok bcbdf2346b
Update base.po (#3164)
* Update base.po

* Update base.po

Latest review made at base.po pt-br file before Pull Request
2025-02-08 11:54:04 +11:00
renovate[bot] 9e88cd0cc8
fix(deps): update dependency ruff to v0.9.5 (#3162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:53:12 +11:00
walken 85d89c7b33
Czech localization update (#3163) 2025-02-08 11:52:16 +11:00
renovate[bot] e489f7f33d
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.5 (#3161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-08 11:51:43 +11:00
renovate[bot] 889f0ad5ae
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.15.0 (#3159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 19:32:42 +11:00
renovate[bot] 8c4634c4d6
fix(deps): update dependency mypy to v1.15.0 (#3158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 17:00:51 +11:00
utuhiro78 419b320e9e
Update Japanese translation (#3157) 2025-02-02 10:14:49 +11:00
correctmost fb3dd7da4c
Use the official ruff GitHub action instead of custom code (#3156)
This speeds up PR checks and reduces network I/O.
2025-02-01 17:04:28 +11:00
summoner001 94ae4e3dd7
Update hungarian translation (#3153)
Translate new strings and add minor fixes.
2025-02-01 08:30:56 +11:00
renovate[bot] 77059e8819
fix(deps): update dependency ruff to v0.9.4 (#3151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 17:08:44 +11:00
renovate[bot] 40b6c160e3
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.4 (#3150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 17:07:58 +11:00
DrakeCoding 71a6c84df0
Fixed grammar under function "get_arguments" (#3152) 2025-01-31 14:24:51 +11:00
Franco Castillo ae3f59ccae
Update .po, base.pot and archinstall/locales/es/LC_MESSAGES/base.mo files. (#3149)
Signed-off-by: Franco Castillo <castillofrancodamian@gmail.com>
2025-01-31 07:25:44 +11:00
uday e110492b56
Replace Sway with Hyprland in profile seat selection (#3145) 2025-01-29 08:15:31 +11:00
correctmost 7b0efd35f0
Remove unneeded mypy override for archinstall.scripts (#3147)
The override is no longer needed after commit fc63d45fe6.
2025-01-29 08:14:45 +11:00
renovate[bot] 4f53961392
fix(deps): update dependency pylint to v3.3.4 (#3146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-29 07:33:20 +11:00
renovate[bot] 0f6f5dae86
fix(deps): update dependency pydantic to v2.10.6 (#3143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-25 10:04:15 +11:00
Wise 0b89966720
Update missing translation strings for TR (#3142) 2025-01-24 12:32:06 +11:00
renovate[bot] 59b90d65a1
fix(deps): update dependency ruff to v0.9.3 (#3141)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 09:58:55 +11:00
renovate[bot] 88eedf04aa
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.3 (#3140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-24 09:58:40 +11:00
codefiles f05af2e6c4
Consolidate preparations for lvm and partitions (#3135) 2025-01-23 17:50:35 +11:00
Anton Hvornum 6653a4e5a9
Removed requirement for root for pylint (#3138) 2025-01-22 19:46:44 +01:00
Anton Hvornum 7ccc288b14
Bumping to version 3.0.2 (#3137) 2025-01-22 19:03:33 +01:00
Daniel Girtler 6c6ceef6f2
Fix remote mirrorlist parsing (#3136) 2025-01-22 10:04:59 +01:00
correctmost 8aa479bf3e
Use float instead of int for pre-validated mirror score fields (#3079)
This fixes an unnecessary-round ruff warning in validate_score.
2025-01-22 18:54:32 +11:00
Anton Hvornum 6d371da5df
Remove reiserfs as it's been deprecated since 2022 (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=a452c4eb404df8a7f2a79a37ac77b90b6db1a2c9) and been removed in the kernel as of 2024-11-21 (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c01f664e4ca210823b7594b50669bbd9b0a3c3b0) (#3134) 2025-01-21 18:53:55 +01:00
Daniel Girtler 4aa8317b7c
Add skip for partitioning menus (#3121) 2025-01-21 18:42:16 +01:00
Daniel Girtler 985775b143
Fix misaligned table headers (#3120) 2025-01-21 18:41:47 +01:00
Daniel Girtler 64f08cc890
Fix scrolling in console (#3113) 2025-01-21 18:41:23 +01:00
Daniel Girtler fb159a8255
Fix 3081 - Local mirrorlist parsing bug (#3104)
* Fix 3081 - shortcircuit on empty mirror options

* Update

* Update
2025-01-21 18:40:54 +01:00
codefiles c9bdaa209e
Replace list with link to contributors page (#3102) 2025-01-21 12:53:36 +01:00
Daniel Girtler fc63d45fe6
Remove deprecated swiss config (#3042)
* Remove deprecated swiss config

* Update
2025-01-21 12:42:31 +01:00
renovate[bot] 2bd4344d43
fix(deps): update dependency pre-commit to v4.1.0 (#3131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 12:17:25 +11:00
codefiles ee69e19f8b
Refactor preparations for fs type and encryption (#3130) 2025-01-20 20:22:43 +11:00
codefiles 19c390e072
disk: add support for creating swap partitions (#3129) 2025-01-18 16:48:48 +11:00
renovate[bot] 8d923ff09e
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.2 (#3127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 09:28:34 +11:00
renovate[bot] 99fe401979
fix(deps): update dependency ruff to v0.9.2 (#3125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 09:28:17 +11:00
correctmost c94d0a56d9
Enable most flake8-commas rules in ruff and fix warning (#3122) 2025-01-15 16:30:13 +11:00
codefiles 41215741f0
disk: add udev sync after partitioning (#3119) 2025-01-14 14:23:25 +11:00
codefiles d5bbda1e2f
Refactor cmd path check using startswith() (#3117) 2025-01-14 14:22:47 +11:00
codefiles b7a5d00676
Remove superfluous /usr/bin/ from commands (#3115) 2025-01-13 14:42:39 +11:00
correctmost c0a2de4330
Enable the set-attr-with-constant ruff rule and fix warnings (#3114) 2025-01-12 15:02:06 +11:00
correctmost fd77b5d4ce
Enable the strip-with-multi-characters ruff rule (#3112)
Commit 4212357c6 fixed the remaining warnings.
2025-01-12 12:39:10 +11:00
correctmost 457e790bd0
Enable most flake8-bugbear rules in ruff (#3110) 2025-01-12 11:51:06 +11:00
correctmost 4212357c6f
Use removeprefix/removesuffix instead of incorrect lstrip/rstrip calls (#3109) 2025-01-12 11:50:09 +11:00
codefiles 6681501904
Fix Btrfs subvolume mount options (#3108) 2025-01-12 11:46:34 +11:00
correctmost 47736c4060
Avoid reassigning a parameter value to fix Pyright warnings (#3106) 2025-01-12 11:45:41 +11:00
renovate[bot] df2791295d
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.1 (#3100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-12 11:30:59 +11:00
codefiles 3409f84e79
Use list comprehension (#3105) 2025-01-12 11:30:27 +11:00
Melroy van den Berg 7202d964dd
Make libera.chat irc link clickable (#3099)
Fix libera.chat link in markdown
2025-01-12 11:27:30 +11:00
correctmost e7f2a8c203
Fix some mypy warnings in archinstall/lib/ (#3103) 2025-01-11 15:44:43 +11:00
codefiles 22b410d082
Change to import Path for consistency (#3101) 2025-01-11 15:42:50 +11:00
Melroy van den Berg a575ac2c47
Use Matrix channel invite link (#3098) 2025-01-11 15:40:13 +11:00
renovate[bot] 39c7eda352
fix(deps): update dependency ruff to v0.9.1 (#3097)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-11 15:37:33 +11:00
codefiles 700294f19a
disk: validate first partition start (#3096) 2025-01-10 20:12:47 +11:00
renovate[bot] ad015033b8
fix(deps): update dependency ruff to v0.9.0 (#3095)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 08:21:18 +11:00
renovate[bot] cb414c1c5b
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.9.0 (#3093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 08:19:37 +11:00
renovate[bot] 2778ca60f6
fix(deps): update dependency pydantic to v2.10.5 (#3092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 08:19:21 +11:00
codefiles c081f77d57
pacman/repo: use StrEnum (#3088) 2025-01-08 12:13:11 +11:00
correctmost 10a12dc855
Fix mutable-class-default ruff warnings (#3087) 2025-01-08 10:23:07 +11:00
renovate[bot] 34ef6527ce
fix(deps): update dependency pylint-pydantic to v0.3.5 (#3086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 07:52:24 +11:00
codefiles 6398f6852d
audio: use StrEnum (#3084) 2025-01-08 07:45:19 +11:00
correctmost 40210f8733
Fix used-dummy-variable ruff warnings (#3083) 2025-01-08 07:44:48 +11:00
codefiles 9163e8c84c
disk: rework manual partitioning (#3071)
* disk: rework manual partitioning

* disk: fix end alignment

* disk: validate config

* test: fix disk config partition overlap
2025-01-08 07:44:03 +11:00
correctmost 25b7bbb0ed
Add if-key-in-dict-del ruff rule to ignore list (#3082)
The rule is concerned with code style rather than correctness or
performance.
2025-01-08 07:41:25 +11:00
correctmost e712144e5d
Fix falsy-dict-get-fallback ruff warnings (#3077) 2025-01-08 07:38:40 +11:00
codefiles fd91cbaac1
pacman/repo: update enum members to uppercase (#3076) 2025-01-08 07:37:43 +11:00
codefiles fa515dfdc6
general: remove superfluous callbacks variable (#3075) 2025-01-08 07:36:12 +11:00
renovate[bot] 984fa6af46
fix(deps): update dependency ruff to v0.8.6 (#3074)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 07:34:31 +11:00
renovate[bot] 7cba07b730
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.8.6 (#3073)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 07:34:12 +11:00
renovate[bot] f1f08c811b
fix(deps): update dependency ruff to v0.8.5 (#3064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 15:52:58 +11:00
codefiles 6dab4650fa
audio: capitalize enums (#3067) 2025-01-03 15:24:10 +11:00
renovate[bot] cb6417be6e
fix(deps): update dependency mypy to v1.14.1 (#3057)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 14:13:00 +11:00
renovate[bot] 77f5b075b6
fix(deps): update dependency pylint to v3.3.3 (#3046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 14:12:46 +11:00
renovate[bot] 82bcb59044
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.14.1 (#3062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 13:56:33 +11:00
renovate[bot] 9d1a47df13
chore(deps): update pre-commit hook astral-sh/ruff-pre-commit to v0.8.5 (#3063)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 13:56:16 +11:00
codefiles 547ce8033d
audio: remove incorrect use of dataclass (#3058) 2025-01-03 13:44:25 +11:00
codefiles 18ef7fd469
Use Path.chmod() (#3051) 2025-01-03 13:41:41 +11:00
codefiles 459b84b6fe
Rework mount point argument (#3041) 2024-12-23 11:04:16 +11:00
codefiles d5c5b60d5c
Rework profiles path (#3040) 2024-12-23 09:08:48 +11:00
codefiles 5f8cdcb39b
disk: change from_partition() parameters (#3039) 2024-12-23 09:08:02 +11:00
renovate[bot] a8c2a5a54f
fix(deps): update dependency mypy to v1.14.0 (#3036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-22 10:38:20 +11:00
renovate[bot] 2996e0ae14
chore(deps): update pre-commit hook pre-commit/mirrors-mypy to v1.14.0 (#3037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-22 10:37:58 +11:00
codefiles 24ff132f93
general: remove unspecified exception handling (#3035) 2024-12-22 10:21:56 +11:00
codefiles c978c841de
disk: move ENC_IDENTIFIER (#3034) 2024-12-22 10:21:21 +11:00
codefiles 9b5fd6bad5
mirrors: use pydantic model_validate_json() (#3033) 2024-12-20 17:26:45 +11:00
codefiles 367c8d781c
general: rework environment_vars (#3032) 2024-12-20 17:26:11 +11:00
codefiles f685849b8d
luks: remove superfluous _mapper_dev (#3031) 2024-12-20 17:25:12 +11:00
renovate[bot] 75459f4ac2
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.4 (#3030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 12:01:27 +11:00
renovate[bot] 5a82ce7719
Update dependency ruff to v0.8.4 (#3029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 12:01:09 +11:00
renovate[bot] 05441a4c11
Update dependency pydantic to v2.10.4 (#3026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-19 17:50:14 +11:00
codefiles ae51af67a3
Remove disk retries and timeouts (#3025) 2024-12-19 17:49:44 +11:00
Mohamed E d2ef961b32
[Feat] Wayfire support (#3020)
* add profile

* fix pylint
2024-12-18 07:04:48 +11:00
lazysixoeight 5288e246ec
Specified GNOME as an acronym (#3023) 2024-12-16 20:49:42 +11:00
renovate[bot] fb27fbdfa8
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.3 (#3019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-15 09:18:28 +11:00
renovate[bot] 7b09c5a7ca
Update dependency ruff to v0.8.3 (#3018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-15 09:18:03 +11:00
Daniel Girtler 1ff04c6df0
Use ctrl+h for help menu trigger (#2996) 2024-12-12 09:43:54 +01:00
renovate[bot] 27df486dda
Update dependency pylint-pydantic to v0.3.4 (#3017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 15:54:31 +11:00
codefiles 4dccd54cbd
Replace commonprefix() with commonpath() (#3016) 2024-12-09 13:25:08 +11:00
codefiles 3716df9bb7
Fix pre-mounted Btrfs subvolumes root detection (#3014) 2024-12-09 10:38:16 +11:00
codefiles 4592280baa
Refactor set_mirrors() (#3011) 2024-12-08 15:07:17 +11:00
codefiles ae3b0f8d75
Remove superfluous try in get_btrfs_info() (#3010) 2024-12-08 09:01:51 +11:00
Daniel Girtler 1d278f8abd
Fix 2991 - Restructure the mirror list handling (#3007)
* Fix 2991 - mirror configuration

* Update

* Update

* Update
2024-12-08 08:58:16 +11:00
codefiles 8f2bf2b737
Refactor set_hostname() (#3009) 2024-12-08 08:57:31 +11:00
codefiles 3400991c9b
Refactor enable_sudo() (#3008) 2024-12-08 08:56:58 +11:00
codefiles 19a0d49509
Update partition flags value (#3006) 2024-12-07 11:48:49 +11:00
codefiles 0a9fae2e57
Replace deprecated pydantic parse_raw() (#3004) 2024-12-07 10:20:23 +11:00
renovate[bot] fbdb3c4f37
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.2 (#3002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 17:17:49 +11:00
renovate[bot] 2d4128fd84
Update dependency ruff to v0.8.2 (#3001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 17:17:03 +11:00
renovate[bot] 89cd718462
Update dependency pydantic to v2.10.3 (#2999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 21:05:29 +11:00
zbik 3905bfe31a
Update plasma.py (#2998)
changed kwrite to kate as the two packages merged together in 2022 and removed egl-wayland (why was it there in the first place?)
2024-12-03 20:03:42 +11:00
renovate[bot] 03e41d3721
Update dependency pylint to v3.3.2 (#2992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 18:18:21 +11:00
renovate[bot] 7d0acbcc6d
Update dependency pylint-pydantic to v0.3.3 (#2995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 18:17:52 +11:00
renovate[bot] ac75dda390
Update dependency pytest to v8.3.4 (#2989)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-02 13:12:05 +11:00
Michael Ziminsky (Z) 256f206b9e
btrfs improvements and fixes (#2970) 2024-12-01 18:35:30 +11:00
correctmost 60842bd1cf
Enable bare-except linter rules and fix warnings (#2988) 2024-12-01 18:34:51 +11:00
correctmost 0bc2ad700f
Enable the "unreachable" pylint warning and remove unused code (#2987)
The os.fsync call causes an "invalid argument" OSError when it
actually runs, so it has been removed altogether.

Closes #2773
2024-12-01 18:34:00 +11:00
correctmost 507e4322d6
Remove unused SysCommand.__json__ method (#2986) 2024-12-01 18:33:22 +11:00
correctmost fcf9658f8f
Disable some rules that warn when using ruff's preview mode (#2985) 2024-12-01 17:50:33 +11:00
correctmost 8fa5ec15af
Enable the yield-in-for-loop ruff rule and fix warnings (#2984) 2024-12-01 17:50:11 +11:00
correctmost af043afa88
Enable the unnecessary-lambda pylint rule and fix warnings (#2983) 2024-12-01 17:44:39 +11:00
correctmost e0db642366
Fix "selction" typo (#2981) 2024-12-01 17:43:29 +11:00
correctmost b49b5bfa11
Fix "Pssword" typo (#2980) 2024-12-01 17:42:33 +11:00
correctmost 760963f7a6
Enable the unused-variable pylint rule and fix warnings (#2978) 2024-12-01 17:41:34 +11:00
correctmost f578ac4ade
Remove unused argument from GlobalMenu._get_menu_options (#2977) 2024-12-01 17:41:03 +11:00
correctmost eacc28d577
Remove unused argument from AbstractCurses._confirm_interrupt (#2976) 2024-12-01 11:13:04 +11:00
correctmost a8fad93ae0
Add missing typing.override annotations reported by Pyright (#2974)
This commit also removes duplicated code in Viewport._replace_str.
2024-12-01 11:01:16 +11:00
correctmost e33ac034dc
Replace deprecated stage name in pre-commit config (#2975)
This commit fixes the following warning:

"top-level `default_stages` uses deprecated stage names (commit)
which will be removed in a future version."
2024-12-01 11:01:02 +11:00
correctmost 6a6642a9c1
Replace some Any instances with specific type hints (#2973) 2024-12-01 09:17:10 +11:00
correctmost 0a1d036750
Remove unused VersionDef class (#2972)
This allows the comparison-with-callable pylint rule to be enabled.
2024-12-01 08:40:38 +11:00
renovate[bot] cdd9e0cbcc
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.1 (#2956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-30 22:57:01 +11:00
correctmost 007f2ff797
Use TypedDict to annotate disk-related serializations (#2935) 2024-11-30 22:56:46 +11:00
correctmost 11f8490b59
Enable the bad-indentation pylint rule and fix space indentations (#2968) 2024-11-30 22:54:53 +11:00
correctmost 1e6492d34a
Enable the printf-string-formatting ruff rule and fix warnings (#2967) 2024-11-30 22:54:20 +11:00
correctmost b0222111f5
Enable the redefined-builtin pylint rule and fix warnings (#2966) 2024-11-30 22:53:54 +11:00
renovate[bot] 6f42eba2f4
Update dependency ruff to v0.8.1 (#2955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-30 22:52:03 +11:00
correctmost 604526c3c8
Enable the f-string ruff rule and fix warnings (#2965) 2024-11-30 20:03:09 +11:00
correctmost d4c04a83a3
Enable the redefined-outer-name pylint rule and fix warnings (#2964) 2024-11-30 20:01:38 +11:00
correctmost 544400606f
Enable mypy checks for tests/ files (#2963) 2024-11-30 20:00:35 +11:00
correctmost 2825818af6
Enable useless-parent-delegation pylint rule and fix warnings (#2962) 2024-11-30 19:59:28 +11:00
correctmost 45bc9c2e65
Use the ruff version specified in pyproject.toml for CI checks (#2960)
Previously, the ruff system package was used, which could lag
behind the PyPI version.
2024-11-30 19:58:10 +11:00
correctmost 80eebcff4f
Enable the explicit-f-string-type-conversion rule and fix warnings (#2954) 2024-11-29 06:43:13 +11:00
renovate[bot] 7b291a6681
Update dependency pydantic to v2.10.2 (#2942)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 22:45:34 +11:00
correctmost e3ff449bc4
Enable the Ruff-specific ruleset (#2951) 2024-11-28 20:15:30 +11:00
correctmost f314b7be54
Replace some Any instances with specific type hints (#2950) 2024-11-28 20:14:58 +11:00
renovate[bot] 8646c9aac2
Update dependency pytest to v8 (#2941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 18:05:12 +11:00
correctmost e49a679fb8
Enable pytest test runs on CI (#2947)
This commit also fixes fixture and test issues that caused failures.
2024-11-27 17:42:22 +11:00
correctmost a28fb27a7a
Use human-friendly names in mypy and Pylint ignore directives (#2946) 2024-11-27 06:48:38 +11:00
correctmost 7bdef2f017
Fix mypy warnings in tests/test_args.py (#2945) 2024-11-27 06:46:17 +11:00
Daniel Girtler 198efb5f53
Fix 2916 - Reinstate prompt in list menus (#2940)
* Reinstate prompt in list menus

* Ruff
2024-11-26 11:10:48 +01:00
Daniel Girtler e51f7adf21
Dataclasses for args and config (#2936)
* Introduce dataclass for arguments and configuration

* Update

* Update
2024-11-26 11:08:36 +01:00
correctmost f2bc5ff280
Remove unused Selector class (#2938)
The code is unused as of 88b91ae201.
2024-11-26 12:22:16 +11:00
correctmost ee2ed3fe54
Start using typing.override to annotate overridden methods and properties (#2934) 2024-11-25 19:51:16 +11:00
correctmost 8d807c08ee
Use precise type hints for most DeferredTranslation references (#2929)
The remaining "_: Any" instances will require accompanying code
changes.
2024-11-25 18:49:22 +11:00
correctmost 46e4e28294
Bump the minimum Python version to 3.12 (#2933) 2024-11-25 18:45:50 +11:00
correctmost 6c37ba68e2
Remove unused singleton utility code (#2931) 2024-11-25 18:45:01 +11:00
correctmost 9cabc981b2
Remove python-simple-term-menu installations from CI workflows (#2932)
The dependency is no longer needed as of 0f2e0095.
2024-11-25 08:26:09 +01:00
correctmost d89ae04e0e
Delete ignore directive for Ruff rule that was removed (#2928)
The unpacked-list-comprehension rule was removed in Ruff 0.8.0.
2024-11-25 14:25:53 +11:00
Anton Hvornum aa80f8cf41
Bump to version 3.0.1 (#2920) 2024-11-23 11:45:32 +01:00
codefiles 7eb1f47084
Use binary units (#2917) 2024-11-23 08:53:32 +01:00
Daniel Girtler 5c83e230df
Fix 2915 - Reinstate partition information in device selection (#2919)
* Fix regresion in device selection - show partition info

* Fix linting
2024-11-23 08:52:53 +01:00
ots25 41600aefa4
Add files via upload (#2912)
Update  and fix Arabic translation
2024-11-23 08:19:36 +11:00
renovate[bot] 4d1318e71a
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.0 (#2911)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 22:15:27 +11:00
renovate[bot] 4cbf0f49db
Update dependency ruff to v0.8.0 (#2910)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 22:15:11 +11:00
renovate[bot] 1579483193
Update dependency pydantic to v2.10.1 (#2901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 22:05:11 +11:00
Thierry M b2b3a728a0
Update base.po (French) (#2907)
Hello,
Here is the update on the French language.
Best regards,
Roxfr
2024-11-22 19:07:11 +11:00
codefiles f5447e91c4
Increase suggested root partition size (#2905) 2024-11-22 07:23:21 +11:00
codefiles bda0752eec
Change partition flag name Boot -> BOOT (#2903) 2024-11-21 12:22:50 +11:00
codefiles 7fd726f03f
Fix log disk states (#2902) 2024-11-21 10:50:43 +11:00
utuhiro78 1464806f18
Update Japanese translation (#2900) 2024-11-21 09:01:09 +11:00
codefiles 0eac05cecc
Add linux-home partition flag (#2898) 2024-11-20 15:32:29 +01:00
codefiles 611af783aa
Fix partition flag value (#2897) 2024-11-20 14:49:34 +01:00
codefiles 83d222cec6
Rework partition flag (#2895) 2024-11-20 07:55:09 +01:00
codefiles 3453816b38
Fix hostname None (#2893) 2024-11-20 16:32:05 +11:00
walken f827851560
Czech localization update (#2891) 2024-11-20 13:04:56 +11:00
Hugo Carvalho 1e3fb257a2
Update portuguese translation (#2886) 2024-11-19 23:41:57 +01:00
codefiles 4f704b8501
Refactor partition type GUID (#2890) 2024-11-19 23:41:27 +01:00
Daniel Girtler 20cc124a6d
Fix seat-access selection (#2885)
* Fix seat-access selection

* Format import
2024-11-19 14:50:16 +01:00
BringBack1800s 69b443c1ad
Update hyprland.py (#2879) 2024-11-19 18:49:16 +11:00
summoner001 68a221ea1b
Update hungarian translation (#2884)
Fixing the „custom” word coherence.
Fixing mispelled „zram” -> zRam
2024-11-19 18:48:41 +11:00
codefiles f19f35897b
Add _get_key_file() (#2882) 2024-11-19 18:37:24 +11:00
codefiles 594ca3504f
Fix btrfs skip mount options (#2881) 2024-11-19 18:36:31 +11:00
Odyssey f034c3693c
Update Catalan translation (#2883) 2024-11-19 18:31:11 +11:00
correctmost f3f7700945
Remove unused Any instances from TYPE_CHECKING blocks (#2878)
This will make it easier to start removing more Any instances from
the codebase.
2024-11-19 18:30:41 +11:00
correctmost 3255744278
Sort and format remaining imports (#2877)
This commit also enables isort rules in the Ruff config.
2024-11-19 11:18:47 +11:00
correctmost 955b2cfc3e
Sort and format imports in lib/disk/ (#2875) 2024-11-18 23:29:56 +01:00
correctmost b3421c0a82
Sort and format imports in scripts/, tui/, and top-level files (#2874) 2024-11-19 08:16:38 +11:00
correctmost 0aa6dcc78e
Sort and format imports in examples/ and default_profiles/ (#2873) 2024-11-19 07:54:17 +11:00
Anton Hvornum d3fa738bdd
Changed the invitation link to discord to go from #general to #help channel, as it's not always given how discord works (#2870) 2024-11-19 07:51:33 +11:00
correctmost 7776f82cbc
Reduce the max line length from 220 to 160 by wrapping some lines (#2867)
This will make it easier to auto-format import sections.
2024-11-19 07:50:53 +11:00
utuhiro78 89425912b9
Update Japanese translation (#2869) 2024-11-18 22:01:18 +11:00
correctmost 80b4dab092
Remove remaining Optional and Union usage from the codebase (#2868) 2024-11-18 20:59:08 +11:00
correctmost 97d6d84c3c
Replace most Union[] instances with pipe syntax (#2843) 2024-11-18 20:07:14 +11:00
correctmost 9626965982
Remove remaining deprecated typing.Dict and typing.List usage (#2859) 2024-11-18 20:02:35 +11:00
Daniel Girtler 41e5a0fcfd
Fixes 2849 (#2862) 2024-11-18 09:54:29 +01:00
Daniel Girtler f648eb6d66
Fix 2860 (#2863) 2024-11-18 09:52:32 +01:00
correctmost d79e082837
Remove outdated formatting note from contributors doc (#2857)
Binary operators are formatted consistently as of 6102a08c.
2024-11-18 09:04:44 +11:00
codefiles 6ca80d1fd7
Fix partition creation size end (#2858) 2024-11-18 09:04:12 +11:00
codefiles 8fc3dc4358
Parse lsblk data with pydantic (#2775) 2024-11-18 07:42:09 +11:00
Franco Castillo 74fd463873
Update Spanish translation (#2853) 2024-11-18 07:22:21 +11:00
summoner001 0c9064d6ad
Update Hungarian Translation (#2852)
Translate new strings
2024-11-18 07:22:04 +11:00
codefiles 80bba6c52b
Fix partition creation default size (#2856) 2024-11-18 07:20:12 +11:00
codefiles fad97e8465
Fix EditMenu input text (#2855) 2024-11-18 07:18:42 +11:00
utuhiro78 5aa73a5edc
Update Japanese translation (#2854) 2024-11-18 07:17:49 +11:00
Luna Jernberg 242ecc1ed2
Update base.po (#2848)
Update Swedish translation for 3.0
2024-11-17 12:07:38 +01:00
322 changed files with 57253 additions and 19471 deletions

View File

@ -1,11 +1,8 @@
[flake8]
count = True
# Several of the following could be autofixed or improved by running the code through psf/black
ignore = E722,W191,W503
ignore = W191,W503,E704,E203
max-complexity = 40
max-line-length = 220
max-line-length = 160
show-source = True
statistics = True
builtins = _
per-file-ignores = __init__.py:E128,F401
exclude = .git,__pycache__,build,docs,actions-runner

4
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [archlinux]
custom: ['https://archlinux.org/donate/']

View File

@ -41,8 +41,8 @@ body:
attributes:
value: >
**Note**: Assuming you have network connectivity,
you can easily post the installation log using the following command:
`curl -F'file=@/var/log/archinstall/install.log' https://0x0.st`
you can easily upload the installation log and get a shareable URL by running:
`archinstall share-log`
- type: textarea
id: freeform

View File

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

View File

@ -1,18 +1,18 @@
on: [ push, pull_request ]
name: flake8 linting (3 ignores)
name: flake8 linting
jobs:
flake8:
runs-on: ubuntu-latest
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pyparted python-simple-term-menu pkgconfig gcc
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
- run: pip install --break-system-packages --upgrade pip
# this will install the exact version of flake8 that is in the pyproject.toml file
- name: Install archinstall dependencies

View File

@ -21,16 +21,16 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
- name: Install pre-dependencies
run: |
pacman -Sy --noconfirm tree git python-pyparted python-simple-term-menu 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
- name: Sphinx build
run: |
sphinx-build docs _build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
if: ${{ github.event_name != 'pull_request' }}
with:
publish_branch: gh-pages

View File

@ -26,14 +26,14 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: pwd
- run: find .
- run: cat /etc/os-release
- run: pacman-key --init
- run: pacman --noconfirm -Sy archlinux-keyring
- run: ./build_iso.sh
- uses: actions/upload-artifact@v4
- run: ./test_tooling/mkarchiso/build_iso.sh
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: Arch Live ISO
path: /tmp/archlive/out/*.iso

View File

@ -6,13 +6,13 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pyparted python-simple-term-menu pkgconfig gcc
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
- run: pip install --break-system-packages --upgrade pip
# this will install the exact version of mypy that is in the pyproject.toml file
- name: Install archinstall dependencies

View File

@ -6,15 +6,15 @@ jobs:
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pyparted python-simple-term-menu pkgconfig gcc
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
- run: pip install --break-system-packages --upgrade pip
- name: Install Pylint and Pylint plug-ins
- name: Install Pylint
run: pip install --break-system-packages .[dev]
- run: python --version
- run: pylint --version

View File

@ -7,9 +7,15 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- run: pacman --noconfirm -Syu python python-pip qemu gcc
- run: python -m pip install --break-system-packages --upgrade pip
- run: pip install --break-system-packages pytest
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
- run: pip install --break-system-packages --upgrade pip
- name: Install archinstall dependencies
run: pip install --break-system-packages .[dev]
- name: Test with pytest
run: python -m pytest || exit 0
run: pytest

View File

@ -11,30 +11,29 @@ jobs:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python-pip python-pydantic python-pyparted python-simple-term-menu pkgconfig gcc
- name: Install build dependencies
run: |
python -m pip install --break-system-packages --upgrade pip
pip install --break-system-packages --upgrade build twine wheel setuptools installer
pip uninstall archinstall -y --break-system-packages
pacman --noconfirm -Sy python-uv python-setuptools python-pip
pacman --noconfirm -Sy python-pyparted python-pydantic python-textual
- name: Remove existing archinstall (if any)
run:
uv pip uninstall archinstall --break-system-packages --system
- name: Build archinstall
run: python -m build --wheel --no-isolation
run: uv build --no-build-isolation --wheel
- name: Install archinstall
run: python -m installer dist/*.whl
run: |
uv pip install dist/*.whl --break-system-packages --system --no-build --no-deps
- name: Run archinstall
run: |
python -V
archinstall --script guided -v
archinstall --script swiss -v
archinstall --script only_hd -v
archinstall --script minimal -v
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: archinstall
path: dist/*

View File

@ -1,4 +1,4 @@
# This workflow will upload a Python Package using Twine when a release is created
# This workflow will upload a Python Package when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload archinstall to PyPi
@ -11,23 +11,23 @@ jobs:
deploy:
runs-on: ubuntu-latest
permissions:
# IMPORTANT: this permission is mandatory for Trusted Publishing
id-token: write
container:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Prepare arch
run: |
python -m pip install --upgrade pip
pip install build twine
pacman-key --init
pacman --noconfirm -Sy archlinux-keyring
pacman --noconfirm -Syyu
pacman --noconfirm -Sy python python-uv python-setuptools python-pip python-pyparted python-pydantic python-textual
- name: Build archinstall
run: |
python -m build . --wheel
uv build --no-build-isolation --wheel
- name: Publish archinstall to PyPi
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
twine upload dist/*
uv publish --trusted-publishing always

9
.github/workflows/ruff-format.yaml vendored Normal file
View File

@ -0,0 +1,9 @@
on: [ push, pull_request ]
name: ruff check formatting
jobs:
ruff_format_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
- run: ruff format --diff

8
.github/workflows/ruff-lint.yaml vendored Normal file
View File

@ -0,0 +1,8 @@
on: [ push, pull_request ]
name: ruff check linting
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0

View File

@ -1,12 +0,0 @@
on: [ push, pull_request ]
name: ruff linting
jobs:
ruff:
runs-on: ubuntu-latest
container:
image: archlinux/archlinux:latest
steps:
- uses: actions/checkout@v4
- run: pacman --noconfirm -Syu ruff
- name: Lint with ruff
run: ruff check

View File

@ -1,28 +1,22 @@
#on:
# push:
# paths:
# - 'archinstall/locales/**'
# pull_request:
# paths:
# - 'archinstall/locales/**'
#name: Verify local_generate script was run on translation changes
#jobs:
# translation-check:
# runs-on: ubuntu-latest
# container:
# image: archlinux/archlinux:latest
# steps:
# - uses: actions/checkout@v4
# - run: pacman --noconfirm -Syu python git diffutils
# - name: Verify all translation scripts are up to date
# run: |
# cd ..
# cp -r archinstall archinstall_orig
# cd archinstall/archinstall/locales
# bash locales_generator.sh 1> /dev/null
# cd ../../..
# git diff \
# --quiet --no-index --name-only \
# archinstall_orig/archinstall/locales \
# archinstall/archinstall/locales \
# || (echo "Translation files have not been updated after translation, please run ./locales_generator.sh once more and commit" && exit 1)
name: Translation validation
on:
push:
paths:
- 'archinstall/**/*.py'
- 'archinstall/locales/**'
- '.github/workflows/translation-check.yaml'
pull_request:
paths:
- 'archinstall/**/*.py'
- 'archinstall/locales/**'
- '.github/workflows/translation-check.yaml'
jobs:
translations:
name: Validate translations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install gettext
run: sudo apt-get update && sudo apt-get install -y gettext
- name: Run translation checks
run: bash archinstall/locales/locales_generator.sh check

40
.github/workflows/uki-build.yaml vendored Normal file
View File

@ -0,0 +1,40 @@
# This workflow will build an Arch Linux UKI file with the commit on it
name: Build Arch UKI with ArchInstall Commit
on:
push:
branches:
- master
- main # In case we adopt this convention in the future
pull_request:
paths-ignore:
- 'docs/**'
- '**.editorconfig'
- '**.gitignore'
- '**.md'
- 'LICENSE'
- 'PKGBUILD'
release:
types:
- created
jobs:
build:
runs-on: ubuntu-latest
container:
image: archlinux/archlinux:latest
options: --privileged
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- run: pwd
- run: find .
- run: cat /etc/os-release
- run: pacman-key --init
- run: pacman --noconfirm -Sy archlinux-keyring
- run: pacman --noconfirm -Sy mkosi
- run: (cd test_tooling/mkosi/ && mkosi build -B)
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: Arch Live UKI
path: test_tooling/mkosi/mkosi.output/*.efi

6
.gitignore vendored
View File

@ -39,3 +39,9 @@ requirements.txt
/.gitconfig
/actions-runner
/cmd_output.txt
node_modules/
uv.lock
test_tooling/mkosi/mkosi.output/*image*
test_tooling/mkosi/mkosi.cache/**
test_tooling/mkosi/mkosi.tools/**
test_tooling/mkosi/mkosi.tools.manifest

View File

@ -36,7 +36,7 @@ flake8:
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# We currently do not have unit tests implemented but this stage is written in anticipation of their future usage.
# When a stage name is preceeded with a '.' it's treated as "disabled" by GitLab and is not executed, so it's fine for it to be declared.
# When a stage name is preceded with a '.' it's treated as "disabled" by GitLab and is not executed, so it's fine for it to be declared.
.pytest:
stage: test
tags:

View File

@ -1,19 +1,17 @@
default_stages: ['commit']
default_stages: ['pre-commit']
repos:
- repo: https://github.com/pycqa/autoflake
rev: v2.3.1
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.14
hooks:
- id: autoflake
args: [
'--in-place',
'--remove-all-unused-imports',
'--ignore-init-module-imports'
]
files: \.py$
require_serial: true
fail_fast: true
# fix unused imports and sort them
- id: ruff
args: ["--extend-select", "I", "--fix"]
# format the code
- id: ruff-format
# run the linter
- id: ruff
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
# general hooks:
- id: check-added-large-files # Prevent giant files from being committed
@ -23,19 +21,17 @@ repos:
- id: check-yaml # Attempts to load all yaml files to verify syntax
- id: destroyed-symlinks # Detects symlinks which are changed to regular files
- id: detect-private-key # Checks for the existence of private keys
- id: end-of-file-fixer # Makes sure files end in a newline and only a newline
- id: trailing-whitespace # Trims trailing whitespace
# Python specific hooks:
- id: check-ast # Simply check whether files parse as valid python
- id: check-docstring-first # Checks for a common error of placing code before the docstring
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
rev: 7.3.0
hooks:
- id: flake8
args: [--config=.flake8]
fail_fast: true
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v2.1.0
hooks:
- id: mypy
args: [
@ -44,11 +40,10 @@ repos:
fail_fast: true
additional_dependencies:
- pydantic
- pydantic-settings
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.4
hooks:
- id: ruff
- pytest
- hypothesis
- cryptography
- textual
- repo: local
hooks:
- id: pylint

View File

@ -12,4 +12,4 @@ sphinx:
build:
os: "ubuntu-22.04"
tools:
python: "3.11"
python: "3.12"

View File

@ -16,7 +16,7 @@ Patch releases will be done against their own branches, branched from stable tag
## Discussions
Currently, questions, bugs and suggestions should be reported through [GitHub issue tracker](https://github.com/archlinux/archinstall/issues).<br>
For less formal discussions there is also an [archinstall Discord server](https://discord.gg/cqXU88y).
For less formal discussions there is also an [archinstall Discord server](https://discord.gg/aDeMffrxNg).
## Coding convention
@ -27,9 +27,7 @@ The exceptions to PEP8 are:
* Archinstall uses [tabs instead of spaces](https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces) simply to make it
easier for non-IDE developers to navigate the code *(Tab display-width should be equal to 4 spaces)*. Exception to the
rule are comments that need fine-tuned indentation for documentation purposes.
* [Line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) a maximum line length is enforced via flake8 with 220 characters
* [Line breaks before/after binary operator](https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator)
is not enforced, as long as the style of line breaks is consistent within the same code block.
* [Line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) a maximum line length is enforced via flake8 with 160 characters
* Archinstall should always be saved with **Unix-formatted line endings** and no other platform-specific formats.
* [String quotes](https://www.python.org/dev/peps/pep-0008/#string-quotes) follow PEP8, the exception being when
creating formatted strings, double-quoted strings are *preferred* but not required on the outer edges *(
@ -70,15 +68,4 @@ squash commits when the pull request is merged.
Maintainer:
* Anton Hvornum ([@Torxed](https://github.com/Torxed))
At present the current contributors are (alphabetically):
* Anton Hvornum ([@Torxed](https://github.com/Torxed))
* Borislav Kosharov ([@nikibobi](https://github.com/nikibobi))
* demostanis ([@demostanis](https://github.com/demostanis))
* Dylan Taylor ([@dylanmtaylor](https://github.com/dylanmtaylor))
* Giancarlo Razzolini (@[grazzolini](https://github.com/grazzolini))
* Jerker Bengtsson ([@jaybent](https://github.com/jaybent))
* j-james ([@j-james](https://github.com/j-james))
* Ninchester ([@ninchester](https://github.com/ninchester))
* nullrequest ([@advaithm](https://github.com/advaithm))
* Philipp Schaffrath ([@phisch](https://github.com/phisch))
* Varun Madiath ([@vamega](https://github.com/vamega))
[Contributors](https://github.com/archlinux/archinstall/graphs/contributors)

View File

@ -1,15 +1,16 @@
# Maintainer: David Runge <dvzrv@archlinux.org>
# Maintainer: Giancarlo Razzolini <grazzolini@archlinux.org>
# Maintainer: Anton Hvornum <torxed@archlinux.org>
# Contributor: Anton Hvornum <anton@hvornum.se>
# Contributor: demostanis worlds <demostanis@protonmail.com>
pkgname=archinstall
pkgver=3.0.0
pkgver=4.3
pkgrel=1
pkgdesc="Just another guided/automated Arch Linux installer with a twist"
arch=(any)
url="https://github.com/archlinux/archinstall"
license=(GPL3)
license=(GPL-3.0-only)
depends=(
'arch-install-scripts'
'btrfs-progs'
@ -19,26 +20,34 @@ depends=(
'e2fsprogs'
'glibc'
'kbd'
'libcrypt.so'
'libxcrypt'
'pciutils'
'procps-ng'
'python'
'python-cryptography'
'python-pydantic'
'python-pyparted'
'python-textual'
'python-markdown-it-py'
'python-linkify-it-py'
'systemd'
'util-linux'
'xfsprogs'
'lvm2'
'f2fs-tools'
'ntfs-3g'
'reiserfsprogs'
'libfido2'
)
makedepends=(
'python-setuptools'
'python-sphinx'
'python-build'
'python-installer'
'python-setuptools'
'python-sphinx'
'python-wheel'
'python-sphinx_rtd_theme'
'python-pylint'
'python-pylint-pydantic'
'ruff'
)
optdepends=(
'python-systemd: Adds journald logging'
@ -47,14 +56,17 @@ provides=(python-archinstall archinstall)
conflicts=(python-archinstall archinstall-git)
replaces=(python-archinstall archinstall-git)
source=(
$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz
$pkgname-$pkgver.tar.gz.sig::$url/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz.sig
$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/$pkgver.tar.gz
$pkgname-$pkgver.tar.gz.sig::$url/releases/download/$pkgver/$pkgname-$pkgver.tar.gz.sig
)
sha512sums=('64cb3593c5091b3885ad14ef073cfab31090b4f9bcb4405b18cf9b19adb5ca42255ba8891ec62e21f92d59872541ef6d94f186fb05c625822af63525441e08d9'
'SKIP')
b2sums=('9c0ec0871841804377ba8310dc744711adcec4eed7319a8d89d684af8e7b822bb9d47540b00f4d746a9fcd7b9ea1b9e07bac773e6c28fabc760e4df38b16748b'
'SKIP')
validpgpkeys=('256F73CEEFC6705C6BBAB20E5FBBB32941E3740A') # Anton Hvornum (Torxed) <anton@hvornum.se>
sha512sums=()
b2sums=()
validpgpkeys=('8AA2213C8464C82D879C8127D4B58E897A929F2E') # torxed@archlinux.org
check() {
cd $pkgname-$pkgver
ruff check
}
pkgver() {
cd $pkgname-$pkgver
@ -62,13 +74,6 @@ pkgver() {
awk '$1 ~ /^__version__/ {gsub("\"", ""); print $3}' archinstall/__init__.py
}
prepare() {
cd $pkgname-$pkgver
# use real directories for examples and profiles, as symlinks do not work
rm -fv $pkgname/{examples,profiles}
}
build() {
cd $pkgname-$pkgver

191
README.md
View File

@ -6,36 +6,61 @@
[![Lint Python and Find Syntax Errors](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml/badge.svg)](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml)
Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist.
The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium)*.
The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium or from an existing installation)*.
* archinstall [discord](https://discord.gg/cqXU88y) server
* archinstall [matrix.org](https://app.element.io/#/room/#archinstall:matrix.org) channel
* archinstall [#archinstall@irc.libera.chat](irc://#archinstall@irc.libera.chat:6697)
* archinstall [discord](https://discord.gg/aDeMffrxNg) server
* archinstall [#archinstall:matrix.org](https://matrix.to/#/#archinstall:matrix.org) Matrix channel
* archinstall [#archinstall@irc.libera.chat:6697](https://web.libera.chat/?channel=#archinstall)
* archinstall [documentation](https://archinstall.archlinux.page/)
# Installation & Usage
> [!TIP]
> In the ISO you are root by default. Use sudo if running from an existing system.
```shell
sudo pacman -S archinstall
```
Alternative ways to install are `git clone` the repository or `pip install --upgrade archinstall`.
## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer
Assuming you are on an Arch Linux live-ISO or installed via `pip`:
```shell
pacman-key --init
pacman -Sy archinstall
archinstall
```
## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git`
Alternative ways to install are `git clone` the repository (and is better since you get the latest code regardless of [build date](https://archlinux.org/packages/?sort=&q=archinstall)) or `pip install --upgrade archinstall`.
## Upgrade `archinstall` on live Arch ISO image
Upgrading archinstall on the ISO needs to be done via a full system upgrade using
```shell
# cd archinstall-git
# python -m archinstall
pacman -Syu
```
When booting from a live USB, the space on the ramdisk is limited and may not be sufficient to allow running a re-installation or upgrade of the installer.
In case one runs into this issue, any of the following can be used
* Resize the root partition https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_the_root_file_system
* Specify the boot parameter copytoram=y (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26) which will copy the root filesystem to tmpfs
## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer
Assuming you are on an Arch Linux live-ISO or installed via `pip`, `archinstall` will use the `guided` script by default
```shell
archinstall
```
similar goes for running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git
```shell
git clone https://github.com/archlinux/archinstall
cd archinstall
python -m archinstall $@
```
To run alternative scripts using the `--script` parameter
```
archinstall --script <name>
```
#### Advanced
Some additional options that most users do not need are hidden behind the `--advanced` flag.
Some additional options that most users do not need are hidden behind the `--advanced` flag and all options/args can be consulted through `-h` or `--help`.
## Running from a declarative configuration file or URL
@ -56,17 +81,29 @@ To load the configuration file into `archinstall` run the following command
archinstall --config <path to user config file or URL> --creds <path to user credentials config file or URL>
```
### Credentials configuration file encryption
By default, all user account credentials are hashed with `yescrypt` and only the hash is stored in the saved `user_credentials.json` file.
This is not possible for disk encryption password which needs to be stored in plaintext to be able to apply it.
However, when selecting to save configuration files, `archinstall` will prompt for the option to encrypt the `user_credentials.json` file content.
A prompt will require to enter a encryption password to encrypt the file. When providing an encrypted `user_configuration.json` as a argument with `--creds <user_credentials.json>`
there are multiple ways to provide the decryption key:
* Provide the decryption key via the command line argument `--creds-decryption-key <password>`
* Store the encryption key in the environment variable `ARCHINSTALL_CREDS_DECRYPTION_KEY` which will be read automatically
* If none of the above is provided a prompt will be shown to enter the decryption key manually
# Help or Issues
If you come across any issues, kindly submit your issue here on Github or post your query in the
[discord](https://discord.gg/cqXU88y) help channel.
If you come across any issues, kindly submit your issue here on GitHub or post your query in the
[discord](https://discord.gg/aDeMffrxNg) help channel.
When submitting an issue, please:
* Provide the stacktrace of the output if applicable
* Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you!
* To extract the log from the ISO image, one way is to use<br>
* To upload the log from the ISO image and get a shareable URL, run<br>
```shell
curl -F'file=@/var/log/archinstall/install.log' https://0x0.st
archinstall share-log
```
@ -92,67 +129,9 @@ All available console fonts can be found in `/usr/share/kbd/consolefonts` and se
## Scripting interactive installation
There are some examples in the `examples/` directory that should serve as a starting point.
For an example of a fully scripted, interactive installation please refer to the example
[interactive_installation.py](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py)
The following is a small example of how to script your own *interactive* installation:
```python
from pathlib import Path
from archinstall import Installer, ProfileConfiguration, profile_handler, User
from archinstall.default_profiles.minimal import MinimalProfile
from archinstall.lib.disk.device_model import FilesystemType
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
from archinstall.lib.disk.filesystem import FilesystemHandler
from archinstall.lib.interactions.disk_conf import select_disk_config
fs_type = FilesystemType('ext4')
# Select a device to use for the installation
disk_config = select_disk_config()
# Optional: ask for disk encryption configuration
data_store = {}
disk_encryption = DiskEncryptionMenu(disk_config.device_modifications, data_store).run()
# initiate file handler with the disk config and the optional disk encryption config
fs_handler = FilesystemHandler(disk_config, disk_encryption)
# perform all file operations
# WARNING: this will potentially format the filesystem and delete all data
fs_handler.perform_filesystem_operations()
mountpoint = Path('/tmp')
with Installer(
mountpoint,
disk_config,
disk_encryption=disk_encryption,
kernels=['linux']
) as installation:
installation.mount_ordered_layout()
installation.minimal_installation(hostname='minimal-arch')
installation.add_additional_packages(['nano', 'wget', 'git'])
# Optionally, install a profile of choice.
# In this case, we install a minimal profile that is empty
profile_config = ProfileConfiguration(MinimalProfile())
profile_handler.install_profile_config(installation, profile_config)
user = User('archinstall', 'password', True)
installation.create_users(user)
```
This installer will perform the following actions:
* Prompt the user to configure the disk partitioning
* Prompt the user to setup disk encryption
* Create a file handler instance for the configured disk and the optional disk encryption
* Perform the disk operations (WARNING: this will potentially format the disks and erase all data)
* Install a basic instance of Arch Linux *(base base-devel linux linux-firmware btrfs-progs efibootmgr)*
* Install and configures a bootloader to partition 0 on UEFI. On BIOS, it sets the root to partition 0.
* Install additional packages *(nano, wget, git)*
* Create a new user
> **To create your own ISO with this script in it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on creating your own ISO.
@ -161,14 +140,6 @@ This installer will perform the following actions:
For an example of a fully scripted, automated installation please refer to the example
[full_automated_installation.py](https://github.com/archlinux/archinstall/blob/master/examples/full_automated_installation.py)
## Unattended installation based on MAC address
Archinstall comes with an [unattended](https://github.com/archlinux/archinstall/blob/master/examples/mac_address_installation.py)
example which will look for a matching profile for the machine it is being run on, based on any local MAC address.
For instance, if the machine the code is executed on has the MAC address `52:54:00:12:34:56` it will look for a profile called
[52-54-00-12-34-56.py](https://github.com/archlinux/archinstall/blob/master/archinstall/default_profiles/tailored.py).
If it's found, the unattended installation will begin and source that profile as its installation procedure.
# Profiles
`archinstall` comes with a set of pre-configured profiles available for selection during the installation process.
@ -187,12 +158,6 @@ The profiles' definitions and the packages they will install can be directly vie
If you want to test a commit, branch, or bleeding edge release from the repository using the standard Arch Linux Live ISO image,
replace the archinstall version with a newer one and execute the subsequent steps defined below.
*Note: When booting from a live USB, the space on the ramdisk is limited and may not be sufficient to allow
running a re-installation or upgrade of the installer. In case one runs into this issue, any of the following can be used
- Resize the root partition https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_the_root_file_system
- The boot parameter `copytoram=y` (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26)
can be specified which will copy the root filesystem to tmpfs.*
1. You need a working network connection
2. Install the build requirements with `pacman -Sy; pacman -S git python-pip gcc pkgconf`
*(note that this may or may not work depending on your RAM and current state of the squashfs maximum filesystem free space)*
@ -211,10 +176,10 @@ To test this without a live ISO, the simplest approach is to use a local image a
This can be done by installing `pacman -S arch-install-scripts util-linux` locally and doing the following:
# truncate -s 20G testimage.img
# losetup --partscan --show --find ./testimage.img
# losetup --partscan --show ./testimage.img
# pip install --upgrade archinstall
# python -m archinstall --script guided
# qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_VARS.fd
# qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd
This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.<br>
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~<br>
@ -223,9 +188,41 @@ This will create a *20 GB* `testimage.img` and create a loop device which we can
There's also a [Building and Testing](https://github.com/archlinux/archinstall/wiki/Building-and-Testing) guide.<br>
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch.
## Boot an Arch ISO image in a VM
You may want to boot an ISO image in a VM to test `archinstall` in there.
* Download the latest [Arch ISO](https://archlinux.org/download/)
* Use the the below command to boot the ISO in a VM
```
qemu-system-x86_64 -enable-kvm \
-machine q35,accel=kvm -device intel-iommu \
-cpu host -m 4096 -boot order=d \
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw
```
HINT: For espeakup support
```
qemu-system-x86_64 -enable-kvm \
-machine q35,accel=kvm -device intel-iommu \
-cpu host -m 4096 -boot order=d \
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw \
-device intel-hda -device hda-duplex,audiodev=snd0 \
-audiodev pa,id=snd0,server=/run/user/1000/pulse/native
```
# FAQ
## AUR
`archinstall` will not offer or bundle AUR helpers or AUR packages due to a current consensus. This is not any individual developers decision. The reasons and discussions for this stance on the topic can be found on our mailing list thread: [(optional) AUR helper in archinstall](https://lists.archlinux.org/archives/list/arch-dev-public@lists.archlinux.org/thread/VYOULH2GOJLFM2BXOFLWH3D754YXFPSL/).
## Keyring out-of-date
For a description of the problem see https://archinstall.archlinux.page/help/known_issues.html#keyring-is-out-of-date-2213 and discussion in issue https://github.com/archlinux/archinstall/issues/2213.

View File

@ -1,355 +1,3 @@
"""Arch Linux installer - guided, templates etc."""
import importlib
import os
import sys
import time
import curses
import traceback
from argparse import ArgumentParser, Namespace
from pathlib import Path
from typing import TYPE_CHECKING, Any, Union
from archinstall.lib.plugins import plugin
from .lib import disk
from .lib import models
from .lib import packages
from .lib import exceptions
from .lib import luks
from .lib import locale
from .lib import mirrors
from .lib import networking
from .lib import profile
from .lib import interactions
from . import default_profiles
from .lib.hardware import SysInfo, GfxDriver
from .lib.installer import Installer, accessibility_tools_in_use
from .lib.output import FormattedOutput, log, error, debug, warn, info
from .lib.pacman import Pacman
from .lib.storage import storage
from .lib.global_menu import GlobalMenu
from .lib.boot import Boot
from .lib.translationhandler import TranslationHandler, Language, DeferredTranslation
from .lib.plugins import plugins, load_plugin
from .lib.configuration import ConfigurationOutput
from .tui import Tui
from .lib.general import (
generate_password, locate_binary, clear_vt100_escape_codes,
JSON, UNSAFE_JSON, SysCommandWorker, SysCommand,
run_custom_user_commands, json_stream_to_structure, secret
)
if TYPE_CHECKING:
_: Any
__version__ = "3.0.0"
storage['__version__'] = __version__
# add the custom _ as a builtin, it can now be used anywhere in the
# project to mark strings as translatable with _('translate me')
DeferredTranslation.install()
# Log various information about hardware before starting the installation. This might assist in troubleshooting
debug(f"Hardware model detected: {SysInfo.sys_vendor()} {SysInfo.product_name()}; UEFI mode: {SysInfo.has_uefi()}")
debug(f"Processor model detected: {SysInfo.cpu_model()}")
debug(f"Memory statistics: {SysInfo.mem_available()} available out of {SysInfo.mem_total()} total installed")
debug(f"Virtualization detected: {SysInfo.virtualization()}; is VM: {SysInfo.is_vm()}")
debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}")
# For support reasons, we'll log the disk layout pre installation to match against post-installation layout
debug(f"Disk states before installing: {disk.disk_layouts()}")
parser = ArgumentParser()
def define_arguments() -> None:
"""
Define which explicit arguments do we allow.
Refer to https://docs.python.org/3/library/argparse.html for documentation and
https://docs.python.org/3/howto/argparse.html for a tutorial
Remember that the property/entry name python assigns to the parameters is the first string defined as argument and
dashes inside it '-' are changed to '_'
"""
parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__)
parser.add_argument("--config", nargs="?", help="JSON configuration file or URL")
parser.add_argument("--creds", nargs="?", help="JSON credentials configuration file")
parser.add_argument("--silent", action="store_true",
help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored")
parser.add_argument("--dry-run", "--dry_run", action="store_true",
help="Generates a configuration file and then exits instead of performing an installation")
parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str)
parser.add_argument("--mount-point", "--mount_point", nargs="?", type=str,
help="Define an alternate mount point for installation")
parser.add_argument("--skip-ntp", action="store_true", help="Disables NTP checks during installation", default=False)
parser.add_argument("--debug", action="store_true", default=False, help="Adds debug info into the log")
parser.add_argument("--offline", action="store_true", default=False,
help="Disabled online upstream services such as package search and key-ring auto update.")
parser.add_argument("--no-pkg-lookups", action="store_true", default=False,
help="Disabled package validation specifically prior to starting installation.")
parser.add_argument("--plugin", nargs="?", type=str)
parser.add_argument("--skip-version-check", action="store_true",
help="Skip the version check when running archinstall")
if 'sphinx' not in sys.modules:
if '--help' in sys.argv or '-h' in sys.argv:
define_arguments()
parser.print_help()
exit(0)
if os.getuid() != 0:
print(_("Archinstall requires root privileges to run. See --help for more."))
exit(1)
def parse_unspecified_argument_list(unknowns: list, multiple: bool = False, err: bool = False) -> dict: # type: ignore[type-arg]
"""We accept arguments not defined to the parser. (arguments "ad hoc").
Internally argparse return to us a list of words so we have to parse its contents, manually.
We accept following individual syntax for each argument
--argument value
--argument=value
--argument = value
--argument (boolean as default)
the optional parameters to the function alter a bit its behaviour:
* multiple allows multivalued arguments, each value separated by whitespace. They're returned as a list
* error. If set any non correctly specified argument-value pair to raise an exception. Else, simply notifies the existence of a problem and continues processing.
To a certain extent, multiple and error are incompatible. In fact, the only error this routine can catch, as of now, is the event
argument value value ...
which isn't am error if multiple is specified
"""
tmp_list = [arg for arg in unknowns if arg != "="] # wastes a few bytes, but avoids any collateral effect of the destructive nature of the pop method()
config = {}
key = None
last_key = None
while tmp_list:
element = tmp_list.pop(0) # retrieve an element of the list
if element.startswith('--'): # is an argument ?
if '=' in element: # uses the arg=value syntax ?
key, value = [x.strip() for x in element[2:].split('=', 1)]
config[key] = value
last_key = key # for multiple handling
key = None # we have the kwy value pair we need
else:
key = element[2:]
config[key] = True # every argument starts its lifecycle as boolean
elif key:
config[key] = element
last_key = key # multiple
key = None
elif multiple and last_key:
if isinstance(config[last_key], str):
config[last_key] = [config[last_key], element]
else:
config[last_key].append(element)
elif err:
raise ValueError(f"Entry {element} is not related to any argument")
else:
print(f" We ignore the entry {element} as it isn't related to any argument")
return config
def cleanup_empty_args(args: Union[Namespace, dict]) -> dict: # type: ignore[type-arg]
"""
Takes arguments (dictionary or argparse Namespace) and removes any
None values. This ensures clean mergers during dict.update(args)
"""
if type(args) is Namespace:
args = vars(args)
clean_args = {}
for key, val in args.items():
if isinstance(val, dict):
val = cleanup_empty_args(val)
if val is not None:
clean_args[key] = val
return clean_args
def get_arguments() -> dict[str, Any]:
""" The handling of parameters from the command line
Is done on following steps:
0) we create a dict to store the arguments and their values
1) preprocess.
We take those arguments which use JSON files, and read them into the argument dict. So each first level entry becomes a argument on it's own right
2) Load.
We convert the predefined argument list directly into the dict via the vars() function. Non specified arguments are loaded with value None or false if they are booleans (action="store_true").
The name is chosen according to argparse conventions. See above (the first text is used as argument name, but underscore substitutes dash)
We then load all the undefined arguments. In this case the names are taken as written.
Important. This way explicit command line arguments take precedence over configuration files.
3) Amend
Change whatever is needed on the configuration dictionary (it could be done in post_process_arguments but this ougth to be left to changes anywhere else in the code, not in the arguments dictionary
"""
config: dict[str, Any] = {}
args, unknowns = parser.parse_known_args()
# preprocess the JSON files.
# TODO Expand the url access to the other JSON file arguments ?
if args.config is not None:
if not json_stream_to_structure('--config', args.config, config):
exit(1)
if args.creds is not None:
if not json_stream_to_structure('--creds', args.creds, config):
exit(1)
# load the parameters. first the known, then the unknowns
clean_args = cleanup_empty_args(args)
config.update(clean_args)
config.update(parse_unspecified_argument_list(unknowns))
# amend the parameters (check internal consistency)
# Installation can't be silent if config is not passed
if clean_args.get('config') is None:
config["silent"] = False
else:
config["silent"] = clean_args.get('silent')
# avoiding a compatibility issue
if 'dry-run' in config:
del config['dry-run']
return config
def load_config() -> None:
"""
refine and set some arguments. Formerly at the scripts
"""
from .lib.models import NetworkConfiguration
arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments)
if (archinstall_lang := arguments.get('archinstall-language', None)) is not None:
arguments['archinstall-language'] = TranslationHandler().get_language_by_name(archinstall_lang)
if disk_config := arguments.get('disk_config', {}):
arguments['disk_config'] = disk.DiskLayoutConfiguration.parse_arg(disk_config)
if profile_config := arguments.get('profile_config', None):
arguments['profile_config'] = profile.ProfileConfiguration.parse_arg(profile_config)
if mirror_config := arguments.get('mirror_config', None):
arguments['mirror_config'] = mirrors.MirrorConfiguration.parse_args(mirror_config)
if arguments.get('servers', None) is not None:
storage['_selected_servers'] = arguments.get('servers', None)
if (net_config := arguments.get('network_config', None)) is not None:
config = NetworkConfiguration.parse_arg(net_config)
arguments['network_config'] = config
if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None:
users = arguments.get('!users', None)
superusers = arguments.get('!superusers', None)
arguments['!users'] = models.User.parse_arguments(users, superusers)
if arguments.get('bootloader', None) is not None:
arguments['bootloader'] = models.Bootloader.from_arg(arguments['bootloader'])
if arguments.get('uki') and not arguments['bootloader'].has_uki_support():
arguments['uki'] = False
if arguments.get('audio_config', None) is not None:
arguments['audio_config'] = models.AudioConfiguration.parse_arg(arguments['audio_config'])
if arguments.get('disk_encryption', None) is not None and disk_config is not None:
arguments['disk_encryption'] = disk.DiskEncryption.parse_arg(
arguments['disk_config'],
arguments['disk_encryption'],
arguments.get('encryption_password', '')
)
def post_process_arguments(arguments: dict[str, Any]) -> None:
storage['arguments'] = arguments
if mountpoint := arguments.get('mount_point', None):
storage['MOUNT_POINT'] = Path(mountpoint)
if arguments.get('debug', False):
warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!")
if arguments.get('plugin', None):
path = arguments['plugin']
load_plugin(path)
load_config()
define_arguments()
arguments: dict[str, Any] = get_arguments()
post_process_arguments(arguments)
# @archinstall.plugin decorator hook to programmatically add
# plugins in runtime. Useful in profiles_bck and other things.
def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
plugins[f.__name__] = f
def _check_new_version() -> None:
info("Checking version...")
try:
Pacman.run("-Sy")
except Exception as e:
debug(f'Failed to perform version check: {e}')
info('Arch Linux mirrors are not reachable. Please check your internet connection')
exit(1)
upgrade = None
try:
upgrade = Pacman.run("-Qu archinstall").decode()
except Exception as e:
debug(f'Failed determine pacman version: {e}')
if upgrade:
text = f'New version available: {upgrade}'
info(text)
time.sleep(3)
def main() -> None:
"""
This can either be run as the compiled and installed application: python setup.py install
OR straight as a module: python -m archinstall
In any case we will be attempting to load the provided script to be run from the scripts/ folder
"""
if not arguments.get('skip_version_check', False):
_check_new_version()
script = arguments.get('script', None)
if script is None:
print('No script to run provided')
mod_name = f'archinstall.scripts.{script}'
# by loading the module we'll automatically run the script
importlib.import_module(mod_name)
def run_as_a_module() -> None:
exc = None
try:
main()
except Exception as e:
exc = e
finally:
# restore the terminal to the original state
Tui.shutdown()
if exc:
err = ''.join(traceback.format_exception(exc))
error(err)
text = (
'Archinstall experienced the above error. If you think this is a bug, please report it to\n'
'https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log".\n\n'
'Hint: To extract the log from a live ISO \ncurl -F\'file=@/var/log/archinstall/install.log\' https://0x0.st\n'
)
warn(text)
exit(1)
__all__ = ['plugin']

View File

@ -1,19 +1,6 @@
import importlib
import sys
import pathlib
# Load .git version before the builtin version
if pathlib.Path('./archinstall/__init__.py').absolute().exists():
spec = importlib.util.spec_from_file_location("archinstall", "./archinstall/__init__.py")
if spec is None or spec.loader is None:
raise ValueError('Could not retrieve spec from file: archinstall/__init__.py')
archinstall = importlib.util.module_from_spec(spec)
sys.modules["archinstall"] = archinstall
spec.loader.exec_module(archinstall)
else:
import archinstall
from archinstall.main import main
if __name__ == '__main__':
archinstall.run_as_a_module()
sys.exit(main())

View File

@ -0,0 +1,80 @@
from typing import TYPE_CHECKING
from archinstall.lib.hardware import SysInfo
from archinstall.lib.log import debug
from archinstall.lib.models.application import Audio, AudioConfiguration
from archinstall.lib.models.users import User
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class AudioApp:
@property
def pulseaudio_packages(self) -> list[str]:
return [
'pulseaudio',
]
@property
def pipewire_packages(self) -> list[str]:
return [
'pipewire',
'pipewire-alsa',
'pipewire-jack',
'pipewire-pulse',
'gst-plugin-pipewire',
'libpulse',
'wireplumber',
]
def _enable_pipewire(
self,
install_session: Installer,
users: list[User] | None = None,
) -> None:
if users is None:
return
for user in users:
# Create the full path for enabling the pipewire systemd items
service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants'
service_dir.mkdir(parents=True, exist_ok=True)
# Set ownership of the entire user catalogue
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
# symlink in the correct pipewire systemd items
install_session.arch_chroot(
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service',
run_as=user.username,
)
install_session.arch_chroot(
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket',
run_as=user.username,
)
def install(
self,
install_session: Installer,
audio_config: AudioConfiguration,
users: list[User] | None = None,
) -> None:
debug(f'Installing audio server: {audio_config.audio.value}')
if audio_config.audio == Audio.NO_AUDIO:
debug('No audio server selected, skipping installation.')
return
if SysInfo.requires_sof_fw():
install_session.add_additional_packages('sof-firmware')
if SysInfo.requires_alsa_fw():
install_session.add_additional_packages('alsa-firmware')
match audio_config.audio:
case Audio.PIPEWIRE:
install_session.add_additional_packages(self.pipewire_packages)
self._enable_pipewire(install_session, users)
case Audio.PULSEAUDIO:
install_session.add_additional_packages(self.pulseaudio_packages)

View File

@ -0,0 +1,26 @@
from typing import TYPE_CHECKING
from archinstall.lib.log import debug
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class BluetoothApp:
@property
def packages(self) -> list[str]:
return [
'bluez',
'bluez-utils',
]
@property
def services(self) -> list[str]:
return [
'bluetooth.service',
]
def install(self, install_session: Installer) -> None:
debug('Installing Bluetooth')
install_session.add_additional_packages(self.packages)
install_session.enable_service(self.services)

View File

@ -0,0 +1,52 @@
from typing import TYPE_CHECKING
from archinstall.lib.log import debug
from archinstall.lib.models.application import Firewall, FirewallConfiguration
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class FirewallApp:
@property
def ufw_packages(self) -> list[str]:
return [
'ufw',
]
@property
def fwd_packages(self) -> list[str]:
return [
'firewalld',
]
@property
def ufw_services(self) -> list[str]:
return [
'ufw.service',
]
@property
def fwd_services(self) -> list[str]:
return [
'firewalld.service',
]
def install(
self,
install_session: Installer,
firewall_config: FirewallConfiguration,
) -> None:
debug(f'Installing firewall: {firewall_config.firewall.value}')
match firewall_config.firewall:
case Firewall.UFW:
install_session.add_additional_packages(self.ufw_packages)
install_session.enable_service(self.ufw_services)
# write default conf file to enabled
ufw_conf = install_session.target / 'etc/ufw/ufw.conf'
ufw_conf.write_text(ufw_conf.read_text().replace('ENABLED=no', 'ENABLED=yes'))
case Firewall.FWD:
install_session.add_additional_packages(self.fwd_packages)
install_session.enable_service(self.fwd_services)

View File

@ -0,0 +1,14 @@
from typing import TYPE_CHECKING
from archinstall.lib.log import debug
from archinstall.lib.models.application import FontsConfiguration
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class FontsApp:
def install(self, install_session: Installer, fonts_config: FontsConfiguration) -> None:
packages = [f.value for f in fonts_config.fonts]
debug(f'Installing fonts: {packages}')
install_session.add_additional_packages(packages)

View File

@ -0,0 +1,49 @@
from typing import TYPE_CHECKING
from archinstall.lib.log import debug
from archinstall.lib.models.application import PowerManagement, PowerManagementConfiguration
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class PowerManagementApp:
@property
def ppd_packages(self) -> list[str]:
return [
'power-profiles-daemon',
]
@property
def tuned_packages(self) -> list[str]:
return [
'tuned',
'tuned-ppd',
]
@property
def ppd_services(self) -> list[str]:
return [
'power-profiles-daemon.service',
]
@property
def tuned_services(self) -> list[str]:
return [
'tuned.service',
]
def install(
self,
install_session: Installer,
power_management_config: PowerManagementConfiguration,
) -> None:
debug(f'Installing power management daemon: {power_management_config.power_management.value}')
match power_management_config.power_management:
case PowerManagement.POWER_PROFILES_DAEMON:
install_session.add_additional_packages(self.ppd_packages)
install_session.enable_service(self.ppd_services)
case PowerManagement.TUNED:
install_session.add_additional_packages(self.tuned_packages)
install_session.enable_service(self.tuned_services)

View File

@ -0,0 +1,23 @@
from typing import TYPE_CHECKING
from archinstall.lib.log import debug
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class PrintServiceApp:
@property
def packages(self) -> list[str]:
return ['cups', 'system-config-printer', 'cups-pk-helper']
@property
def services(self) -> list[str]:
return [
'cups.service',
]
def install(self, install_session: Installer) -> None:
debug('Installing print service')
install_session.add_additional_packages(self.packages)
install_session.enable_service(self.services)

View File

@ -1,49 +0,0 @@
from typing import Union, Any, TYPE_CHECKING
import archinstall
from archinstall.default_profiles.profile import Profile, ProfileType
from archinstall.lib.models import User
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
class PipewireProfile(Profile):
def __init__(self) -> None:
super().__init__('Pipewire', ProfileType.Application)
@property
def packages(self) -> list[str]:
return [
'pipewire',
'pipewire-alsa',
'pipewire-jack',
'pipewire-pulse',
'gst-plugin-pipewire',
'libpulse',
'wireplumber'
]
def _enable_pipewire_for_all(self, install_session: 'Installer') -> None:
users: Union[User, list[User]] = archinstall.arguments.get('!users', [])
if not isinstance(users, list):
users = [users]
for user in users:
# Create the full path for enabling the pipewire systemd items
service_dir = install_session.target / "home" / user.username / ".config" / "systemd" / "user" / "default.target.wants"
service_dir.mkdir(parents=True, exist_ok=True)
# Set ownership of the entire user catalogue
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
# symlink in the correct pipewire systemd items
install_session.arch_chroot(f'ln -s /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service', run_as=user.username)
install_session.arch_chroot(f'ln -s /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket', run_as=user.username)
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
install_session.add_additional_packages(self.packages)
self._enable_pipewire_for_all(install_session)

View File

@ -1,218 +0,0 @@
# from typing import List, Dict, Optional, TYPE_CHECKING, Any
#
# from ..lib import menu
# from archinstall.lib.output import log, FormattedOutput
# from archinstall.lib.profile.profiles_handler import profile_handler
# from archinstall.default_profiles.profile import Profile, ProfileType, SelectResult, ProfileInfo, TProfile
#
# if TYPE_CHECKING:
# from archinstall.lib.installer import Installer
# _: Any
#
#
# class CustomProfileList(menu.ListManager):
# def __init__(self, prompt: str, profiles: List[TProfile]):
# self._actions = [
# str(_('Add profile')),
# str(_('Edit profile')),
# str(_('Delete profile'))
# ]
# super().__init__(prompt, profiles, [self._actions[0]], self._actions[1:])
#
# def reformat(self, data: List[TProfile]) -> Dict[str, Optional[TProfile]]:
# table = FormattedOutput.as_table(data)
# rows = table.split('\n')
#
# # these are the header rows of the table and do not map to any profile obviously
# # we're adding 2 spaces as prefix because the menu selector '> ' will be put before
# # the selectable rows so the header has to be aligned
# display_data: Dict[str, Optional[TProfile]] = {f' {rows[0]}': None, f' {rows[1]}': None}
#
# for row, profile in zip(rows[2:], data):
# row = row.replace('|', '\\|')
# display_data[row] = profile
#
# return display_data
#
# def selected_action_display(self, profile: TProfile) -> str:
# return profile.name
#
# def handle_action(
# self,
# action: str,
# entry: Optional['CustomTypeProfile'],
# data: List['CustomTypeProfile']
# ) -> List['CustomTypeProfile']:
# if action == self._actions[0]: # add
# new_profile = self._add_profile()
# if new_profile is not None:
# # in case a profile with the same name as an existing profile
# # was created we'll replace the existing one
# data = [d for d in data if d.name != new_profile.name]
# data += [new_profile]
# elif entry is not None:
# if action == self._actions[1]: # edit
# new_profile = self._add_profile(entry)
# if new_profile is not None:
# # we'll remove the original profile and add the modified version
# data = [d for d in data if d.name != entry.name and d.name != new_profile.name]
# data += [new_profile]
# elif action == self._actions[2]: # delete
# data = [d for d in data if d != entry]
#
# return data
#
# def _is_new_profile_name(self, name: str) -> bool:
# existing_profile = profile_handler.get_profile_by_name(name)
# if existing_profile is not None and existing_profile.profile_type != ProfileType.CustomType:
# return False
# return True
#
# def _add_profile(self, editing: Optional['CustomTypeProfile'] = None) -> Optional['CustomTypeProfile']:
# name_prompt = '\n\n' + str(_('Profile name: '))
#
# while True:
# profile_name = menu.TextInput(name_prompt, editing.name if editing else '').run().strip()
#
# if not profile_name:
# return None
#
# if not self._is_new_profile_name(profile_name):
# error_prompt = str(_("The profile name you entered is already in use. Try again"))
# print(error_prompt)
# else:
# break
#
# packages_prompt = str(_('Packages to be install with this profile (space separated, leave blank to skip): '))
# edit_packages = ' '.join(editing.packages) if editing else ''
# packages = menu.TextInput(packages_prompt, edit_packages).run().strip()
#
# services_prompt = str(_('Services to be enabled with this profile (space separated, leave blank to skip): '))
# edit_services = ' '.join(editing.services) if editing else ''
# services = menu.TextInput(services_prompt, edit_services).run().strip()
#
# choice = menu.Menu(
# str(_('Should this profile be enabled for installation?')),
# menu.Menu.yes_no(),
# skip=False,
# default_option=menu.Menu.no(),
# clear_screen=False,
# show_search_hint=False
# ).run()
#
# enable_profile = True if choice.value == menu.Menu.yes() else False
#
# profile = CustomTypeProfile(
# profile_name,
# enabled=enable_profile,
# packages=packages.split(' '),
# services=services.split(' ')
# )
#
# return profile
#
#
# # TODO
# # Still needs some ironing out
# class CustomProfile():
# def __init__(self):
# super().__init__(
# 'Custom',
# ProfileType.Custom,
# description=str(_('Create your own'))
# )
#
# def json(self) -> Dict[str, Any]:
# data: Dict[str, Any] = {'main': self.name, 'gfx_driver': self.gfx_driver, 'custom': []}
#
# for profile in self._current_selection:
# data['custom'].append({
# 'name': profile.name,
# 'packages': profile.packages,
# 'services': profile.services,
# 'enabled': profile.custom_enabled
# })
#
# return data
#
# def do_on_select(self) -> SelectResult:
# custom_profile_list = CustomProfileList('', profile_handler.get_custom_profiles())
# custom_profiles = custom_profile_list.run()
#
# # we'll first remove existing custom default_profiles with
# # the same name and then add the new ones this
# # will avoid errors of default_profiles with duplicate naming
# profile_handler.remove_custom_profiles(custom_profiles)
# profile_handler.add_custom_profiles(custom_profiles)
#
# self.set_current_selection(custom_profiles)
#
# if custom_profile_list.is_last_choice_cancel():
# return SelectResult.SameSelection
#
# enabled_profiles = [p for p in self._current_selection if p.custom_enabled]
# # in case we only created inactive default_profiles we wanna store them but
# # we want to reset the original setting
# if not enabled_profiles:
# return SelectResult.ResetCurrent
#
# return SelectResult.NewSelection
#
# def post_install(self, install_session: 'Installer'):
# for profile in self._current_selection:
# profile.post_install(install_session)
#
# def install(self, install_session: 'Installer'):
# driver_packages = self.gfx_driver_packages()
# install_session.add_additional_packages(driver_packages)
#
# for profile in self._current_selection:
# if profile.custom_enabled:
# log(f'Installing custom profile {profile.name}...')
#
# install_session.add_additional_packages(profile.packages)
# install_session.enable_service(profile.services)
#
# profile.install(install_session)
#
# def info(self) -> Optional[ProfileInfo]:
# enabled_profiles = [p for p in self._current_selection if p.custom_enabled]
# if enabled_profiles:
# details = ', '.join([p.name for p in enabled_profiles])
# gfx_driver = self.gfx_driver
# return ProfileInfo(self.name, details, gfx_driver)
#
# return None
#
# def reset(self):
# for profile in self._current_selection:
# profile.set_enabled(False)
#
# self.gfx_driver = None
#
#
# class CustomTypeProfile(Profile):
# def __init__(
# self,
# name: str,
# enabled: bool = False,
# packages: List[str] = [],
# services: List[str] = []
# ):
# super().__init__(
# name,
# ProfileType.CustomType,
# packages=packages,
# services=services,
# support_gfx_driver=True
# )
#
# self.custom_enabled = enabled
#
# def json(self) -> Dict[str, Any]:
# return {
# 'name': self.name,
# 'packages': self.packages,
# 'services': self.services,
# 'enabled': self.custom_enabled
# }

View File

@ -1,30 +1,28 @@
from typing import Any, TYPE_CHECKING
from typing import TYPE_CHECKING, Self, override
from archinstall.lib.output import info
from archinstall.default_profiles.profile import 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
from archinstall.default_profiles.profile import Profile, ProfileType, SelectResult, GreeterType
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, ResultType, PreviewStyle
)
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
from archinstall.lib.models.users import User
class DesktopProfile(Profile):
def __init__(self, current_selection: list[Profile] = []) -> None:
def __init__(self, current_selection: list[Self] = []) -> None:
super().__init__(
'Desktop',
ProfileType.Desktop,
description=str(_('Provides a selection of desktop environments and tiling window managers, e.g. GNOME, KDE Plasma, Sway')),
current_selection=current_selection,
support_greeter=True
support_greeter=True,
)
@property
@override
def packages(self) -> list[str]:
return [
'nano',
@ -32,14 +30,12 @@ class DesktopProfile(Profile):
'openssh',
'htop',
'wget',
'iwd',
'wireless_tools',
'wpa_supplicant',
'smartmontools',
'xdg-utils'
'xdg-utils',
]
@property
@override
def default_greeter_type(self) -> GreeterType | None:
combined_greeters: dict[GreeterType, int] = {}
for profile in self.current_selection:
@ -52,54 +48,67 @@ class DesktopProfile(Profile):
return None
def _do_on_select_profiles(self) -> None:
async def _do_on_select_profiles(self) -> None:
for profile in self.current_selection:
profile.do_on_select()
await profile.do_on_select()
def do_on_select(self) -> SelectResult | None:
@override
async def do_on_select(self) -> SelectResult:
items = [
MenuItem(
p.name,
value=p,
preview_action=lambda x: x.value.preview_text()
) for p in profile_handler.get_desktop_profiles()
preview_action=lambda x: x.value.preview_text() if x.value else None,
)
for p in profile_handler.get_desktop_profiles()
]
group = MenuItemGroup(items, sort_items=True)
group = MenuItemGroup(items, sort_items=True, sort_case_sensitive=False)
group.set_selected_by_value(self.current_selection)
result = SelectMenu(
result = await Selection[Self](
group,
multi=True,
allow_reset=True,
allow_skip=True,
preview_style=PreviewStyle.RIGHT,
preview_size='auto',
preview_frame=FrameProperties.max('Info')
).run()
preview_location='right',
).show()
match result.type_:
case ResultType.Selection:
self.current_selection = result.get_values()
self._do_on_select_profiles()
await self._do_on_select_profiles()
return SelectResult.NewSelection
case ResultType.Skip:
return SelectResult.SameSelection
case ResultType.Reset:
return SelectResult.ResetCurrent
def post_install(self, install_session: 'Installer') -> None:
@override
def post_install(self, install_session: Installer) -> None:
for profile in self.current_selection:
profile.post_install(install_session)
def install(self, install_session: 'Installer') -> None:
@override
def provision(self, install_session: Installer, users: list[User]) -> None:
for profile in self.current_selection:
profile.provision(install_session, users)
@override
def install(self, install_session: Installer) -> None:
# Install common packages for all desktop environments
install_session.add_additional_packages(self.packages)
xorg_installed = False
for profile in self.current_selection:
info(f'Installing profile {profile.name}...')
install_session.add_additional_packages(profile.packages)
install_session.enable_service(profile.services)
if not xorg_installed and profile.display_server == DisplayServerType.Xorg:
install_session.add_additional_packages(['xorg-server', 'xorg-xinit'])
xorg_installed = True
profile.install(install_session)

View File

@ -1,23 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import ProfileType
from archinstall.default_profiles.xorg import XorgProfile
from archinstall.default_profiles.profile import DisplayServerType, Profile, ProfileType
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
class AwesomeProfile(XorgProfile):
class AwesomeProfile(Profile):
def __init__(self) -> None:
super().__init__('Awesome', ProfileType.WindowMgr, description='')
super().__init__(
'Awesome',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return super().packages + [
return [
'awesome',
'alacritty',
'xorg-xinit',
'xorg-xrandr',
'xterm',
'feh',
@ -28,36 +31,37 @@ class AwesomeProfile(XorgProfile):
'xsel',
]
def install(self, install_session: 'Installer') -> None:
@override
def install(self, install_session: Installer) -> None:
super().install(install_session)
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua") as fh:
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua') as fh:
awesome_lua = fh.read()
# Replace xterm with alacritty for a smoother experience.
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
with open(f"{install_session.target}/etc/xdg/awesome/rc.lua", 'w') as fh:
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua', 'w') as fh:
fh.write(awesome_lua)
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
# TODO: check if we selected a greeter,
# but for now, awesome is intended to run without one.
with open(f"{install_session.target}/etc/X11/xinit/xinitrc") as xinitrc:
with open(f'{install_session.target}/etc/X11/xinit/xinitrc') as xinitrc:
xinitrc_data = xinitrc.read()
for line in xinitrc_data.split('\n'):
if "twm &" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
if "xclock" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
if "xterm" in line:
xinitrc_data = xinitrc_data.replace(line, f"# {line}")
if 'twm &' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if 'xclock' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
if 'xterm' in line:
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
xinitrc_data += '\n'
xinitrc_data += 'exec awesome\n'
with open(f"{install_session.target}/etc/X11/xinit/xinitrc", 'w') as xinitrc:
with open(f'{install_session.target}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
xinitrc.write(xinitrc_data)

View File

@ -1,27 +1,39 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
from archinstall.lib.installer import Installer
from archinstall.lib.models.users import User
class BspwmProfile(XorgProfile):
class BspwmProfile(Profile):
def __init__(self) -> None:
super().__init__('Bspwm', ProfileType.WindowMgr, description='')
super().__init__(
'Bspwm',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
# return super().packages + [
return [
'bspwm',
'sxhkd',
'dmenu',
'xdo',
'rxvt-unicode'
'rxvt-unicode',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm
@override
def provision(self, install_session: Installer, users: list[User]) -> None:
for user in users:
install_session.arch_chroot('mkdir -p ~/.config/bspwm ~/.config/sxhkd', run_as=user.username)
install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/bspwmrc ~/.config/bspwm/', run_as=user.username)
install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/sxhkdrc ~/.config/sxhkd/', run_as=user.username)
install_session.arch_chroot('chmod +x ~/.config/bspwm/bspwmrc', run_as=user.username)

View File

@ -1,26 +1,30 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class BudgieProfile(XorgProfile):
class BudgieProfile(Profile):
def __init__(self) -> None:
super().__init__('Budgie', ProfileType.DesktopEnv, description='')
super().__init__(
'Budgie',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
@override
def packages(self) -> list[str]:
return [
"arc-gtk-theme",
"budgie",
"mate-terminal",
"nemo",
"papirus-icon-theme"
'materia-gtk-theme',
'budgie',
'mate-terminal',
'nemo',
'nemo-fileroller',
'papirus-icon-theme',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.LightdmSlick

View File

@ -1,32 +1,33 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class CinnamonProfile(XorgProfile):
class CinnamonProfile(Profile):
def __init__(self) -> None:
super().__init__('Cinnamon', ProfileType.DesktopEnv, description='')
super().__init__(
'Cinnamon',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
"cinnamon",
"system-config-printer",
"gnome-keyring",
"gnome-terminal",
"blueman",
"bluez-utils",
"engrampa",
"gnome-screenshot",
"gvfs-smb",
"xed",
"xdg-user-dirs-gtk"
'cinnamon',
'system-config-printer',
'gnome-keyring',
'gnome-terminal',
'engrampa',
'gnome-screenshot',
'gvfs-smb',
'xed',
'xdg-user-dirs-gtk',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -1,22 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class CosmicProfile(XorgProfile):
class CosmicProfile(Profile):
def __init__(self) -> None:
super().__init__('cosmic-epoch', ProfileType.DesktopEnv, description='', advanced=True)
super().__init__(
'Cosmic',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
@override
def packages(self) -> list[str]:
return [
"cosmic",
'cosmic',
'xdg-user-dirs',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.CosmicSession

View File

@ -1,27 +0,0 @@
from typing import Any, TYPE_CHECKING
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
class CutefishProfile(XorgProfile):
def __init__(self) -> None:
super().__init__('Cutefish', ProfileType.DesktopEnv, description='')
@property
def packages(self) -> list[str]:
return [
"cutefish",
"noto-fonts"
]
@property
def default_greeter_type(self) -> GreeterType | None:
return GreeterType.Sddm
def install(self, install_session: 'Installer') -> None:
super().install(install_session)

View File

@ -1,24 +1,27 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class DeepinProfile(XorgProfile):
class DeepinProfile(Profile):
def __init__(self) -> None:
super().__init__('Deepin', ProfileType.DesktopEnv, description='')
super().__init__(
'Deepin',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
"deepin",
"deepin-terminal",
"deepin-editor"
'deepin',
'deepin-terminal',
'deepin-editor',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -1,23 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class EnlighenmentProfile(XorgProfile):
class EnlightenmentProfile(Profile):
def __init__(self) -> None:
super().__init__('Enlightenment', ProfileType.WindowMgr, description='')
super().__init__(
'Enlightenment',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
"enlightenment",
"terminology"
'enlightenment',
'terminology',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -1,23 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class GnomeProfile(XorgProfile):
class GnomeProfile(Profile):
def __init__(self) -> None:
super().__init__('Gnome', ProfileType.DesktopEnv, description='')
super().__init__(
'GNOME',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
@override
def packages(self) -> list[str]:
return [
'gnome',
'gnome-tweaks'
'gnome-tweaks',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Gdm

View File

@ -1,81 +1,52 @@
from enum import Enum
from typing import TYPE_CHECKING, Any
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType, SelectResult
from archinstall.default_profiles.xorg import XorgProfile
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, ResultType, Alignment
)
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
from archinstall.default_profiles.desktops.utils import select_seat_access
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
class SeatAccess(Enum):
seatd = 'seatd'
polkit = 'polkit'
class HyprlandProfile(XorgProfile):
class HyprlandProfile(Profile):
def __init__(self) -> None:
super().__init__('Hyprland', ProfileType.DesktopEnv, description='')
super().__init__(
'Hyprland',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
self.custom_settings = {'seat_access': None}
self.custom_settings = {CustomSetting.SeatAccess: None}
@property
@override
def packages(self) -> list[str]:
return [
"hyprland",
"dunst",
"kitty",
"dolphin",
"wofi",
"xdg-desktop-portal-hyprland",
"qt5-wayland",
"qt6-wayland",
"polkit-kde-agent",
"grim",
"slurp"
'hyprland',
'dunst',
'kitty',
'uwsm',
'dolphin',
'wofi',
'xdg-desktop-portal-hyprland',
'qt5-wayland',
'qt6-wayland',
'polkit-kde-agent',
'grim',
'slurp',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Sddm
@property
@override
def services(self) -> list[str]:
if pref := self.custom_settings.get('seat_access', None):
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
return [pref]
return []
def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group
header = str(_('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
header += '\n' + str(_('Choose an option to give Sway access to your hardware')) + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(str(_('Seat access'))),
alignment=Alignment.CENTER
).run()
if result.type_ == ResultType.Selection:
if result.item() is not None:
self.custom_settings['seat_access'] = result.get_value()
def do_on_select(self) -> SelectResult | None:
self._ask_seat_access()
return None
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
@override
async def do_on_select(self) -> None:
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
seat_access = await select_seat_access(self.name, default)
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value

View File

@ -1,17 +1,19 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class I3wmProfile(XorgProfile):
class I3wmProfile(Profile):
def __init__(self) -> None:
super().__init__('i3-wm', ProfileType.WindowMgr, description='')
super().__init__(
'i3-wm',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
'i3-wm',
@ -22,9 +24,10 @@ class I3wmProfile(XorgProfile):
'xterm',
'lightdm-gtk-greeter',
'lightdm',
'dmenu'
'dmenu',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -0,0 +1,46 @@
from typing import override
from archinstall.default_profiles.desktops.utils import select_seat_access
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
class LabwcProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Labwc',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
self.custom_settings = {CustomSetting.SeatAccess: None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
additional = [seat]
return [
'alacritty',
'labwc',
] + additional
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm
@property
@override
def services(self) -> list[str]:
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
return [pref]
return []
@override
async def do_on_select(self) -> None:
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
seat_access = await select_seat_access(self.name, default)
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value

View File

@ -1,31 +1,34 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class LxqtProfile(XorgProfile):
class LxqtProfile(Profile):
def __init__(self) -> None:
super().__init__('Lxqt', ProfileType.DesktopEnv, description='')
super().__init__(
'Lxqt',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
# LXQt works with lightdm, but since this is not supported, we will not default to this.
# https://github.com/lxqt/lxqt/issues/795
@property
@override
def packages(self) -> list[str]:
return [
"lxqt",
"breeze-icons",
"oxygen-icons",
"xdg-utils",
"ttf-freefont",
"leafpad",
"slock"
'lxqt',
'breeze-icons',
'oxygen-icons',
'xdg-utils',
'ttf-freefont',
'l3afpad',
'slock',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Sddm

View File

@ -1,23 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class MateProfile(XorgProfile):
class MateProfile(Profile):
def __init__(self) -> None:
super().__init__('Mate', ProfileType.DesktopEnv, description='')
super().__init__(
'Mate',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
"mate",
"mate-extra"
'mate',
'mate-extra',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -0,0 +1,54 @@
from typing import override
from archinstall.default_profiles.desktops.utils import select_seat_access
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
class NiriProfile(Profile):
def __init__(self) -> None:
super().__init__(
'niri',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
self.custom_settings = {CustomSetting.SeatAccess: None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
additional = [seat]
return [
'niri',
'alacritty',
'fuzzel',
'mako',
'xorg-xwayland',
'waybar',
'swaybg',
'swayidle',
'swaylock',
'xdg-desktop-portal-gnome',
] + additional
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm
@property
@override
def services(self) -> list[str]:
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
return [pref]
return []
@override
async def do_on_select(self) -> None:
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
seat_access = await select_seat_access(self.name, default)
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value

View File

@ -0,0 +1,66 @@
import shutil
from pathlib import Path
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
from archinstall.lib.models.users import User
_TERMINAL = 'alacritty'
_ASSETS_DIR = Path(__file__).parent / 'niri_dms_assets'
class NiriDmsProfile(Profile):
def __init__(self) -> None:
super().__init__(
'niri - DankMaterialShell',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
@override
def packages(self) -> list[str]:
return [
'niri',
'dms-shell-niri',
'polkit',
'xdg-desktop-portal-gnome',
'xorg-xwayland',
'matugen',
'cava',
'kimageformats',
'cups-pk-helper',
'tuned-ppd',
_TERMINAL,
]
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.GreetdDms
@override
def provision(self, install_session: Installer, users: list[User]) -> None:
binds = (_ASSETS_DIR / 'dms/binds.kdl').read_text().replace('{{TERMINAL_COMMAND}}', _TERMINAL)
for user in users:
home = install_session.target / 'home' / user.username
niri_dir = home / '.config/niri'
dms_dir = niri_dir / 'dms'
dms_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(_ASSETS_DIR / 'niri.kdl', niri_dir / 'config.kdl')
for name in ('colors.kdl', 'layout.kdl', 'alttab.kdl', 'outputs.kdl', 'cursor.kdl'):
shutil.copy(_ASSETS_DIR / 'dms' / name, dms_dir / name)
(dms_dir / 'binds.kdl').write_text(binds)
niri_unit_dropin = home / '.config/systemd/user/niri.service.d'
niri_unit_dropin.mkdir(parents=True, exist_ok=True)
(niri_unit_dropin / 'dms.conf').write_text('[Unit]\nWants=dms.service\n')
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}/.config')

View File

@ -0,0 +1,10 @@
// ! DO NOT EDIT !
// ! AUTO-GENERATED BY DMS !
// ! CHANGES WILL BE OVERWRITTEN !
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
recent-windows {
highlight {
corner-radius 12
}
}

View File

@ -0,0 +1,221 @@
binds {
// === System & Overview ===
Mod+D repeat=false { toggle-overview; }
Mod+Tab repeat=false { toggle-overview; }
Mod+Shift+Slash { show-hotkey-overlay; }
// === Application Launchers ===
Mod+T hotkey-overlay-title="Open Terminal" { spawn "{{TERMINAL_COMMAND}}"; }
Mod+Space hotkey-overlay-title="Application Launcher" {
spawn "dms" "ipc" "call" "spotlight" "toggle";
}
Mod+V hotkey-overlay-title="Clipboard Manager" {
spawn "dms" "ipc" "call" "clipboard" "toggle";
}
Mod+M hotkey-overlay-title="Task Manager" {
spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
}
Super+X hotkey-overlay-title="Power Menu: Toggle" { spawn "dms" "ipc" "call" "powermenu" "toggle"; }
Mod+Comma hotkey-overlay-title="Settings" {
spawn "dms" "ipc" "call" "settings" "focusOrToggle";
}
Mod+Y hotkey-overlay-title="Browse Wallpapers" {
spawn "dms" "ipc" "call" "dankdash" "wallpaper";
}
Mod+N hotkey-overlay-title="Notification Center" { spawn "dms" "ipc" "call" "notifications" "toggle"; }
Mod+Shift+N hotkey-overlay-title="Notepad" { spawn "dms" "ipc" "call" "notepad" "toggle"; }
// === Security ===
Mod+Alt+L hotkey-overlay-title="Lock Screen" {
spawn "dms" "ipc" "call" "lock" "lock";
}
Mod+Shift+E { quit; }
Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
}
// === Audio Controls ===
XF86AudioRaiseVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "increment" "3";
}
XF86AudioLowerVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "decrement" "3";
}
XF86AudioMute allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "mute";
}
XF86AudioMicMute allow-when-locked=true {
spawn "dms" "ipc" "call" "audio" "micmute";
}
XF86AudioPause allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "playPause";
}
XF86AudioPlay allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "playPause";
}
XF86AudioPrev allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "previous";
}
XF86AudioNext allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "next";
}
Ctrl+XF86AudioRaiseVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "increment" "3";
}
Ctrl+XF86AudioLowerVolume allow-when-locked=true {
spawn "dms" "ipc" "call" "mpris" "decrement" "3";
}
// === Brightness Controls ===
XF86MonBrightnessUp allow-when-locked=true {
spawn "dms" "ipc" "call" "brightness" "increment" "5" "";
}
XF86MonBrightnessDown allow-when-locked=true {
spawn "dms" "ipc" "call" "brightness" "decrement" "5" "";
}
// === Window Management ===
Mod+Q repeat=false { close-window; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
Mod+Shift+T { toggle-window-floating; }
Mod+Shift+V { switch-focus-between-floating-and-tiling; }
Mod+W { toggle-column-tabbed-display; }
Mod+Shift+W hotkey-overlay-title="Create window rule" { spawn "dms" "ipc" "call" "window-rules" "toggle"; }
// === Focus Navigation ===
Mod+Left { focus-column-left; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Right { focus-column-right; }
Mod+H { focus-column-left; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+L { focus-column-right; }
// === Window Movement ===
Mod+Shift+Left { move-column-left; }
Mod+Shift+Down { move-window-down; }
Mod+Shift+Up { move-window-up; }
Mod+Shift+Right { move-column-right; }
Mod+Shift+H { move-column-left; }
Mod+Shift+J { move-window-down; }
Mod+Shift+K { move-window-up; }
Mod+Shift+L { move-column-right; }
// === Column Navigation ===
Mod+Home { focus-column-first; }
Mod+End { focus-column-last; }
Mod+Ctrl+Home { move-column-to-first; }
Mod+Ctrl+End { move-column-to-last; }
// === Monitor Navigation ===
Mod+Ctrl+Left { focus-monitor-left; }
//Mod+Ctrl+Down { focus-monitor-down; }
//Mod+Ctrl+Up { focus-monitor-up; }
Mod+Ctrl+Right { focus-monitor-right; }
Mod+Ctrl+H { focus-monitor-left; }
Mod+Ctrl+J { focus-monitor-down; }
Mod+Ctrl+K { focus-monitor-up; }
Mod+Ctrl+L { focus-monitor-right; }
// === Move to Monitor ===
Mod+Shift+Ctrl+Left { move-column-to-monitor-left; }
Mod+Shift+Ctrl+Down { move-column-to-monitor-down; }
Mod+Shift+Ctrl+Up { move-column-to-monitor-up; }
Mod+Shift+Ctrl+Right { move-column-to-monitor-right; }
Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
// === Workspace Navigation ===
Mod+Page_Down { focus-workspace-down; }
Mod+Page_Up { focus-workspace-up; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Ctrl+Down { move-column-to-workspace-down; }
Mod+Ctrl+Up { move-column-to-workspace-up; }
Mod+Ctrl+U { move-column-to-workspace-down; }
Mod+Ctrl+I { move-column-to-workspace-up; }
// === Workspace Management ===
Ctrl+Shift+R hotkey-overlay-title="Rename Workspace" {
spawn "dms" "ipc" "call" "workspace-rename" "open";
}
// === Move Workspaces ===
Mod+Shift+Page_Down { move-workspace-down; }
Mod+Shift+Page_Up { move-workspace-up; }
Mod+Shift+U { move-workspace-down; }
Mod+Shift+I { move-workspace-up; }
// === Mouse Wheel Navigation ===
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; }
Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; }
Mod+WheelScrollRight { focus-column-right; }
Mod+WheelScrollLeft { focus-column-left; }
Mod+Ctrl+WheelScrollRight { move-column-right; }
Mod+Ctrl+WheelScrollLeft { move-column-left; }
Mod+Shift+WheelScrollDown { focus-column-right; }
Mod+Shift+WheelScrollUp { focus-column-left; }
Mod+Ctrl+Shift+WheelScrollDown { move-column-right; }
Mod+Ctrl+Shift+WheelScrollUp { move-column-left; }
// === Numbered Workspaces ===
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+5 { focus-workspace 5; }
Mod+6 { focus-workspace 6; }
Mod+7 { focus-workspace 7; }
Mod+8 { focus-workspace 8; }
Mod+9 { focus-workspace 9; }
// === Move to Numbered Workspaces ===
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
Mod+Shift+4 { move-column-to-workspace 4; }
Mod+Shift+5 { move-column-to-workspace 5; }
Mod+Shift+6 { move-column-to-workspace 6; }
Mod+Shift+7 { move-column-to-workspace 7; }
Mod+Shift+8 { move-column-to-workspace 8; }
Mod+Shift+9 { move-column-to-workspace 9; }
// === Column Management ===
Mod+BracketLeft { consume-or-expel-window-left; }
Mod+BracketRight { consume-or-expel-window-right; }
Mod+Period { expel-window-from-column; }
// === Sizing & Layout ===
Mod+R { switch-preset-column-width; }
Mod+Shift+R { switch-preset-window-height; }
Mod+Ctrl+R { reset-window-height; }
Mod+Ctrl+F { expand-column-to-available-width; }
Mod+C { center-column; }
Mod+Ctrl+C { center-visible-columns; }
// === Manual Sizing ===
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
// === Screenshots ===
XF86Launch1 { screenshot; }
Ctrl+XF86Launch1 { screenshot-screen; }
Alt+XF86Launch1 { screenshot-window; }
Print { screenshot; }
Ctrl+Print { screenshot-screen; }
Alt+Print { screenshot-window; }
// === System Controls ===
Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
Mod+Shift+P { power-off-monitors; }
}

View File

@ -0,0 +1,39 @@
// ! Auto-generated file. Do not edit directly.
// Remove `include "dms/colors.kdl"` from your config to override.
layout {
background-color "transparent"
focus-ring {
active-color "#d0bcff"
inactive-color "#948f99"
urgent-color "#f2b8b5"
}
border {
active-color "#d0bcff"
inactive-color "#948f99"
urgent-color "#f2b8b5"
}
shadow {
color "#00000070"
}
tab-indicator {
active-color "#d0bcff"
inactive-color "#948f99"
urgent-color "#f2b8b5"
}
insert-hint {
color "#d0bcff80"
}
}
recent-windows {
highlight {
active-color "#4f378b"
urgent-color "#f2b8b5"
}
}

View File

@ -0,0 +1,6 @@
// Place cursor configuration here.
// Example:
// cursor {
// xcursor-theme "Adwaita"
// xcursor-size 24
// }

View File

@ -0,0 +1,22 @@
// ! DO NOT EDIT !
// ! AUTO-GENERATED BY DMS !
// ! CHANGES WILL BE OVERWRITTEN !
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
layout {
gaps 4
border {
width 2
}
focus-ring {
width 2
}
}
window-rule {
geometry-corner-radius 12
clip-to-geometry true
tiled-state true
draw-border-with-background false
}

View File

@ -0,0 +1,7 @@
// Place per-output configuration here.
// Example:
// output "DP-1" {
// mode "2560x1440@165"
// position x=0 y=0
// scale 1
// }

View File

@ -0,0 +1,279 @@
// This config is in the KDL format: https://kdl.dev
// "/-" comments out the following node.
// Check the wiki for a full description of the configuration:
// https://github.com/YaLTeR/niri/wiki/Configuration:-Introduction
config-notification {
disable-failed
}
gestures {
hot-corners {
off
}
}
// Input device configuration.
// Find the full list of options on the wiki:
// https://github.com/YaLTeR/niri/wiki/Configuration:-Input
input {
keyboard {
xkb {
// You can set rules, model, layout, variant and options.
// For more information, see xkeyboard-config(7).
// For example:
// layout "us,ru"
// options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
// If this section is empty, niri will fetch xkb settings
// from org.freedesktop.locale1. You can control these using
// localectl set-x11-keymap.
}
// Enable numlock on startup, omitting this setting disables it.
numlock
}
// Next sections include libinput settings.
// Omitting settings disables them, or leaves them at their default values.
// All commented-out settings here are examples, not defaults.
touchpad {
// off
tap
// dwt
// dwtp
// drag false
// drag-lock
natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "two-finger"
// disabled-on-external-mouse
}
mouse {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "no-scroll"
}
trackpoint {
// off
// natural-scroll
// accel-speed 0.2
// accel-profile "flat"
// scroll-method "on-button-down"
// scroll-button 273
// scroll-button-lock
// middle-emulation
}
// Uncomment this to make the mouse warp to the center of newly focused windows.
// warp-mouse-to-focus
// Focus windows and outputs automatically when moving the mouse into them.
// Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
// focus-follows-mouse max-scroll-amount="0%"
}
// You can configure outputs by their name, which you can find
// by running `niri msg outputs` while inside a niri instance.
// The built-in laptop monitor is usually called "eDP-1".
// Find more information on the wiki:
// https://github.com/YaLTeR/niri/wiki/Configuration:-Outputs
// Remember to uncomment the node by removing "/-"!
/-output "eDP-2" {
mode "2560x1600@239.998993"
position x=2560 y=0
variable-refresh-rate
}
// Settings that influence how windows are positioned and sized.
// Find more information on the wiki:
// https://github.com/YaLTeR/niri/wiki/Configuration:-Layout
layout {
// Set gaps around windows in logical pixels.
background-color "transparent"
// When to center a column when changing focus, options are:
// - "never", default behavior, focusing an off-screen column will keep at the left
// or right edge of the screen.
// - "always", the focused column will always be centered.
// - "on-overflow", focusing a column will center it if it doesn't fit
// together with the previously focused column.
center-focused-column "never"
// You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between.
preset-column-widths {
// Proportion sets the width as a fraction of the output width, taking gaps into account.
// For example, you can perfectly fit four windows sized "proportion 0.25" on an output.
// The default preset widths are 1/3, 1/2 and 2/3 of the output.
proportion 0.33333
proportion 0.5
proportion 0.66667
// Fixed sets the width in logical pixels exactly.
// fixed 1920
}
// You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between.
// preset-window-heights { }
// You can change the default width of the new windows.
default-column-width { proportion 0.5; }
// If you leave the brackets empty, the windows themselves will decide their initial width.
// default-column-width {}
// By default focus ring and border are rendered as a solid background rectangle
// behind windows. That is, they will show up through semitransparent windows.
// This is because windows using client-side decorations can have an arbitrary shape.
//
// If you don't like that, you should uncomment `prefer-no-csd` below.
// Niri will draw focus ring and border *around* windows that agree to omit their
// client-side decorations.
//
// Alternatively, you can override it with a window rule called
// `draw-border-with-background`.
border {
off
width 4
active-color "#707070" // Neutral gray
inactive-color "#d0d0d0" // Light gray
urgent-color "#cc4444" // Softer red
}
shadow {
softness 30
spread 5
offset x=0 y=5
color "#0007"
}
struts {
}
}
layer-rule {
match namespace="^quickshell$"
place-within-backdrop true
}
overview {
workspace-shadow {
off
}
}
// Add lines like this to spawn processes at startup.
// Note that running niri as a session supports xdg-desktop-autostart,
// which may be more convenient to use.
// See the binds section below for more spawn examples.
// This line starts waybar, a commonly used bar for Wayland compositors.
environment {
XDG_CURRENT_DESKTOP "niri"
}
hotkey-overlay {
skip-at-startup
}
prefer-no-csd
screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
animations {
workspace-switch {
spring damping-ratio=0.80 stiffness=523 epsilon=0.0001
}
window-open {
duration-ms 150
curve "ease-out-expo"
}
window-close {
duration-ms 150
curve "ease-out-quad"
}
horizontal-view-movement {
spring damping-ratio=0.85 stiffness=423 epsilon=0.0001
}
window-movement {
spring damping-ratio=0.75 stiffness=323 epsilon=0.0001
}
window-resize {
spring damping-ratio=0.85 stiffness=423 epsilon=0.0001
}
config-notification-open-close {
spring damping-ratio=0.65 stiffness=923 epsilon=0.001
}
screenshot-ui-open {
duration-ms 200
curve "ease-out-quad"
}
overview-open-close {
spring damping-ratio=0.85 stiffness=800 epsilon=0.0001
}
}
// Window rules let you adjust behavior for individual windows.
// Find more information on the wiki:
// https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules
// Work around WezTerm's initial configure bug
// by setting an empty default-column-width.
window-rule {
// This regular expression is intentionally made as specific as possible,
// since this is the default config, and we want no false positives.
// You can get away with just app-id="wezterm" if you want.
match app-id=r#"^org\.wezfurlong\.wezterm$"#
default-column-width {}
}
window-rule {
match app-id=r#"^org\.gnome\."#
draw-border-with-background false
geometry-corner-radius 12
clip-to-geometry true
}
window-rule {
match app-id=r#"^gnome-control-center$"#
match app-id=r#"^pavucontrol$"#
match app-id=r#"^nm-connection-editor$"#
default-column-width { proportion 0.5; }
open-floating false
}
window-rule {
match app-id=r#"^org\.gnome\.Calculator$"#
match app-id=r#"^gnome-calculator$"#
match app-id=r#"^galculator$"#
match app-id=r#"^blueman-manager$"#
match app-id=r#"^org\.gnome\.Nautilus$"#
match app-id=r#"^xdg-desktop-portal$"#
open-floating true
}
window-rule {
match app-id=r#"^steam$"# title=r#"^notificationtoasts_\d+_desktop$"#
default-floating-position x=10 y=10 relative-to="bottom-right"
open-focused false
}
window-rule {
match app-id=r#"^org\.wezfurlong\.wezterm$"#
match app-id="Alacritty"
match app-id="zen"
match app-id="com.mitchellh.ghostty"
match app-id="kitty"
draw-border-with-background false
}
window-rule {
match app-id=r#"firefox$"# title="^Picture-in-Picture$"
match app-id="zoom"
open-floating true
}
// Open dms windows as floating by default
window-rule {
match app-id=r#"org.quickshell$"#
match app-id=r#"com.danklinux.dms$"#
open-floating true
}
debug {
honor-xdg-activation-with-invalid-serial
}
// Override to disable super+tab
recent-windows {
binds {
Alt+Tab { next-window scope="output"; }
Alt+Shift+Tab { previous-window scope="output"; }
Alt+grave { next-window filter="app-id"; }
Alt+Shift+grave { previous-window filter="app-id"; }
}
}
// Include dms files
include "dms/colors.kdl"
include "dms/layout.kdl"
include "dms/alttab.kdl"
include "dms/binds.kdl"
include "dms/outputs.kdl"
include "dms/cursor.kdl"

View File

@ -1,28 +1,120 @@
from typing import Any, TYPE_CHECKING
from enum import StrEnum
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
from archinstall.lib.menu.helpers import Selection
from archinstall.lib.packages.packages import available_package, package_group_info
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class PlasmaProfile(XorgProfile):
def __init__(self) -> None:
super().__init__('KDE Plasma', ProfileType.DesktopEnv, description='')
class PlasmaFlavor(StrEnum):
Meta = 'plasma-meta'
Plasma = 'plasma'
Desktop = 'plasma-desktop'
def show(self) -> str:
match self:
case PlasmaFlavor.Meta:
return f'{self.value} ({tr("Recommended")})'
case PlasmaFlavor.Plasma | PlasmaFlavor.Desktop:
return self.value
def package_details(self) -> str:
ty = ''
details = ''
desc = ''
match self:
case PlasmaFlavor.Meta:
ty = tr('Package')
desc = tr('Curated selection of KDE Plasma packages')
info = available_package(self.value)
if info is not None:
details = tr('Dependencies') + '\n'
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
case PlasmaFlavor.Plasma:
ty = tr('Package group')
desc = tr('Extensive KDE Plasma installation')
group = package_group_info(self.value)
if group is not None:
details = tr('Packages in group') + '\n'
details += '\n'.join(f'- {entry}' for entry in group.packages)
case PlasmaFlavor.Desktop:
ty = tr('Package group')
desc = tr('Minimal KDE Plasma installation')
info = available_package(self.value)
if info is not None:
details = tr('Dependencies') + '\n'
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
return f'{tr("Type")}: {ty}\n{tr("Description")}: {desc}\n\n{details}'
@property
def packages(self) -> list[str]:
return [
"plasma-meta",
"konsole",
"kwrite",
"dolphin",
"ark",
"plasma-workspace",
"egl-wayland"
]
match self:
case PlasmaFlavor.Meta:
return ['plasma-meta']
case PlasmaFlavor.Plasma:
return ['plasma']
case PlasmaFlavor.Desktop:
return ['plasma-desktop']
class PlasmaProfile(Profile):
def __init__(self) -> None:
super().__init__(
'KDE Plasma',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
def default_greeter_type(self) -> GreeterType | None:
return GreeterType.Sddm
@override
def packages(self) -> list[str]:
flavor_str = self.custom_settings.get(CustomSetting.PlasmaFlavor)
if flavor_str is not None:
flavor = PlasmaFlavor(flavor_str)
return flavor.packages()
else:
return PlasmaFlavor.Meta.packages() # use plasma-meta as the recommended default
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.PlasmaLoginManager
async def _select_flavor(self) -> None:
header = tr('Select a flavor of KDE Plasma to install') + '\n'
items = [
MenuItem(
s.show(),
value=s,
preview_action=lambda x: x.value.package_details() if x.value else None,
)
for s in PlasmaFlavor
]
group = MenuItemGroup(items, sort_items=False)
default = self.custom_settings.get(CustomSetting.PlasmaFlavor, None)
group.set_default_by_value(default)
result = await Selection[PlasmaFlavor](
group,
header=header,
allow_skip=False,
preview_location='right',
).show()
if result.type_ == ResultType.Selection:
self.custom_settings[CustomSetting.PlasmaFlavor] = result.get_value().value
@override
async def do_on_select(self) -> None:
await self._select_flavor()

View File

@ -1,23 +1,26 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class QtileProfile(XorgProfile):
class QtileProfile(Profile):
def __init__(self) -> None:
super().__init__('Qtile', ProfileType.WindowMgr, description='')
super().__init__(
'Qtile',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
'qtile',
'alacritty'
'alacritty',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -0,0 +1,27 @@
from typing import override
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class RiverProfile(Profile):
def __init__(self) -> None:
super().__init__(
'River',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
@property
@override
def packages(self) -> list[str]:
return [
'foot',
'xdg-desktop-portal-wlr',
'river',
]
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -1,91 +1,56 @@
from enum import Enum
from typing import TYPE_CHECKING, Any
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType, SelectResult
from archinstall.default_profiles.xorg import XorgProfile
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, Alignment, ResultType
)
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
from archinstall.default_profiles.desktops.utils import select_seat_access
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
class SeatAccess(Enum):
seatd = 'seatd'
polkit = 'polkit'
class SwayProfile(XorgProfile):
class SwayProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Sway',
ProfileType.WindowMgr,
description=''
support_gfx_driver=True,
display_server=DisplayServerType.Wayland,
)
self.custom_settings = {'seat_access': None}
self.custom_settings = {CustomSetting.SeatAccess: None}
@property
@override
def packages(self) -> list[str]:
additional = []
if seat := self.custom_settings.get('seat_access', None):
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
additional = [seat]
return [
"sway",
"swaybg",
"swaylock",
"swayidle",
"waybar",
"dmenu",
"brightnessctl",
"grim",
"slurp",
"pavucontrol",
"foot",
"xorg-xwayland"
'sway',
'swaybg',
'swaylock',
'swayidle',
'waybar',
'wmenu',
'brightnessctl',
'grim',
'slurp',
'pavucontrol',
'foot',
'xorg-xwayland',
] + additional
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm
@property
@override
def services(self) -> list[str]:
if pref := self.custom_settings.get('seat_access', None):
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
return [pref]
return []
def _ask_seat_access(self) -> None:
# need to activate seat service and add to seat group
header = str(_('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
header += '\n' + str(_('Choose an option to give Sway access to your hardware')) + '\n'
items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True)
default = self.custom_settings.get('seat_access', None)
group.set_default_by_value(default)
result = SelectMenu(
group,
header=header,
allow_skip=False,
frame=FrameProperties.min(str(_('Seat access'))),
alignment=Alignment.CENTER
).run()
if result.type_ == ResultType.Selection:
if result.item() is not None:
self.custom_settings['seat_access'] = result.get_value()
def do_on_select(self) -> SelectResult | None:
self._ask_seat_access()
return None
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
@override
async def do_on_select(self) -> None:
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
seat_access = await select_seat_access(self.name, default)
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value

View File

@ -0,0 +1,33 @@
from enum import Enum
from archinstall.lib.menu.helpers import Selection
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class SeatAccess(Enum):
seatd = 'seatd'
polkit = 'polkit'
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'
header += tr('Choose an option how to give {} access to your hardware').format(profile_name)
items = [MenuItem(s.value, value=s) for s in SeatAccess]
group = MenuItemGroup(items, sort_items=True)
group.set_default_by_value(default)
result = await Selection[SeatAccess](
group,
header=header,
allow_skip=False,
).show()
if result.type_ == ResultType.Selection:
return result.get_value()
else:
raise ValueError('Unexpected result type from seat access selection')

View File

@ -1,26 +1,29 @@
from typing import Any, TYPE_CHECKING
from typing import override
from archinstall.default_profiles.profile import ProfileType, GreeterType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
_: Any
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class Xfce4Profile(XorgProfile):
class Xfce4Profile(Profile):
def __init__(self) -> None:
super().__init__('Xfce4', ProfileType.DesktopEnv, description='')
super().__init__(
'Xfce4',
ProfileType.DesktopEnv,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
"xfce4",
"xfce4-goodies",
"pavucontrol",
"gvfs",
"xarchiver"
'xfce4',
'xfce4-goodies',
'pavucontrol',
'gvfs',
'xarchiver',
]
@property
def default_greeter_type(self) -> GreeterType | None:
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -0,0 +1,29 @@
from typing import override
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
class XmonadProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Xmonad',
ProfileType.WindowMgr,
support_gfx_driver=True,
display_server=DisplayServerType.Xorg,
)
@property
@override
def packages(self) -> list[str]:
return [
'xmonad',
'xmonad-contrib',
'xmonad-extras',
'xterm',
'dmenu',
]
@property
@override
def default_greeter_type(self) -> GreeterType:
return GreeterType.Lightdm

View File

@ -1,15 +1,9 @@
from typing import Any, TYPE_CHECKING
from archinstall.default_profiles.profile import Profile, ProfileType
if TYPE_CHECKING:
_: Any
class MinimalProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Minimal',
ProfileType.Minimal,
description=str(_('A very basic installation that allows you to customize Arch Linux as you see fit.'))
)

View File

@ -1,14 +1,16 @@
from __future__ import annotations
from enum import Enum, StrEnum, auto
from typing import TYPE_CHECKING, Self
import sys
from enum import Enum, auto
from typing import Any, TYPE_CHECKING
from ..lib.storage import storage
from archinstall.lib.translationhandler import tr
if TYPE_CHECKING:
from ..lib.installer import Installer
_: Any
from archinstall.lib.installer import Installer
from archinstall.lib.models.users import User
class DisplayServerType(Enum):
Xorg = 'Xorg'
Wayland = 'Wayland'
class ProfileType(Enum):
@ -24,7 +26,6 @@ class ProfileType(Enum):
DesktopEnv = 'Desktop Environment'
CustomType = 'CustomType'
# special things
Tailored = 'Tailored'
Application = 'Application'
@ -34,10 +35,9 @@ class GreeterType(Enum):
Sddm = 'sddm'
Gdm = 'gdm'
Ly = 'ly'
# .. todo:: Remove when we un-hide cosmic behind --advanced
if '--advanced' in sys.argv:
CosmicSession = "cosmic-greeter"
CosmicSession = 'cosmic-greeter'
PlasmaLoginManager = 'plasma-login-manager'
GreetdDms = 'dms-greeter'
class SelectResult(Enum):
@ -46,27 +46,30 @@ class SelectResult(Enum):
ResetCurrent = auto()
class CustomSetting(StrEnum):
SeatAccess = 'seat_access'
PlasmaFlavor = 'plasma_flavor'
class Profile:
def __init__(
self,
name: str,
profile_type: ProfileType,
description: str = '',
current_selection: list[Profile] = [],
current_selection: list[Self] = [],
packages: list[str] = [],
services: list[str] = [],
support_gfx_driver: bool = False,
support_greeter: bool = False,
advanced: bool = False
display_server: DisplayServerType | None = None,
) -> None:
self.name = name
self.description = description
self.profile_type = profile_type
self.custom_settings: dict[str, Any] = {}
self.advanced = advanced
self.custom_settings: dict[CustomSetting, str | None] = {}
self._support_gfx_driver = support_gfx_driver
self._support_greeter = support_greeter
self._display_server = display_server
# self.gfx_driver: str | None = None
@ -100,38 +103,38 @@ class Profile:
"""
return None
def _advanced_check(self) -> bool:
"""
Used to control if the Profile() should be visible or not in different contexts.
Returns True if --advanced is given on a Profile(advanced=True) instance.
"""
return self.advanced is False or storage['arguments'].get('advanced', False) is True
def install(self, install_session: 'Installer') -> None:
def install(self, install_session: Installer) -> None:
"""
Performs installation steps when this profile was selected
"""
def post_install(self, install_session: 'Installer') -> None:
def post_install(self, install_session: Installer) -> None:
"""
Hook that will be called when the installation process is
finished and custom installation steps for specific default_profiles
are needed
"""
def json(self) -> dict[str, Any]:
def provision(self, install_session: Installer, users: list[User]) -> None:
"""
Hook that will be called when the installation process is
finished and user configuration for specific default_profiles
is needed
"""
def json(self) -> dict[str, str]:
"""
Returns a json representation of the profile
"""
return {}
def do_on_select(self) -> SelectResult | None:
async def do_on_select(self) -> SelectResult | None:
"""
Hook that will be called when a profile is selected
"""
return SelectResult.NewSelection
def set_custom_settings(self, settings: dict[str, Any]) -> None:
def set_custom_settings(self, settings: dict[CustomSetting, str | None]) -> None:
"""
Set the custom settings for the profile.
This is also called when the settings are parsed from the config
@ -152,19 +155,16 @@ class Profile:
return self.profile_type in top_levels
def is_desktop_profile(self) -> bool:
return self.profile_type == ProfileType.Desktop if self._advanced_check() else False
return self.profile_type == ProfileType.Desktop
def is_server_type_profile(self) -> bool:
return self.profile_type == ProfileType.ServerType
def is_desktop_type_profile(self) -> bool:
return (self.profile_type == ProfileType.DesktopEnv or self.profile_type == ProfileType.WindowMgr) if self._advanced_check() else False
return self.profile_type == ProfileType.DesktopEnv or self.profile_type == ProfileType.WindowMgr
def is_xorg_type_profile(self) -> bool:
return self.profile_type == ProfileType.Xorg if self._advanced_check() else False
def is_tailored(self) -> bool:
return self.profile_type == ProfileType.Tailored
return self.profile_type == ProfileType.Xorg
def is_custom_type_profile(self) -> bool:
return self.profile_type == ProfileType.CustomType
@ -180,10 +180,23 @@ class Profile:
def is_greeter_supported(self) -> bool:
return self._support_greeter
def preview_text(self) -> str | None:
@property
def display_server(self) -> DisplayServerType | None:
return self._display_server
def preview_text(self) -> str:
"""
Override this method to provide a preview text for the profile
"""
if self.is_desktop_type_profile():
if self._display_server:
text = tr('Environment type: {} {}').format(self._display_server.value, self.profile_type.value)
else:
text = tr('Environment type: {}').format(self.profile_type.value)
if packages := self.packages_text():
text += f'\n{packages}'
return text
return self.packages_text()
def packages_text(self, include_sub_packages: bool = False) -> str:
@ -197,9 +210,9 @@ class Profile:
if sub_profile.packages:
packages.update(sub_profile.packages)
text = str(_('Installed packages')) + ':\n'
text = tr('Installed packages') + ':\n'
for pkg in sorted(packages):
text += f'\t- {pkg}\n'
text += f' - {pkg}\n'
return text

View File

@ -1,49 +1,46 @@
from typing import Any, TYPE_CHECKING
from typing import TYPE_CHECKING, Self, override
from archinstall.lib.output import info
from archinstall.default_profiles.profile import 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
from archinstall.default_profiles.profile import ProfileType, Profile, SelectResult
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, ResultType, PreviewStyle
)
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
from archinstall.lib.models.users import User
class ServerProfile(Profile):
def __init__(self, current_value: list[Profile] = []):
def __init__(self, current_value: list[Self] = []):
super().__init__(
'Server',
ProfileType.Server,
description=str(_('Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb')),
current_selection=current_value
current_selection=current_value,
)
def do_on_select(self) -> SelectResult | None:
@override
async def do_on_select(self) -> SelectResult:
items = [
MenuItem(
p.name,
value=p,
preview_action=lambda x: x.value.preview_text()
) for p in profile_handler.get_server_profiles()
preview_action=lambda x: x.value.preview_text() if x.value else None,
)
for p in profile_handler.get_server_profiles()
]
group = MenuItemGroup(items, sort_items=True)
group.set_selected_by_value(self.current_selection)
result = SelectMenu(
result = await Selection[Self](
group,
allow_reset=True,
allow_skip=True,
preview_style=PreviewStyle.RIGHT,
preview_size='auto',
preview_frame=FrameProperties.max('Info'),
multi=True
).run()
multi=True,
preview_location='right',
).show()
match result.type_:
case ResultType.Selection:
@ -55,11 +52,18 @@ class ServerProfile(Profile):
case ResultType.Reset:
return SelectResult.ResetCurrent
def post_install(self, install_session: 'Installer') -> None:
@override
def provision(self, install_session: Installer, users: list[User]) -> None:
for profile in self.current_selection:
profile.provision(install_session, users)
@override
def post_install(self, install_session: Installer) -> None:
for profile in self.current_selection:
profile.post_install(install_session)
def install(self, install_session: 'Installer') -> None:
@override
def install(self, install_session: Installer) -> None:
server_info = self.current_selection_names()
details = ', '.join(server_info)
info(f'Now installing the selected servers: {details}')

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class CockpitProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Cockpit',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['cockpit', 'udisks2', 'packagekit']
@property
@override
def services(self) -> list[str]:
return ['cockpit.socket']

View File

@ -1,33 +1,30 @@
from typing import Union, TYPE_CHECKING
import archinstall
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import Profile, ProfileType
from archinstall.lib.models import User
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
from archinstall.lib.models.users import User
class DockerProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Docker',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['docker']
@property
@override
def services(self) -> list[str]:
return ['docker']
def post_install(self, install_session: 'Installer') -> None:
users: Union[User, list[User]] = archinstall.arguments.get('!users', [])
if not isinstance(users, list):
users = [users]
@override
def provision(self, install_session: Installer, users: list[User]) -> None:
for user in users:
install_session.arch_chroot(f'usermod -a -G docker {user.username}')

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class HttpdProfile(Profile):
def __init__(self) -> None:
super().__init__(
'httpd',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['apache']
@property
@override
def services(self) -> list[str]:
return ['httpd']

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class LighttpdProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Lighttpd',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['lighttpd']
@property
@override
def services(self) -> list[str]:
return ['lighttpd']

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -10,16 +10,19 @@ class MariadbProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Mariadb',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['mariadb']
@property
@override
def services(self) -> list[str]:
return ['mariadb']
def post_install(self, install_session: 'Installer') -> None:
@override
def post_install(self, install_session: Installer) -> None:
install_session.arch_chroot('mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql')

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class NginxProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Nginx',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['nginx']
@property
@override
def services(self) -> list[str]:
return ['nginx']

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -11,16 +11,18 @@ class PostgresqlProfile(Profile):
super().__init__(
'Postgresql',
ProfileType.ServerType,
''
)
@property
@override
def packages(self) -> list[str]:
return ['postgresql']
@property
@override
def services(self) -> list[str]:
return ['postgresql']
def post_install(self, install_session: 'Installer') -> None:
install_session.arch_chroot("initdb -D /var/lib/postgres/data", run_as='postgres')
@override
def post_install(self, install_session: Installer) -> None:
install_session.arch_chroot('initdb -D /var/lib/postgres/data', run_as='postgres')

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class SshdProfile(Profile):
def __init__(self) -> None:
super().__init__(
'sshd',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['openssh']
@property
@override
def services(self) -> list[str]:
return ['sshd']

View File

@ -1,3 +1,5 @@
from typing import override
from archinstall.default_profiles.profile import Profile, ProfileType
@ -5,13 +7,15 @@ class TomcatProfile(Profile):
def __init__(self) -> None:
super().__init__(
'Tomcat',
ProfileType.ServerType
ProfileType.ServerType,
)
@property
@override
def packages(self) -> list[str]:
return ['tomcat10']
@property
@override
def services(self) -> list[str]:
return ['tomcat10']

View File

@ -1,21 +0,0 @@
from typing import Any, TYPE_CHECKING
from archinstall.default_profiles.profile import ProfileType
from archinstall.default_profiles.xorg import XorgProfile
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
_: Any
class TailoredProfile(XorgProfile):
def __init__(self) -> None:
super().__init__('52-54-00-12-34-56', ProfileType.Tailored, description='')
@property
def packages(self) -> list[str]:
return ['nano', 'wget', 'git']
def install(self, install_session: 'Installer') -> None:
super().install(install_session)
# do whatever you like here :)

View File

@ -1,9 +1,9 @@
from typing import Any, TYPE_CHECKING
from typing import TYPE_CHECKING, override
from archinstall.default_profiles.profile import Profile, ProfileType
from archinstall.default_profiles.profile import DisplayServerType, Profile, ProfileType
if TYPE_CHECKING:
_: Any
from archinstall.lib.installer import Installer
class XorgProfile(Profile):
@ -11,26 +11,22 @@ class XorgProfile(Profile):
self,
name: str = 'Xorg',
profile_type: ProfileType = ProfileType.Xorg,
description: str = str(_('Installs a minimal system as well as xorg and graphics drivers.')),
advanced: bool = False
):
super().__init__(
name,
profile_type,
description=description,
support_gfx_driver=True,
advanced=advanced
display_server=DisplayServerType.Xorg,
)
def preview_text(self) -> str | None:
text = str(_('Environment type: {}')).format(self.profile_type.value)
if packages := self.packages_text():
text += f'\n{packages}'
return text
@property
@override
def packages(self) -> list[str]:
return [
'xorg-server'
'xorg-server',
'xorg-xinit',
]
@override
def install(self, install_session: Installer) -> None:
install_session.add_additional_packages(self.packages)

View File

@ -0,0 +1,51 @@
from typing import TYPE_CHECKING
from archinstall.applications.audio import AudioApp
from archinstall.applications.bluetooth import BluetoothApp
from archinstall.applications.firewall import FirewallApp
from archinstall.applications.fonts import FontsApp
from archinstall.applications.power_management import PowerManagementApp
from archinstall.applications.print_service import PrintServiceApp
from archinstall.lib.models import Audio
from archinstall.lib.models.application import ApplicationConfiguration
from archinstall.lib.models.users import User
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class ApplicationHandler:
def __init__(self) -> None:
pass
def install_applications(self, install_session: Installer, app_config: ApplicationConfiguration, users: list[User] | None = None) -> None:
if app_config.bluetooth_config and app_config.bluetooth_config.enabled:
BluetoothApp().install(install_session)
if app_config.audio_config and app_config.audio_config.audio != Audio.NO_AUDIO:
AudioApp().install(
install_session,
app_config.audio_config,
users,
)
if app_config.power_management_config:
PowerManagementApp().install(
install_session,
app_config.power_management_config,
)
if app_config.print_service_config and app_config.print_service_config.enabled:
PrintServiceApp().install(install_session)
if app_config.firewall_config:
FirewallApp().install(
install_session,
app_config.firewall_config,
)
if app_config.fonts_config:
FontsApp().install(
install_session,
app_config.fonts_config,
)

View File

@ -0,0 +1,263 @@
from typing import override
from archinstall.lib.hardware import SysInfo
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.models.application import (
ApplicationConfiguration,
Audio,
AudioConfiguration,
BluetoothConfiguration,
Firewall,
FirewallConfiguration,
FontPackage,
FontsConfiguration,
PowerManagement,
PowerManagementConfiguration,
PrintServiceConfiguration,
)
from archinstall.lib.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
def __init__(
self,
preset: ApplicationConfiguration | None = None,
):
if preset:
self._app_config = preset
else:
self._app_config = ApplicationConfiguration()
menu_options = self._define_menu_options()
self._item_group = MenuItemGroup(menu_options, checkmarks=True)
super().__init__(
self._item_group,
config=self._app_config,
allow_reset=True,
)
@override
async def show(self) -> ApplicationConfiguration | None:
_ = await super().show()
return self._app_config
def _define_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=tr('Bluetooth'),
action=select_bluetooth,
value=self._app_config.bluetooth_config,
preview_action=self._prev_bluetooth,
key='bluetooth_config',
),
MenuItem(
text=tr('Audio'),
action=select_audio,
preview_action=self._prev_audio,
key='audio_config',
),
MenuItem(
text=tr('Print service'),
action=select_print_service,
preview_action=self._prev_print_service,
key='print_service_config',
),
MenuItem(
text=tr('Power management'),
action=select_power_management,
preview_action=self._prev_power_management,
enabled=SysInfo.has_battery(),
key='power_management_config',
),
MenuItem(
text=tr('Firewall'),
action=select_firewall,
preview_action=self._prev_firewall,
key='firewall_config',
),
MenuItem(
text=tr('Additional fonts'),
action=select_fonts,
value=self._app_config.fonts_config,
preview_action=self._prev_fonts,
key='fonts_config',
),
]
def _prev_power_management(self, item: MenuItem) -> str | None:
if item.value is not None:
config: PowerManagementConfiguration = item.value
return f'{tr("Power management")}: {config.power_management.value}'
return None
def _prev_bluetooth(self, item: MenuItem) -> str | None:
if item.value is not None:
bluetooth_config: BluetoothConfiguration = item.value
output = f'{tr("Bluetooth")}: '
output += tr('Enabled') if bluetooth_config.enabled else tr('Disabled')
return output
return None
def _prev_audio(self, item: MenuItem) -> str | None:
if item.value is not None:
config: AudioConfiguration = item.value
return f'{tr("Audio")}: {config.audio.value}'
return None
def _prev_print_service(self, item: MenuItem) -> str | None:
if item.value is not None:
print_service_config: PrintServiceConfiguration = item.value
output = f'{tr("Print service")}: '
output += tr('Enabled') if print_service_config.enabled else tr('Disabled')
return output
return None
def _prev_firewall(self, item: MenuItem) -> str | None:
if item.value is not None:
config: FirewallConfiguration = item.value
return f'{tr("Firewall")}: {config.firewall.value}'
return None
def _prev_fonts(self, item: MenuItem) -> str | None:
if item.value is not None:
config: FontsConfiguration = item.value
packages = ', '.join(f.value for f in config.fonts)
return f'{tr("Additional fonts")}: {packages}'
return None
async def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None:
group = MenuItemGroup.from_enum(PowerManagement)
if preset:
group.set_focus_by_value(preset.power_management)
result = await Selection[PowerManagement](
group,
allow_skip=True,
allow_reset=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return PowerManagementConfiguration(power_management=result.get_value())
case ResultType.Reset:
return None
async def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None:
header = tr('Would you like to configure Bluetooth?') + '\n'
preset_val = preset.enabled if preset else False
result = await Confirmation(
header=header,
allow_skip=True,
preset=preset_val,
).show()
match result.type_:
case ResultType.Selection:
return BluetoothConfiguration(result.get_value())
case ResultType.Skip:
return preset
case _:
raise ValueError('Unhandled result type')
async def select_print_service(preset: PrintServiceConfiguration | None) -> PrintServiceConfiguration | None:
header = tr('Would you like to configure the print service?') + '\n'
preset_val = preset.enabled if preset else False
result = await Confirmation(
header=header,
allow_skip=True,
preset=preset_val,
).show()
match result.type_:
case ResultType.Selection:
result.get_value()
return PrintServiceConfiguration(result.get_value())
case ResultType.Skip:
return preset
case _:
raise ValueError('Unhandled result type')
async def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None:
items = [MenuItem(a.value, value=a) for a in Audio]
group = MenuItemGroup(items)
if preset:
group.set_focus_by_value(preset.audio)
result = await Selection[Audio](
group,
header=tr('Select audio configuration'),
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return AudioConfiguration(audio=result.get_value())
case ResultType.Reset:
raise ValueError('Unhandled result type')
async def select_firewall(preset: FirewallConfiguration | None = None) -> FirewallConfiguration | None:
group = MenuItemGroup.from_enum(Firewall)
if preset:
group.set_focus_by_value(preset.firewall)
result = await Selection[Firewall](
group,
allow_skip=True,
allow_reset=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return FirewallConfiguration(firewall=result.get_value())
case ResultType.Reset:
return None
async def select_fonts(preset: FontsConfiguration | None = None) -> FontsConfiguration | None:
items = [MenuItem(f'{f.value} ({f.description()})', value=f) for f in FontPackage]
group = MenuItemGroup(items)
if preset:
for f in preset.fonts:
group.set_selected_by_value(f)
result = await Selection[FontPackage](
group,
header=tr('Select font packages to install'),
allow_skip=True,
allow_reset=True,
multi=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
selected = result.get_values()
if selected:
return FontsConfiguration(fonts=selected)
return None
case ResultType.Reset:
return None

671
archinstall/lib/args.py Normal file
View File

@ -0,0 +1,671 @@
import argparse
import json
import os
import sys
import urllib.error
import urllib.parse
from argparse import ArgumentParser, Namespace
from dataclasses import dataclass, field
from enum import Enum, StrEnum
from pathlib import Path
from typing import Any, Self
from urllib.request import Request, urlopen
from pydantic.dataclasses import dataclass as p_dataclass
from archinstall.lib.crypt import decrypt
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
from archinstall.lib.models.authentication import AuthenticationConfiguration
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.config import SubConfig
from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguration
from archinstall.lib.models.locale import LocaleConfiguration
from archinstall.lib.models.mirrors import MirrorConfiguration
from archinstall.lib.models.network import NetworkConfiguration
from archinstall.lib.models.package_types import DEFAULT_KERNEL
from archinstall.lib.models.packages import Repository
from archinstall.lib.models.pacman import PacmanConfiguration
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.version import get_version
from archinstall.tui.components import tui
class SubCommand(Enum):
SHARE_LOG = 'share-log'
@p_dataclass
class Arguments:
config: Path | None = None
config_url: str | None = None
creds: Path | None = None
creds_url: str | None = None
creds_decryption_key: str | None = None
silent: bool = False
dry_run: bool = False
script: str | None = None
mountpoint: Path = Path('/mnt')
skip_ntp: bool = False
skip_wkd: bool = False
skip_boot: bool = False
debug: bool = False
offline: bool = False
no_pkg_lookups: bool = False
plugin: str | None = None
skip_version_check: bool = False
skip_wifi_check: bool = False
advanced: bool = False
verbose: bool = False
command: SubCommand | None = None
class ArchConfigType(StrEnum):
VERSION = 'version'
SCRIPT = 'script'
LOCALE_CONFIG = 'locale_config'
ARCHINSTALL_LANGUAGE = 'archinstall_language'
DISK_CONFIG = 'disk_config'
PROFILE_CONFIG = 'profile_config'
MIRROR_CONFIG = 'mirror_config'
NETWORK_CONFIG = 'network_config'
BOOTLOADER_CONFIG = 'bootloader_config'
APP_CONFIG = 'app_config'
AUTH_CONFIG = 'auth_config'
SWAP = 'swap'
USERS = 'users'
ROOT_ENC_PASSWORD = 'root_enc_password'
ENCRYPTION_PASSWORD = 'encryption_password'
HOSTNAME = 'hostname'
KERNELS = 'kernels'
NTP = 'ntp'
TIMEZONE = 'timezone'
SERVICES = 'services'
PACKAGES = 'packages'
PACMAN_CONFIG = 'pacman_config'
CUSTOM_COMMANDS = 'custom_commands'
def text(self) -> str:
match self:
case ArchConfigType.ARCHINSTALL_LANGUAGE:
return tr('ArchInstall Language')
case ArchConfigType.VERSION:
return tr('Version')
case ArchConfigType.SCRIPT:
return tr('Installation Script')
case ArchConfigType.LOCALE_CONFIG:
return tr('Locales')
case ArchConfigType.DISK_CONFIG:
return tr('Disk configuration')
case ArchConfigType.PROFILE_CONFIG:
return tr('Profile')
case ArchConfigType.MIRROR_CONFIG:
return tr('Mirrors and repositories')
case ArchConfigType.NETWORK_CONFIG:
return tr('Network')
case ArchConfigType.BOOTLOADER_CONFIG:
return tr('Bootloader')
case ArchConfigType.APP_CONFIG:
return tr('Application')
case ArchConfigType.AUTH_CONFIG:
return tr('Authentication')
case ArchConfigType.SWAP:
return tr('Swap')
case ArchConfigType.HOSTNAME:
return tr('Hostname')
case ArchConfigType.KERNELS:
return tr('Kernels')
case ArchConfigType.NTP:
return tr('Automatic time sync (NTP)')
case ArchConfigType.TIMEZONE:
return tr('Timezone')
case ArchConfigType.SERVICES:
return tr('Services')
case ArchConfigType.PACKAGES:
return tr('Additional packages')
case ArchConfigType.PACMAN_CONFIG:
return tr('Pacman')
case ArchConfigType.CUSTOM_COMMANDS:
return tr('Custom commands')
case ArchConfigType.USERS:
return tr('Users')
case ArchConfigType.ROOT_ENC_PASSWORD:
return tr('Root encrypted password')
case ArchConfigType.ENCRYPTION_PASSWORD:
return tr('Disk encryption password')
@dataclass
class ArchConfig:
version: str | None = None
script: str | None = None
locale_config: LocaleConfiguration | None = None
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
disk_config: DiskLayoutConfiguration | None = None
profile_config: ProfileConfiguration | None = None
mirror_config: MirrorConfiguration | None = None
network_config: NetworkConfiguration | None = None
bootloader_config: BootloaderConfiguration | None = None
app_config: ApplicationConfiguration | None = None
auth_config: AuthenticationConfiguration | None = None
swap: ZramConfiguration | None = None
hostname: str = 'archlinux'
kernels: list[str] = field(default_factory=lambda: [DEFAULT_KERNEL.value])
ntp: bool = True
packages: list[str] = field(default_factory=list)
pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration.default)
timezone: str = 'UTC'
services: list[str] = field(default_factory=list)
custom_commands: list[str] = field(default_factory=list)
def unsafe_config(self) -> dict[ArchConfigType, Any]:
config: dict[ArchConfigType, list[UserSerialization] | str | None] = {}
if self.auth_config:
if self.auth_config.users:
config[ArchConfigType.USERS] = [user.json() for user in self.auth_config.users]
if self.auth_config.root_enc_password:
config[ArchConfigType.ROOT_ENC_PASSWORD] = self.auth_config.root_enc_password.enc_password
if self.disk_config:
disk_encryption = self.disk_config.disk_encryption
if disk_encryption and disk_encryption.encryption_password:
config[ArchConfigType.ENCRYPTION_PASSWORD] = disk_encryption.encryption_password.plaintext
return config
def safe_config(self) -> dict[ArchConfigType, Any]:
base_config: dict[ArchConfigType, Any] = {
ArchConfigType.VERSION: self.version,
ArchConfigType.SCRIPT: self.script,
ArchConfigType.ARCHINSTALL_LANGUAGE: self.archinstall_language.json(),
}
base_config.update(self.plain_cfg())
sub_config = self.sub_cfg()
for config_type, value in sub_config.items():
if not hasattr(value, 'json'):
raise ValueError(f'Config value for {config_type} must implement json() method')
base_config[config_type] = value.json()
return base_config
def plain_cfg(self) -> dict[ArchConfigType, str | list[str] | bool]:
return {
ArchConfigType.HOSTNAME: self.hostname,
ArchConfigType.KERNELS: self.kernels,
ArchConfigType.NTP: self.ntp,
ArchConfigType.TIMEZONE: self.timezone,
ArchConfigType.SERVICES: self.services,
ArchConfigType.PACKAGES: self.packages,
ArchConfigType.CUSTOM_COMMANDS: self.custom_commands,
}
def sub_cfg(self) -> dict[ArchConfigType, SubConfig]:
cfg: dict[ArchConfigType, SubConfig] = {
ArchConfigType.PACMAN_CONFIG: self.pacman_config,
}
if self.mirror_config:
cfg[ArchConfigType.MIRROR_CONFIG] = self.mirror_config
if self.bootloader_config:
cfg[ArchConfigType.BOOTLOADER_CONFIG] = self.bootloader_config
if self.disk_config:
cfg[ArchConfigType.DISK_CONFIG] = self.disk_config
if self.swap:
cfg[ArchConfigType.SWAP] = self.swap
if self.auth_config:
cfg[ArchConfigType.AUTH_CONFIG] = self.auth_config
if self.locale_config:
cfg[ArchConfigType.LOCALE_CONFIG] = self.locale_config
if self.profile_config:
cfg[ArchConfigType.PROFILE_CONFIG] = self.profile_config
if self.network_config:
cfg[ArchConfigType.NETWORK_CONFIG] = self.network_config
if self.app_config:
cfg[ArchConfigType.APP_CONFIG] = self.app_config
return cfg
@classmethod
def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self:
arch_config = cls()
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
if script := args_config.get('script', None):
arch_config.script = script
if archinstall_lang := args_config.get('archinstall-language', None):
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
translation_handler.activate(arch_config.archinstall_language, set_font=False)
if disk_config := args_config.get('disk_config', {}):
enc_password = args_config.get('encryption_password', '')
password = Password(plaintext=enc_password) if enc_password else None
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config, password)
# DEPRECATED
# backwards compatibility for main level disk_encryption entry
disk_encryption: DiskEncryption | None = None
if args_config.get('disk_encryption', None) is not None and arch_config.disk_config is not None:
disk_encryption = DiskEncryption.parse_arg(
arch_config.disk_config,
args_config['disk_encryption'],
Password(plaintext=args_config.get('encryption_password', '')),
)
if disk_encryption:
arch_config.disk_config.disk_encryption = disk_encryption
if profile_config := args_config.get('profile_config', None):
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
if mirror_config := args_config.get('mirror_config', None):
backwards_compatible_repo = []
if additional_repositories := args_config.get('additional-repositories', []):
backwards_compatible_repo = [Repository(r) for r in additional_repositories]
arch_config.mirror_config = MirrorConfiguration.parse_args(
mirror_config,
backwards_compatible_repo,
)
if net_config := args_config.get('network_config', None):
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
if bootloader_config_dict := args_config.get('bootloader_config', None):
arch_config.bootloader_config = BootloaderConfiguration.parse_arg(bootloader_config_dict, args.skip_boot)
# DEPRECATED: separate bootloader and uki fields (backward compatibility)
elif bootloader_str := args_config.get('bootloader', None):
bootloader = Bootloader.from_arg(bootloader_str, args.skip_boot)
uki = args_config.get('uki', False)
if uki and not bootloader.has_uki_support():
uki = False
arch_config.bootloader_config = BootloaderConfiguration(bootloader=bootloader, uki=uki, removable=True)
# deprecated: backwards compatibility
audio_config_args = args_config.get('audio_config', None)
app_config_args = args_config.get('app_config', None)
if audio_config_args is not None or app_config_args is not None:
arch_config.app_config = ApplicationConfiguration.parse_arg(app_config_args, audio_config_args)
if auth_config_args := args_config.get('auth_config', None):
arch_config.auth_config = AuthenticationConfiguration.parse_arg(auth_config_args)
if hostname := args_config.get('hostname', ''):
arch_config.hostname = hostname
if kernels := args_config.get('kernels', []):
arch_config.kernels = kernels
arch_config.ntp = args_config.get('ntp', True)
if packages := args_config.get('packages', []):
arch_config.packages = packages
if pacman_config := args_config.get('pacman_config', None):
arch_config.pacman_config = PacmanConfiguration.parse_arg(pacman_config)
elif parallel_downloads := args_config.get('parallel_downloads', 0):
arch_config.pacman_config = PacmanConfiguration(parallel_downloads=int(parallel_downloads))
swap_arg = args_config.get('swap')
if swap_arg is not None:
arch_config.swap = ZramConfiguration.parse_arg(swap_arg)
if timezone := args_config.get('timezone', 'UTC'):
arch_config.timezone = timezone
if services := args_config.get('services', []):
arch_config.services = services
# DEPRECATED: backwards compatibility
root_password = None
if root_password := args_config.get('!root-password', None):
root_password = Password(plaintext=root_password)
if enc_password := args_config.get('root_enc_password', None):
root_password = Password(enc_password=enc_password)
if root_password is not None:
if arch_config.auth_config is None:
arch_config.auth_config = AuthenticationConfiguration()
arch_config.auth_config.root_enc_password = root_password
# DEPRECATED: backwards compatibility
users: list[User] = []
if args_users := args_config.get('!users', None):
users = User.parse_arguments(args_users)
if args_users := args_config.get('users', None):
users = User.parse_arguments(args_users)
if users:
if arch_config.auth_config is None:
arch_config.auth_config = AuthenticationConfiguration()
arch_config.auth_config.users = users
if custom_commands := args_config.get('custom_commands', []):
arch_config.custom_commands = custom_commands
return arch_config
class ArchConfigHandler:
def __init__(self) -> None:
self._parser: ArgumentParser = self._define_arguments()
self._add_sub_parsers()
self._args: Arguments = self._parse_args()
config = self._parse_config()
try:
self._config = ArchConfig.from_config(config, self._args)
self._config.version = get_version()
except ValueError as err:
warn(str(err))
sys.exit(1)
@property
def config(self) -> ArchConfig:
return self._config
@property
def args(self) -> Arguments:
return self._args
def get_script(self) -> str:
if script := self.args.script:
return script
if script := self.config.script:
return script
return 'guided'
def print_help(self) -> None:
self._parser.print_help()
def _add_sub_parsers(self) -> None:
subparsers = self._parser.add_subparsers(dest='command', help='Available subcommands')
_ = subparsers.add_parser(SubCommand.SHARE_LOG.value, help='Upload log file to public server')
def _define_arguments(self) -> ArgumentParser:
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'-v',
'--version',
action='version',
default=False,
version='%(prog)s ' + get_version(),
)
parser.add_argument(
'--config',
type=Path,
nargs='?',
default=None,
help='JSON configuration file',
)
parser.add_argument(
'--config-url',
type=str,
nargs='?',
default=None,
help='Url to a JSON configuration file',
)
parser.add_argument(
'--creds',
type=Path,
nargs='?',
default=None,
help='JSON credentials configuration file',
)
parser.add_argument(
'--creds-url',
type=str,
nargs='?',
default=None,
help='Url to a JSON credentials configuration file',
)
parser.add_argument(
'--creds-decryption-key',
type=str,
nargs='?',
default=None,
help='Decryption key for credentials file',
)
parser.add_argument(
'--silent',
action='store_true',
default=False,
help='WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored',
)
parser.add_argument(
'--dry-run',
'--dry_run',
action='store_true',
default=False,
help='Generates a configuration file and then exits instead of performing an installation',
)
parser.add_argument(
'--script',
nargs='?',
help='Script to run for installation',
type=str,
)
parser.add_argument(
'--mountpoint',
type=Path,
nargs='?',
default=Path('/mnt'),
help='Define an alternate mount point for installation',
)
parser.add_argument(
'--skip-ntp',
action='store_true',
help='Disables NTP checks during installation',
default=False,
)
parser.add_argument(
'--skip-wkd',
action='store_true',
help='Disables checking if archlinux keyring wkd sync is complete.',
default=False,
)
parser.add_argument(
'--skip-boot',
action='store_true',
help='Disables installation of a boot loader (note: only use this when problems arise with the boot loader step).',
default=False,
)
parser.add_argument(
'--debug',
action='store_true',
default=False,
help='Adds debug info into the log',
)
parser.add_argument(
'--offline',
action='store_true',
default=False,
help='Disabled online upstream services such as package search and key-ring auto update.',
)
parser.add_argument(
'--no-pkg-lookups',
action='store_true',
default=False,
help='Disabled package validation specifically prior to starting installation.',
)
parser.add_argument(
'--plugin',
nargs='?',
type=str,
default=None,
help='File path to a plugin to load',
)
parser.add_argument(
'--skip-version-check',
action='store_true',
default=False,
help='Skip the version check when running archinstall',
)
parser.add_argument(
'--skip-wifi-check',
action='store_true',
default=False,
help='Skip wifi check when running archinstall',
)
parser.add_argument(
'--advanced',
action='store_true',
default=False,
help='Enabled advanced options',
)
parser.add_argument(
'--verbose',
action='store_true',
default=False,
help='Enabled verbose options',
)
return parser
def _parse_args(self) -> Arguments:
argparse_args = vars(self._parser.parse_args())
args: Arguments = Arguments(**argparse_args)
# amend the parameters (check internal consistency)
# Installation can't be silent if config is not passed
if args.config is None and args.config_url is None:
args.silent = False
if args.debug:
warn(f'Warning: --debug mode will write certain credentials to {logger.path}!')
if args.plugin:
plugin_path = Path(args.plugin)
load_plugin(plugin_path)
if args.creds_decryption_key is None:
if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'):
args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY')
return args
def _parse_config(self) -> dict[str, Any]:
config: dict[str, Any] = {}
config_data: str | None = None
creds_data: str | None = None
if self._args.config is not None:
config_data = self._read_file(self._args.config)
elif self._args.config_url is not None:
config_data = self._fetch_from_url(self._args.config_url)
if config_data is not None:
config.update(json.loads(config_data))
if self._args.creds is not None:
creds_data = self._read_file(self._args.creds)
elif self._args.creds_url is not None:
creds_data = self._fetch_from_url(self._args.creds_url)
if creds_data is not None:
json_data = self._process_creds_data(creds_data)
if json_data is not None:
config.update(json_data)
config = self._cleanup_config(config)
return config
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
if creds_data.startswith('$'): # encrypted data
if self._args.creds_decryption_key is not None:
try:
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
return json.loads(creds_data)
except ValueError as err:
if 'Invalid password' in str(err):
error(tr('Incorrect credentials file decryption password'))
sys.exit(1)
else:
debug(f'Error decrypting credentials file: {err}')
raise err from err
else:
header = tr('Enter credentials file decryption password')
wrong_pwd_text = tr('Incorrect password')
prompt = header
while True:
decryption_pwd: Password | None = tui.run(
lambda p=prompt: get_password( # type: ignore[misc]
header=p,
allow_skip=False,
no_confirmation=True,
)
)
if not decryption_pwd:
return None
try:
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
break
except ValueError as err:
if 'Invalid password' in str(err):
debug('Incorrect credentials file decryption password')
prompt = f'{header}' + f'\n\n{wrong_pwd_text}'
else:
debug(f'Error decrypting credentials file: {err}')
raise err from err
return json.loads(creds_data)
def _fetch_from_url(self, url: str) -> str:
if urllib.parse.urlparse(url).scheme:
try:
req = Request(url, headers={'User-Agent': 'ArchInstall'})
with urlopen(req) as resp:
return resp.read().decode('utf-8')
except urllib.error.HTTPError as err:
error(f'Could not fetch JSON from {url}: {err}')
else:
error('Not a valid url')
sys.exit(1)
def _read_file(self, path: Path) -> str:
if not path.exists():
error(f'Could not find file {path}')
sys.exit(1)
return path.read_text()
def _cleanup_config(self, config: Namespace | dict[str, Any]) -> dict[str, Any]:
clean_args = {}
for key, val in config.items():
if isinstance(val, dict):
val = self._cleanup_config(val)
if val is not None:
clean_args[key] = val
return clean_args

View File

@ -0,0 +1,126 @@
import getpass
from pathlib import Path
from typing import TYPE_CHECKING
from archinstall.lib.command import SysCommandWorker
from archinstall.lib.log import debug, info
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.users import User
from archinstall.lib.translationhandler import tr
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class AuthenticationHandler:
def setup_auth(
self,
install_session: Installer,
auth_config: AuthenticationConfiguration,
hostname: str,
) -> None:
if auth_config.u2f_config and auth_config.users is not None:
self._setup_u2f_login(install_session, auth_config.u2f_config, auth_config.users, hostname)
def _setup_u2f_login(self, install_session: Installer, u2f_config: U2FLoginConfiguration, users: list[User], hostname: str) -> None:
self._configure_u2f_mapping(install_session, u2f_config, users, hostname)
self._update_pam_config(install_session, u2f_config)
def _update_pam_config(
self,
install_session: Installer,
u2f_config: U2FLoginConfiguration,
) -> None:
match u2f_config.u2f_login_method:
case U2FLoginMethod.Passwordless:
config_entry = 'auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue'
case U2FLoginMethod.SecondFactor:
config_entry = 'auth required pam_u2f.so authfile=/etc/u2f_mappings cue'
case _:
raise ValueError(f'Unknown U2F login method: {u2f_config.u2f_login_method}')
debug(f'U2F PAM configuration: {config_entry}')
debug(f'Passwordless sudo enabled: {u2f_config.passwordless_sudo}')
sudo_config = install_session.target / 'etc/pam.d/sudo'
sys_login = install_session.target / 'etc/pam.d/system-login'
if u2f_config.passwordless_sudo:
self._add_u2f_entry(sudo_config, config_entry)
self._add_u2f_entry(sys_login, config_entry)
def _add_u2f_entry(self, file: Path, entry: str) -> None:
if not file.exists():
debug(f'File does not exist: {file}')
return
content = file.read_text().splitlines()
# remove any existing u2f auth entry
content = [line for line in content if 'pam_u2f.so' not in line]
# add the u2f auth entry as the first one after comments
for i, line in enumerate(content):
if not line.startswith('#'):
content.insert(i, entry)
break
else:
content.append(entry)
file.write_text('\n'.join(content) + '\n')
def _configure_u2f_mapping(
self,
install_session: Installer,
u2f_config: U2FLoginConfiguration,
users: list[User],
hostname: str,
) -> None:
debug(f'Setting up U2F login: {u2f_config.u2f_login_method.value}')
install_session.pacman.strap('pam-u2f')
print(tr('Setting up U2F login: {}').format(u2f_config.u2f_login_method.value))
# https://developers.yubico.com/pam-u2f/
u2f_auth_file = install_session.target / 'etc/u2f_mappings'
u2f_auth_file.touch()
existing_keys = u2f_auth_file.read_text()
registered_keys: list[str] = []
for user in users:
print('')
info(tr('Setting up U2F device for user: {}').format(user.username))
info(tr('You may need to enter the PIN and then touch your U2F device to register it'))
cmd = ' '.join(
['arch-chroot', '-S', str(install_session.target), 'pamu2fcfg', '-u', user.username, '-o', f'pam://{hostname}', '-i', f'pam://{hostname}']
)
debug(f'Enrolling U2F device: {cmd}')
worker = SysCommandWorker(cmd, peek_output=True)
pin_inputted = False
while worker.is_alive():
if pin_inputted is False:
if bytes('enter pin for', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(''), 'UTF-8'))
pin_inputted = True
output = worker.decode().strip().splitlines()
debug(f'Output from pamu2fcfg: {output}')
key = output[-1].strip()
registered_keys.append(key)
all_keys = '\n'.join(registered_keys)
if existing_keys:
existing_keys += f'\n{all_keys}'
else:
existing_keys = all_keys
u2f_auth_file.write_text(existing_keys)

View File

@ -0,0 +1,147 @@
from typing import override
from archinstall.lib.disk.fido import Fido2
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Confirmation, Selection
from archinstall.lib.menu.util import get_password
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.users import Password, User
from archinstall.lib.translationhandler import tr
from archinstall.lib.user.user_menu import select_users
from archinstall.lib.utils.format import as_table
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
def __init__(self, preset: AuthenticationConfiguration | None = None):
if preset:
self._auth_config = preset
else:
self._auth_config = AuthenticationConfiguration()
menu_options = self._define_menu_options()
self._item_group = MenuItemGroup(menu_options, checkmarks=True)
super().__init__(
self._item_group,
config=self._auth_config,
allow_reset=True,
)
@override
async def show(self) -> AuthenticationConfiguration | None:
return await super().show()
def _define_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=tr('Root password'),
action=lambda x: select_root_password(),
preview_action=self._prev_root_pwd,
key='root_enc_password',
),
MenuItem(
text=tr('User account'),
action=self._create_user_account,
preview_action=self._prev_users,
key='users',
),
MenuItem(
text=tr('U2F login setup'),
action=select_u2f_login,
value=self._auth_config.u2f_config,
preview_action=self._prev_u2f_login,
key='u2f_config',
),
]
async def _create_user_account(self, preset: list[User] | None = None) -> list[User]:
preset = [] if preset is None else preset
users = await select_users(preset=preset)
return users
def _prev_users(self, item: MenuItem) -> str | None:
users: list[User] | None = item.value
if users:
return as_table(users)
return None
def _prev_root_pwd(self, item: MenuItem) -> str | None:
if item.value is not None:
password: Password = item.value
return f'{tr("Root password")}: {password.hidden()}'
return None
def _depends_on_u2f(self) -> bool:
devices = Fido2.get_fido2_devices()
if not devices:
return False
return True
def _prev_u2f_login(self, item: MenuItem) -> str | None:
if item.value is not None:
u2f_config: U2FLoginConfiguration = item.value
login_method = u2f_config.u2f_login_method.display_value()
output = tr('U2F login method: ') + login_method
output += '\n'
output += tr('Passwordless sudo: ') + (tr('Enabled') if u2f_config.passwordless_sudo else tr('Disabled'))
return output
devices = Fido2.get_fido2_devices()
if not devices:
return tr('No U2F devices found')
return None
async def select_root_password() -> Password | None:
password = await get_password(header=tr('Enter root password'), allow_skip=True)
return password
async def select_u2f_login(preset: U2FLoginConfiguration | None) -> U2FLoginConfiguration | None:
devices = Fido2.get_fido2_devices()
if not devices:
return None
items = []
for method in U2FLoginMethod:
items.append(MenuItem(method.display_value(), value=method))
group = MenuItemGroup(items)
if preset is not None:
group.set_selected_by_value(preset.u2f_login_method)
result = await Selection[U2FLoginMethod](
group,
allow_skip=True,
allow_reset=True,
).show()
match result.type_:
case ResultType.Selection:
u2f_method = result.get_value()
header = tr('Enable passwordless sudo?')
result_sudo = await Confirmation(
header=header,
allow_skip=True,
preset=False,
).show()
passwordless_sudo = result_sudo.item() == MenuItem.yes()
return U2FLoginConfiguration(
u2f_login_method=u2f_method,
passwordless_sudo=passwordless_sudo,
)
case ResultType.Skip:
return preset
case ResultType.Reset:
return None

View File

@ -1,37 +1,48 @@
import time
from collections.abc import Iterator
from .exceptions import SysCallError
from .general import SysCommand, SysCommandWorker, locate_binary
from .installer import Installer
from .output import error
from .storage import storage
from pathlib import Path
from types import TracebackType
from typing import ClassVar, Self
from archinstall.lib.command import SysCommand, SysCommandWorker
from archinstall.lib.exceptions import SysCallError
from archinstall.lib.log import error
class Boot:
def __init__(self, installation: Installer):
self.instance = installation
_active_boot: ClassVar[Self | None] = None
def __init__(self, path: Path | str):
if isinstance(path, Path):
path = str(path)
self.path = path
self.container_name = 'archinstall'
self.session: SysCommandWorker | None = None
self.ready = False
def __enter__(self) -> 'Boot':
if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:
raise KeyError("Archinstall only supports booting up one instance, and a active session is already active and it is not this one.")
def __enter__(self) -> Self:
if Boot._active_boot and Boot._active_boot.path != self.path:
raise KeyError('Archinstall only supports booting up one instance and another session is already active.')
if existing_session:
self.session = existing_session.session
self.ready = existing_session.ready
if Boot._active_boot:
self.session = Boot._active_boot.session
self.ready = Boot._active_boot.ready
else:
# '-P' or --console=pipe could help us not having to do a bunch
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
self.session = SysCommandWorker([
'/usr/bin/systemd-nspawn',
'-D', str(self.instance.target),
'--timezone=off',
'-b',
'--no-pager',
'--machine', self.container_name
])
self.session = SysCommandWorker(
[
'systemd-nspawn',
'-D',
self.path,
'--timezone=off',
'-b',
'--no-pager',
'--machine',
self.container_name,
]
)
if not self.ready and self.session:
while self.session.is_alive():
@ -39,17 +50,17 @@ class Boot:
self.ready = True
break
storage['active_boot'] = self
Boot._active_boot = self
return self
def __exit__(self, *args: str, **kwargs: str) -> None:
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
if len(args) >= 2 and args[1]:
if exc_type is not None:
error(
args[1],
f"The error above occurred in a temporary boot-up of the installation {self.instance}"
str(exc_value),
f'The error above occurred in a temporary boot-up of the installation {self.path!r}',
)
shutdown = None
@ -68,19 +79,18 @@ class Boot:
shutdown_exit_code = shutdown.exit_code
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
storage['active_boot'] = None
Boot._active_boot = None
else:
session_exit_code = self.session.exit_code if self.session else -1
raise SysCallError(
f"Could not shut down temporary boot of {self.instance}: {session_exit_code}/{shutdown_exit_code}",
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code]))
f'Could not shut down temporary boot of {self.path!r}: {session_exit_code}/{shutdown_exit_code}',
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
)
def __iter__(self) -> Iterator[bytes]:
if self.session:
for value in self.session:
yield value
yield from self.session
def __contains__(self, key: bytes) -> bool:
if self.session is None:
@ -94,18 +104,8 @@ class Boot:
return self.session.is_alive()
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand:
if cmd[0][0] != '/' and cmd[0][:2] != './':
# This check is also done in SysCommand & SysCommandWorker.
# However, that check is done for `machinectl` and not for our chroot command.
# So this wrapper for SysCommand will do this additionally.
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand: # type: ignore[no-untyped-def]
return SysCommand(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
cmd[0] = locate_binary(cmd[0])
return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker:
if cmd[0][0] != '/' and cmd[0][:2] != './':
cmd[0] = locate_binary(cmd[0])
return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker: # type: ignore[no-untyped-def]
return SysCommandWorker(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)

View File

View File

@ -0,0 +1,217 @@
import textwrap
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.translationhandler import tr
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
def __init__(
self,
bootloader_conf: BootloaderConfiguration,
uefi: bool,
skip_boot: bool = False,
):
self._bootloader_conf = bootloader_conf
self._skip_boot = skip_boot
self._uefi = uefi
menu_options = self._define_menu_options()
self._item_group = MenuItemGroup(menu_options, sort_items=False, checkmarks=True)
super().__init__(
self._item_group,
config=self._bootloader_conf,
allow_reset=False,
)
def _define_menu_options(self) -> list[MenuItem]:
bootloader = self._bootloader_conf.bootloader
# UKI availability
uki_enabled = self._uefi and bootloader.has_uki_support()
if not uki_enabled:
self._bootloader_conf.uki = False
# Removable availability
removable_enabled = self._uefi and bootloader.has_removable_support()
if not removable_enabled:
self._bootloader_conf.removable = False
return [
MenuItem(
text=tr('Bootloader'),
action=self._select_bootloader,
value=self._bootloader_conf.bootloader,
preview_action=self._prev_bootloader,
mandatory=True,
key='bootloader',
),
MenuItem(
text=tr('Unified kernel images'),
action=self._select_uki,
value=self._bootloader_conf.uki,
preview_action=self._prev_uki,
key='uki',
enabled=uki_enabled,
),
MenuItem(
text=tr('Install to removable location'),
action=self._select_removable,
value=self._bootloader_conf.removable,
preview_action=self._prev_removable,
key='removable',
enabled=removable_enabled,
),
]
def _prev_bootloader(self, item: MenuItem) -> str | None:
if item.value:
return f'{tr("Bootloader")}: {item.value.value}'
return None
def _prev_uki(self, item: MenuItem) -> str | None:
uki_text = f'{tr("Unified kernel images")}'
if item.value:
return f'{uki_text}: {tr("Enabled")}'
else:
return f'{uki_text}: {tr("Disabled")}'
def _prev_removable(self, item: MenuItem) -> str | None:
if item.value:
return tr('Will install to /EFI/BOOT/ (removable location, safe default)')
return tr('Will install to custom location with NVRAM entry')
@override
async def show(self) -> BootloaderConfiguration:
_ = await super().show()
return self._bootloader_conf
async def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | None:
bootloader = await select_bootloader(preset, self._uefi, self._skip_boot)
if bootloader:
# Update UKI option based on bootloader
uki_item = self._menu_item_group.find_by_key('uki')
if not self._uefi or not bootloader.has_uki_support():
uki_item.enabled = False
uki_item.value = False
self._bootloader_conf.uki = False
else:
uki_item.enabled = True
# Update removable option based on bootloader
removable_item = self._menu_item_group.find_by_key('removable')
if not self._uefi or not bootloader.has_removable_support():
removable_item.enabled = False
removable_item.value = False
self._bootloader_conf.removable = False
else:
if not removable_item.enabled:
removable_item.value = True
self._bootloader_conf.removable = True
removable_item.enabled = True
return bootloader
async def _select_uki(self, preset: bool) -> bool:
prompt = tr('Would you like to use unified kernel images?') + '\n'
result = await Confirmation(header=prompt, allow_skip=True, preset=preset).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return result.item() == MenuItem.yes()
case ResultType.Reset:
raise ValueError('Unhandled result type')
async def _select_removable(self, preset: bool) -> bool:
prompt = (
tr('Would you like to install the bootloader to the default removable media search location?')
+ '\n\n'
+ tr('This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:')
+ '\n\n'
+ tr('Firmware that does not properly support NVRAM boot entries like most MSI motherboards,')
+ '\n '
+ tr('most Apple Macs, many laptops...')
+ '\n'
+ tr('USB drives or other portable external media.')
+ '\n'
+ tr('Systems where you want the disk to be bootable on any computer.')
+ '\n\n'
+ tr(
textwrap.dedent(
"""\
If you do not know what this means, LEAVE THIS OPTION ENABLED, as it is the safe default.
It is suggested to disable this if none of the above apply, as it makes installing multiple
EFI bootloaders on the same disk easier, and it will not overwrite whatever bootloader
was previously installed at the default removable media search location, if any.
It may also make the installation more resilient in case of dual-booting with Windows,
as Windows is known to sometimes erase or replace the bootloader installed at the removable
location.
"""
)
)
+ '\n'
)
result = await Confirmation(
header=prompt,
allow_skip=True,
preset=preset,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError('Unhandled result type')
async def select_bootloader(
preset: Bootloader | None,
uefi: bool,
skip_boot: bool = False,
) -> Bootloader | None:
options = []
hidden_options = []
header = tr('Select bootloader to install')
default = Bootloader.get_default(uefi, skip_boot)
if not skip_boot:
hidden_options += [Bootloader.NO_BOOTLOADER]
if not uefi:
options += [Bootloader.Grub, Bootloader.Limine]
header += '\n' + tr('UEFI is not detected and some options are disabled')
else:
options += [b for b in Bootloader if b not in hidden_options]
items = [MenuItem(o.value, value=o) for o in options]
group = MenuItemGroup(items)
group.set_default_by_value(default)
group.set_focus_by_value(preset)
result = await Selection[Bootloader](
group,
header=header,
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
return result.get_value()
case ResultType.Reset:
raise ValueError('Unhandled result type')

View File

@ -0,0 +1,86 @@
from dataclasses import dataclass
from enum import Enum, auto
from pathlib import Path
from archinstall.lib.hardware import SysInfo
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.device import DiskLayoutConfiguration
class BootloaderValidationFailureKind(Enum):
LimineNonFatBoot = auto()
LimineLayout = auto()
BootloaderRequiresUefi = auto()
EfistubNonFatBoot = auto()
@dataclass(frozen=True)
class BootloaderValidationFailure:
kind: BootloaderValidationFailureKind
description: str
def validate_bootloader_layout(
bootloader_config: BootloaderConfiguration | None,
disk_config: DiskLayoutConfiguration | None,
) -> BootloaderValidationFailure | None:
"""Validate bootloader configuration against disk layout.
Returns a failure with a human-readable description if the configuration
would produce an unbootable system, or None if it is valid.
"""
if not (bootloader_config and disk_config):
return None
bootloader = bootloader_config.bootloader
if bootloader == Bootloader.NO_BOOTLOADER:
return None
if bootloader.is_uefi_only() and not SysInfo.has_uefi():
return BootloaderValidationFailure(
kind=BootloaderValidationFailureKind.BootloaderRequiresUefi,
description=f'{bootloader.value} requires a UEFI system.',
)
boot_part = next(
(p for m in disk_config.device_modifications if (p := m.get_boot_partition())),
None,
)
if bootloader == Bootloader.Efistub:
# The UEFI firmware reads the kernel directly from the boot partition,
# which must be FAT.
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
return BootloaderValidationFailure(
kind=BootloaderValidationFailureKind.EfistubNonFatBoot,
description='Efistub does not support booting with a non-FAT boot partition.',
)
if bootloader == Bootloader.Limine:
# Limine reads its config and kernels from the boot partition, which
# must be FAT.
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
return BootloaderValidationFailure(
kind=BootloaderValidationFailureKind.LimineNonFatBoot,
description='Limine does not support booting with a non-FAT boot partition.',
)
# When the ESP is the boot partition but mounted outside /boot and
# UKI is disabled, kernels end up on the root filesystem which
# Limine cannot access.
if not bootloader_config.uki:
efi_part = next(
(p for m in disk_config.device_modifications if (p := m.get_efi_partition())),
None,
)
if efi_part and efi_part == boot_part and efi_part.mountpoint != Path('/boot'):
return BootloaderValidationFailure(
kind=BootloaderValidationFailureKind.LimineLayout,
description=(
f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_part.mountpoint}, '
'enable UKI or add a separate /boot partition to install Limine.'
),
)
return None

384
archinstall/lib/command.py Normal file
View File

@ -0,0 +1,384 @@
import os
import shlex
import stat
import subprocess
import sys
import time
from collections.abc import Iterator
from select import EPOLLHUP, EPOLLIN, epoll
from shutil import which
from types import TracebackType
from typing import Any, Self, override
from archinstall.lib.exceptions import RequirementError, SysCallError
from archinstall.lib.log import debug, error, logger
from archinstall.lib.utils.encoding import clear_vt100_escape_codes
class SysCommandWorker:
def __init__(
self,
cmd: str | list[str],
peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None,
working_directory: str = './',
remove_vt100_escape_codes_from_lines: bool = True,
):
if isinstance(cmd, str):
cmd = shlex.split(cmd)
if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well
cmd[0] = locate_binary(cmd[0])
self.cmd = cmd
self.peek_output = peek_output
# define the standard locale for command outputs. For now the C ascii one. Can be overridden
self.environment_vars = {'LC_ALL': 'C'}
if environment_vars:
self.environment_vars.update(environment_vars)
self.working_directory = working_directory
self.exit_code: int | None = None
self._trace_log = b''
self._trace_log_pos = 0
self.poll_object = epoll()
self.child_fd: int | None = None
self.started = False
self.ended = False
self.remove_vt100_escape_codes_from_lines: bool = remove_vt100_escape_codes_from_lines
def __contains__(self, key: bytes) -> bool:
"""
Contains will also move the current buffert position forward.
This is to avoid re-checking the same data when looking for output.
"""
assert isinstance(key, bytes)
index = self._trace_log.find(key, self._trace_log_pos)
if index >= 0:
self._trace_log_pos += index + len(key)
return True
return False
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:
if self.remove_vt100_escape_codes_from_lines:
line = clear_vt100_escape_codes(line)
yield line + b'\n'
self._trace_log_pos = last_line
@override
def __repr__(self) -> str:
self.make_sure_we_are_executing()
return str(self._trace_log)
@override
def __str__(self) -> str:
try:
return self._trace_log.decode('utf-8')
except UnicodeDecodeError:
return str(self._trace_log)
def __enter__(self) -> Self:
return self
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
if self.child_fd:
try:
os.close(self.child_fd)
except Exception:
pass
if self.peek_output:
# To make sure any peaked output didn't leave us hanging
# on the same line we were on.
sys.stdout.write('\n')
sys.stdout.flush()
if exc_type is not None:
debug(str(exc_value))
if self.exit_code != 0:
raise SysCallError(
f'{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}',
self.exit_code,
worker_log=self._trace_log,
)
def is_alive(self) -> bool:
self.poll()
if self.started and not self.ended:
return True
return False
def write(self, data: bytes, line_ending: bool = True) -> int:
assert isinstance(data, bytes) # TODO: Maybe we can support str as well and encode it
self.make_sure_we_are_executing()
if self.child_fd:
return os.write(self.child_fd, data + (b'\n' if line_ending else b''))
return 0
def make_sure_we_are_executing(self) -> bool:
if not self.started:
return self.execute()
return True
def tell(self) -> int:
self.make_sure_we_are_executing()
return self._trace_log_pos
def seek(self, pos: int) -> None:
self.make_sure_we_are_executing()
# Safety check to ensure 0 < pos < len(tracelog)
self._trace_log_pos = min(max(0, pos), len(self._trace_log))
def peak(self, output: str | bytes) -> bool:
if self.peek_output:
if isinstance(output, bytes):
try:
output = output.decode('UTF-8')
except UnicodeDecodeError:
return False
_cmd_output(output)
sys.stdout.write(output)
sys.stdout.flush()
return True
def poll(self) -> None:
self.make_sure_we_are_executing()
if self.child_fd:
got_output = False
for _fileno, _event in self.poll_object.poll(0.1):
try:
output = os.read(self.child_fd, 8192)
got_output = True
self.peak(output)
self._trace_log += output
except OSError:
self.ended = True
break
if self.ended or (not got_output and not _pid_exists(self.pid)):
self.ended = True
try:
wait_status = os.waitpid(self.pid, 0)[1]
self.exit_code = os.waitstatus_to_exitcode(wait_status)
except ChildProcessError:
try:
wait_status = os.waitpid(self.child_fd, 0)[1]
self.exit_code = os.waitstatus_to_exitcode(wait_status)
except ChildProcessError:
self.exit_code = 1
def execute(self) -> bool:
import pty
if (old_dir := os.getcwd()) != self.working_directory:
os.chdir(str(self.working_directory))
# Note: If for any reason, we get a Python exception between here
# and until os.close(), the traceback will get locked inside
# stdout of the child_fd object. `os.read(self.child_fd, 8192)` is the
# only way to get the traceback without losing it.
self.pid, self.child_fd = pty.fork()
# https://stackoverflow.com/questions/4022600/python-pty-fork-how-does-it-work
if not self.pid:
_cmd_history(self.cmd)
try:
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
except FileNotFoundError:
error(f'{self.cmd[0]} does not exist.')
self.exit_code = 1
return False
else:
# Only parent process moves back to the original working directory
os.chdir(old_dir)
self.started = True
self.poll_object.register(self.child_fd, EPOLLIN | EPOLLHUP)
return True
def decode(self, encoding: str = 'UTF-8') -> str:
return self._trace_log.decode(encoding)
class SysCommand:
def __init__(
self,
cmd: str | list[str],
peek_output: bool | None = False,
environment_vars: dict[str, str] | None = None,
working_directory: str = './',
remove_vt100_escape_codes_from_lines: bool = True,
):
self.cmd = cmd
self.peek_output = peek_output
self.environment_vars = environment_vars
self.working_directory = working_directory
self.remove_vt100_escape_codes_from_lines = remove_vt100_escape_codes_from_lines
self.session: SysCommandWorker | None = None
self.create_session()
def __enter__(self) -> SysCommandWorker | None:
return self.session
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
if exc_type is not None:
error(str(exc_value))
def __iter__(self, *args: list[Any], **kwargs: dict[str, Any]) -> Iterator[bytes]:
if self.session:
yield from self.session
def __getitem__(self, key: slice) -> bytes:
if not self.session:
raise KeyError('SysCommand() does not have an active session.')
elif type(key) is slice:
start = key.start or 0
end = key.stop or len(self.session._trace_log)
return self.session._trace_log[start:end]
else:
raise ValueError("SysCommand() doesn't have key & value pairs, only slices, SysCommand('ls')[:10] as an example.")
@override
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
return self.decode('UTF-8', errors='backslashreplace') or ''
def create_session(self) -> bool:
"""
Initiates a :ref:`SysCommandWorker` session in this class ``.session``.
It then proceeds to poll the process until it ends, after which it also
clears any printed output if ``.peek_output=True``.
"""
if self.session:
return True
with SysCommandWorker(
self.cmd,
peek_output=self.peek_output,
environment_vars=self.environment_vars,
remove_vt100_escape_codes_from_lines=self.remove_vt100_escape_codes_from_lines,
working_directory=self.working_directory,
) as session:
self.session = session
while not self.session.ended:
self.session.poll()
if self.peek_output:
sys.stdout.write('\n')
sys.stdout.flush()
return True
def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', strip: bool = True) -> str:
if not self.session:
raise ValueError('No session available to decode')
val = self.session._trace_log.decode(encoding, errors=errors)
if strip:
return val.strip()
return val
def output(self, remove_cr: bool = True) -> bytes:
if not self.session:
raise ValueError('No session available')
if remove_cr:
return self.session._trace_log.replace(b'\r\n', b'\n')
return self.session._trace_log
@property
def exit_code(self) -> int | None:
if self.session:
return self.session.exit_code
else:
return None
@property
def trace_log(self) -> bytes | None:
if self.session:
return self.session._trace_log
return None
def run(
cmd: list[str],
input_data: bytes | None = None,
) -> subprocess.CompletedProcess[bytes]:
_cmd_history(cmd)
return subprocess.run(
cmd,
input=input_data,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
)
def locate_binary(name: str) -> str:
if path := which(name):
return path
raise RequirementError(f'Binary {name} does not exist.')
def _pid_exists(pid: int) -> bool:
try:
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
except subprocess.CalledProcessError:
return False
def _cmd_history(cmd: list[str]) -> None:
content = f'{time.time()} {cmd}\n'
_append_log('cmd_history.txt', content)
def _cmd_output(output: str) -> None:
_append_log('cmd_output.txt', output)
def _append_log(file: str, content: str) -> None:
path = logger.directory / file
change_perm = not path.exists()
try:
with path.open('a') as f:
f.write(content)
if change_perm:
path.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
except PermissionError, FileNotFoundError:
# If the file does not exist, ignore the error
pass

View File

@ -1,115 +1,134 @@
import os
import json
import stat
import readline
import stat
from pathlib import Path
from typing import Any, TYPE_CHECKING
from typing import Any
from .storage import storage
from .general import JSON, UNSAFE_JSON
from .output import debug, warn
from .utils.util import prompt_dir
from pydantic import TypeAdapter
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, Alignment, ResultType,
PreviewStyle, Orientation, Tui
)
if TYPE_CHECKING:
_: Any
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.util import get_password, prompt_dir
from archinstall.lib.models.network import NetworkConfiguration
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: dict):
def __init__(self, config: ArchConfig):
"""
Configuration output handler to parse the existing configuration data structure and prepare for output on the
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: A dictionary containing configurations (basically archinstall.arguments)
:type config: dict
:param config: Archinstall configuration object
:type config: ArchConfig
"""
self._config = config
self._user_credentials: dict[str, Any] = {}
self._user_config: dict[str, Any] = {}
self._default_save_path = storage.get('LOG_PATH', Path('.'))
self._user_config_file = 'user_configuration.json'
self._user_creds_file = "user_credentials.json"
self._sensitive = ['!users', '!root-password']
self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run']
self._process_config()
self._default_save_path = logger.directory
self._user_config_file = Path('user_configuration.json')
self._user_creds_file = Path('user_credentials.json')
@property
def user_credentials_file(self) -> str:
return self._user_creds_file
@property
def user_configuration_file(self) -> str:
def user_configuration_file(self) -> Path:
return self._user_config_file
def _process_config(self) -> None:
for key, value in self._config.items():
if key in self._sensitive:
self._user_credentials[key] = value
elif key in self._ignore:
pass
else:
self._user_config[key] = value
# special handling for encryption password
if key == 'disk_encryption' and value:
self._user_credentials['encryption_password'] = value.encryption_password
@property
def user_credentials_file(self) -> Path:
return self._user_creds_file
def user_config_to_json(self) -> str:
return json.dumps({
'config_version': storage['__version__'], # Tells us what version was used to generate the config
**self._user_config, # __version__ will be overwritten by old version definition found in config
'version': storage['__version__']
}, indent=4, sort_keys=True, cls=JSON)
config = self._config.safe_config()
def user_credentials_to_json(self) -> str | None:
if self._user_credentials:
return json.dumps(self._user_credentials, indent=4, sort_keys=True, cls=UNSAFE_JSON)
return None
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(' -- Chosen configuration --')
debug(self.user_config_to_json())
def confirm_config(self) -> bool:
header = f'{_("The specified configuration will be applied")}. '
header += str(_('Would you like to continue?')) + '\n'
def as_summary(self) -> str:
"""
Render a concise two-column summary of the current configuration.
with Tui():
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.yes()
group.set_preview_for_all(lambda x: self.user_config_to_json())
Returns an empty string if nothing meaningful to show.
"""
cfg: dict[str, str | list[str] | bool] = {}
result = SelectMenu(
group,
header=header,
alignment=Alignment.CENTER,
columns=2,
orientation=Orientation.HORIZONTAL,
allow_skip=False,
preview_size='auto',
preview_style=PreviewStyle.BOTTOM,
preview_frame=FrameProperties.max(str(_('Configuration')))
).run()
for key, value in self._config.plain_cfg().items():
cfg[key.text()] = value
if result.item() != MenuItem.yes():
return False
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, show_install_warnings: bool = False) -> bool:
header = f'{tr("The specified configuration will be applied")}. '
header += tr('Would you like to continue?') + '\n'
if show_install_warnings:
header += self._render_install_warnings()
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 get_install_warnings(self) -> list[str]:
warnings: list[str] = []
if not isinstance(self._config.network_config, NetworkConfiguration):
warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.'))
return warnings
def _render_install_warnings(self) -> str:
warnings = self.get_install_warnings()
if not warnings:
return ''
return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n'
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'
'Configuration files can not be saved',
)
return dest_path_ok
@ -117,37 +136,51 @@ class ConfigurationOutput:
if self._is_valid_path(dest_path):
target = dest_path / self._user_config_file
target.write_text(self.user_config_to_json())
os.chmod(target, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def save_user_creds(self, dest_path: Path) -> None:
if self._is_valid_path(dest_path):
if user_creds := self.user_credentials_to_json():
target = dest_path / self._user_creds_file
target.write_text(user_creds)
os.chmod(target, 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()
def save(self, dest_path: Path | None = None) -> None:
dest_path = dest_path or self._default_save_path
if password:
data = encrypt(password, data)
if self._is_valid_path(dest_path):
self.save_user_config(dest_path)
self.save_user_creds(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)
def save_config(config: dict[str, Any]) -> None:
async def save_config(config: ArchConfig) -> None:
def preview(item: MenuItem) -> str | None:
match item.value:
case "user_config":
case 'user_config':
serialized = config_output.user_config_to_json()
return f"{config_output.user_configuration_file}\n{serialized}"
case "user_creds":
return f'{config_output.user_configuration_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}"
return str(_("No configuration"))
case "all":
output = [config_output.user_configuration_file]
if config_output.user_credentials_to_json():
output.append(config_output.user_credentials_file)
return f'{config_output.user_credentials_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))
return '\n'.join(output)
return None
@ -155,30 +188,28 @@ def save_config(config: dict[str, Any]) -> None:
items = [
MenuItem(
str(_("Save user configuration (including disk layout)")),
value="user_config",
preview_action=lambda x: preview(x)
tr('Save user configuration (including disk layout)'),
value='user_config',
preview_action=preview,
),
MenuItem(
str(_("Save user credentials")),
value="user_creds",
preview_action=lambda x: preview(x)
tr('Save user credentials'),
value='user_creds',
preview_action=preview,
),
MenuItem(
str(_("Save all")),
value="all",
preview_action=lambda x: preview(x)
)
tr('Save all'),
value='all',
preview_action=preview,
),
]
group = MenuItemGroup(items)
result = SelectMenu(
result = await Selection[str](
group,
allow_skip=True,
preview_frame=FrameProperties.max(str(_('Configuration'))),
preview_size='auto',
preview_style=PreviewStyle.RIGHT
).run()
preview_location='right',
).show()
match result.type_:
case ResultType.Skip:
@ -188,43 +219,57 @@ def save_config(config: dict[str, Any]) -> None:
case _:
raise ValueError('Unhandled return type')
readline.set_completer_delims("\t\n=")
readline.parse_and_bind("tab: complete")
readline.set_completer_delims('\t\n=')
readline.parse_and_bind('tab: complete')
dest_path = prompt_dir(
str(_('Directory')),
str(_('Enter a directory for the configuration(s) to be saved (tab completion enabled)')) + '\n',
allow_skip=True
dest_path = await prompt_dir(
tr('Enter a directory for the configuration(s) to be saved') + '\n',
allow_skip=True,
)
if not dest_path:
return
header = str(_("Do you want to save the configuration file(s) to {}?")).format(dest_path)
header = tr('Do you want to save the configuration file(s) to {}?').format(dest_path)
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.yes()
result = SelectMenu(
group,
save_result = await Confirmation(
header=header,
allow_skip=False,
alignment=Alignment.CENTER,
columns=2,
orientation=Orientation.HORIZONTAL
).run()
preset=True,
).show()
match result.type_:
match save_result.type_:
case ResultType.Selection:
if result.item() == MenuItem.no():
if not save_result.get_value():
return
case _:
return
debug("Saving configuration files to {}".format(dest_path.absolute()))
debug(f'Saving configuration files to {dest_path.absolute()}')
header = tr('Do you want to encrypt the user_credentials.json file?')
enc_result = await Confirmation(
header=header,
allow_skip=False,
preset=False,
).show()
enc_password: str | None = None
if enc_result.type_ == ResultType.Selection:
if enc_result.get_value():
password = await get_password(
header=tr('Credentials file encryption password'),
allow_skip=True,
)
if password:
enc_password = password.plaintext
match save_option:
case "user_config":
case 'user_config':
config_output.save_user_config(dest_path)
case "user_creds":
config_output.save_user_creds(dest_path)
case "all":
config_output.save(dest_path)
case 'user_creds':
config_output.save_user_creds(dest_path, password=enc_password)
case 'all':
config_output.save(dest_path, creds=True, password=enc_password)

125
archinstall/lib/crypt.py Normal file
View File

@ -0,0 +1,125 @@
import base64
import ctypes
import os
from pathlib import Path
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
from archinstall.lib.log import debug
libcrypt = ctypes.CDLL('libcrypt.so')
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
libcrypt.crypt.restype = ctypes.c_char_p
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
LOGIN_DEFS = Path('/etc/login.defs')
def _search_login_defs(key: str) -> str | None:
defs = LOGIN_DEFS.read_text()
for line in defs.split('\n'):
line = line.strip()
if line.startswith('#'):
continue
if line.startswith(key):
value = line.split(' ')[1]
return value
return None
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
if isinstance(prefix, str):
prefix = prefix.encode('utf-8')
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
if setting is None:
raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
return setting
def crypt_yescrypt(plaintext: str) -> str:
"""
By default chpasswd in Arch uses PAM to hash the password with crypt_yescrypt
the PAM code https://github.com/linux-pam/linux-pam/blob/master/modules/pam_unix/support.c
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
If no value was specified (or commented out) a default of 5 is chosen
"""
value = _search_login_defs('YESCRYPT_COST_FACTOR')
if value is not None:
rounds = int(value)
if rounds < 3:
rounds = 3
elif rounds > 11:
rounds = 11
else:
rounds = 5
debug(f'Creating yescrypt hash with rounds {rounds}')
enc_plaintext = plaintext.encode('utf-8')
salt = crypt_gen_salt('$y$', rounds)
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
if crypt_hash is None:
raise ValueError('crypt() returned NULL')
return crypt_hash.decode('utf-8')
def _get_fernet(salt: bytes, password: str) -> Fernet:
# https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#argon2id
kdf = Argon2id(
salt=salt,
length=32,
iterations=1,
lanes=4,
memory_cost=64 * 1024,
ad=None,
secret=None,
)
key = base64.urlsafe_b64encode(
kdf.derive(
password.encode('utf-8'),
),
)
return Fernet(key)
def encrypt(password: str, data: str) -> str:
salt = os.urandom(16)
f = _get_fernet(salt, password)
token = f.encrypt(data.encode('utf-8'))
encoded_token = base64.urlsafe_b64encode(token).decode('utf-8')
encoded_salt = base64.urlsafe_b64encode(salt).decode('utf-8')
return f'$argon2id${encoded_salt}${encoded_token}'
def decrypt(data: str, password: str) -> str:
_, algo, encoded_salt, encoded_token = data.split('$')
salt = base64.urlsafe_b64decode(encoded_salt)
token = base64.urlsafe_b64decode(encoded_token)
if algo != 'argon2id':
raise ValueError(f'Unsupported algorithm {algo!r}')
f = _get_fernet(salt, password)
try:
decrypted = f.decrypt(token)
except InvalidToken:
raise ValueError('Invalid password')
return decrypted.decode('utf-8')

View File

@ -1,48 +0,0 @@
from .device_handler import device_handler, disk_layouts
from .fido import Fido2
from .filesystem import FilesystemHandler
from .subvolume_menu import SubvolumeMenu
from .partitioning_menu import (
manual_partitioning,
PartitioningList
)
from .device_model import (
_DeviceInfo,
BDevice,
DiskLayoutType,
DiskLayoutConfiguration,
LvmLayoutType,
LvmConfiguration,
LvmVolumeGroup,
LvmVolume,
LvmVolumeStatus,
PartitionTable,
Unit,
Size,
SectorSize,
SubvolumeModification,
DeviceGeometry,
PartitionType,
PartitionFlag,
FilesystemType,
ModificationStatus,
PartitionModification,
DeviceModification,
EncryptionType,
DiskEncryption,
Fido2Device,
LsblkInfo,
CleanType,
get_lsblk_info,
get_all_lsblk_info,
get_lsblk_by_mountpoint,
)
from .encryption_menu import (
select_encryption_type,
select_encrypted_password,
select_hsm,
select_partitions_to_encrypt,
DiskEncryptionMenu,
)
from .disk_menu import DiskLayoutConfigurationMenu

View File

@ -1,59 +1,68 @@
from __future__ import annotations
import json
import os
import logging
import time
import uuid
import os
from pathlib import Path
from typing import List, Dict, Any, Optional, TYPE_CHECKING, Literal, Iterable
from parted import (
Disk, Geometry, FileSystem,
PartitionException, DiskException, IOException,
getDevice, getAllDevices, newDisk, freshDisk, Partition, Device
from parted import Device, Disk, DiskException, FileSystem, Geometry, IOException, Partition, PartitionException, freshDisk, getAllDevices, getDevice, newDisk
from archinstall.lib.command import SysCommand
from archinstall.lib.disk.luks import Luks2, unlock_luks2_dev
from archinstall.lib.disk.utils import (
find_lsblk_info,
get_all_lsblk_info,
get_lsblk_info,
mount,
udev_sync,
umount,
)
from .device_model import (
DeviceModification, PartitionModification,
BDevice, _DeviceInfo, _PartitionInfo,
FilesystemType, Unit, PartitionTable,
ModificationStatus, get_lsblk_info, find_lsblk_info, LsblkInfo,
_BtrfsSubvolumeInfo, get_all_lsblk_info, DiskEncryption, LvmVolumeGroup, LvmVolume, Size, LvmGroupInfo,
SectorSize, LvmVolumeInfo, LvmPVInfo, SubvolumeModification, BtrfsMountOption
from archinstall.lib.exceptions import DiskError, SysCallError, UnknownFilesystemFormat
from archinstall.lib.log import debug, error, info, log
from archinstall.lib.models.device import (
DEFAULT_ITER_TIME,
BDevice,
BtrfsMountOption,
DeviceModification,
DiskEncryption,
FilesystemType,
LsblkInfo,
ModificationStatus,
PartitionFlag,
PartitionGUID,
PartitionModification,
PartitionTable,
SubvolumeModification,
Unit,
_BtrfsSubvolumeInfo,
_DeviceInfo,
_PartitionInfo,
)
from ..exceptions import DiskError, UnknownFilesystemFormat
from ..general import SysCommand, SysCallError, JSON, SysCommandWorker
from ..luks import Luks2
from ..output import debug, error, info, warn, log
from ..utils.util import is_subpath
if TYPE_CHECKING:
_: Any
from archinstall.lib.models.users import Password
from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT
class DeviceHandler:
_TMP_BTRFS_MOUNT = Path('/mnt/arch_btrfs')
def __init__(self) -> None:
self._devices: Dict[Path, BDevice] = {}
self._devices: dict[Path, BDevice] = {}
self._partition_table = PartitionTable.default()
self.load_devices()
@property
def devices(self) -> List[BDevice]:
def devices(self) -> list[BDevice]:
return list(self._devices.values())
@property
def partition_table(self) -> PartitionTable:
return self._partition_table
def load_devices(self) -> None:
block_devices = {}
self.udev_sync()
udev_sync()
all_lsblk_info = get_all_lsblk_info()
devices = getAllDevices()
devices.extend(self.get_loop_devices())
archiso_mountpoint = Path('/run/archiso/airootfs')
for device in devices:
dev_lsblk_info = find_lsblk_info(device.path, all_lsblk_info)
@ -65,14 +74,14 @@ class DeviceHandler:
continue
# exclude archiso loop device
if dev_lsblk_info.mountpoint == archiso_mountpoint:
if dev_lsblk_info.mountpoint == ARCHISO_MOUNTPOINT:
continue
try:
if dev_lsblk_info.pttype:
disk = newDisk(device)
else:
disk = freshDisk(device, PartitionTable.GPT.value)
disk = freshDisk(device, self.partition_table.value)
except DiskException as err:
debug(f'Unable to get disk from {device.path}: {err}')
continue
@ -90,19 +99,16 @@ class DeviceHandler:
fs_type = self._determine_fs_type(partition, lsblk_info)
subvol_infos = []
if fs_type == FilesystemType.Btrfs:
if fs_type == FilesystemType.BTRFS:
subvol_infos = self.get_btrfs_info(partition.path, lsblk_info)
partition_infos.append(
_PartitionInfo.from_partition(
partition,
lsblk_info,
fs_type,
lsblk_info.partn,
lsblk_info.partuuid,
lsblk_info.uuid,
lsblk_info.mountpoints,
subvol_infos
)
subvol_infos,
),
)
block_device = BDevice(disk, device_info, partition_infos)
@ -137,10 +143,12 @@ class DeviceHandler:
def _determine_fs_type(
self,
partition: Partition,
lsblk_info: Optional[LsblkInfo] = None
) -> Optional[FilesystemType]:
lsblk_info: LsblkInfo | None = None,
) -> FilesystemType | None:
try:
if partition.fileSystem:
if partition.fileSystem.type == FilesystemType.LINUX_SWAP.parted_value:
return FilesystemType.LINUX_SWAP
return FilesystemType(partition.fileSystem.type)
elif lsblk_info is not None:
return FilesystemType(lsblk_info.fstype) if lsblk_info.fstype else None
@ -150,68 +158,51 @@ class DeviceHandler:
return None
def get_device(self, path: Path) -> Optional[BDevice]:
def get_device(self, path: Path) -> BDevice | None:
return self._devices.get(path, None)
def get_device_by_partition_path(self, partition_path: Path) -> Optional[BDevice]:
def get_device_by_partition_path(self, partition_path: Path) -> BDevice | None:
partition = self.find_partition(partition_path)
if partition:
device: Device = partition.disk.device
return self.get_device(Path(device.path))
return None
def find_partition(self, path: Path) -> Optional[_PartitionInfo]:
def find_partition(self, path: Path) -> _PartitionInfo | None:
for device in self._devices.values():
part = next(filter(lambda x: str(x.path) == str(path), device.partition_infos), None)
if part is not None:
return part
return None
def get_parent_device_path(self, dev_path: Path) -> Path:
lsblk = get_lsblk_info(dev_path)
return Path(f'/dev/{lsblk.pkname}')
def get_unique_path_for_device(self, dev_path: Path) -> Optional[Path]:
paths = Path('/dev/disk/by-id').glob('*')
linked_targets = {p.resolve(): p for p in paths}
linked_wwn_targets = {
p: linked_targets[p] for p in linked_targets
if p.name.startswith('wwn-') or p.name.startswith('nvme-eui.')
}
if dev_path in linked_wwn_targets:
return linked_wwn_targets[dev_path]
if dev_path in linked_targets:
return linked_targets[dev_path]
return None
def get_uuid_for_path(self, path: Path) -> Optional[str]:
def get_uuid_for_path(self, path: Path) -> str | None:
partition = self.find_partition(path)
return partition.partuuid if partition else None
def get_btrfs_info(
self,
dev_path: Path,
lsblk_info: Optional[LsblkInfo] = None
) -> List[_BtrfsSubvolumeInfo]:
lsblk_info: LsblkInfo | None = None,
) -> list[_BtrfsSubvolumeInfo]:
if not lsblk_info:
lsblk_info = get_lsblk_info(dev_path)
subvol_infos: List[_BtrfsSubvolumeInfo] = []
subvol_infos: list[_BtrfsSubvolumeInfo] = []
if not lsblk_info.mountpoint:
self.mount(dev_path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
mount(dev_path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
mountpoint = self._TMP_BTRFS_MOUNT
else:
# when multiple subvolumes are mounted then the lsblk output may look like
# "mountpoint": "/mnt/archinstall/.snapshots"
# "mountpoints": ["/mnt/archinstall/.snapshots", "/mnt/archinstall/home", ..]
# "mountpoint": "/mnt/archinstall/var/log"
# "mountpoints": ["/mnt/archinstall/var/log", "/mnt/archinstall/home", ..]
# so we'll determine the minimum common path and assume that's the root
path_strings = [str(m) for m in lsblk_info.mountpoints]
common_prefix = os.path.commonprefix(path_strings)
mountpoint = Path(common_prefix)
try:
common_path = os.path.commonpath(lsblk_info.mountpoints)
except ValueError:
return subvol_infos
mountpoint = Path(common_path)
try:
result = SysCommand(f'btrfs subvolume list {mountpoint}').decode()
@ -219,20 +210,23 @@ class DeviceHandler:
debug(f'Failed to read btrfs subvolume information: {err}')
return subvol_infos
try:
# ID 256 gen 16 top level 5 path @
for line in result.splitlines():
# expected output format:
# ID 257 gen 8 top level 5 path @home
name = Path(line.split(' ')[-1])
sub_vol_mountpoint = lsblk_info.btrfs_subvol_info.get(name, None)
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
except json.decoder.JSONDecodeError as err:
error(f"Could not decode lsblk JSON: {result}")
raise err
# It is assumed that lsblk will contain the fields as
# "mountpoints": ["/mnt/archinstall/log", "/mnt/archinstall/home", "/mnt/archinstall", ...]
# "fsroots": ["/@log", "/@home", "/@"...]
# we'll thereby map the fsroot, which are the mounted filesystem roots
# to the corresponding mountpoints
btrfs_subvol_info = dict(zip(lsblk_info.fsroots, lsblk_info.mountpoints))
# ID 256 gen 16 top level 5 path @
for line in result.splitlines():
# expected output format:
# ID 257 gen 8 top level 5 path @home
name = Path(line.split(' ')[-1])
sub_vol_mountpoint = btrfs_subvol_info.get('/' / name, None)
subvol_infos.append(_BtrfsSubvolumeInfo(name, sub_vol_mountpoint))
if not lsblk_info.mountpoint:
self.umount(dev_path)
umount(dev_path)
return subvol_infos
@ -240,31 +234,35 @@ class DeviceHandler:
self,
fs_type: FilesystemType,
path: Path,
additional_parted_options: List[str] = []
additional_parted_options: list[str] = [],
) -> None:
mkfs_type = fs_type.value
command = None
options = []
match fs_type:
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
case FilesystemType.BTRFS | FilesystemType.XFS:
# Force overwrite
options.append('-f')
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
case FilesystemType.F2FS:
options.append('-f')
options.extend(('-O', 'extra_attr'))
case FilesystemType.EXT2 | FilesystemType.EXT3 | FilesystemType.EXT4:
# Force create
options.append('-F')
case FilesystemType.Fat16 | FilesystemType.Fat32:
case _ if fs_type.is_fat():
mkfs_type = 'fat'
# Set FAT size
options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
case FilesystemType.Ntfs:
# Skip zeroing and bad sector check
options.append('--fast')
case FilesystemType.Reiserfs:
pass
case FilesystemType.LINUX_SWAP:
command = 'mkswap'
case _:
raise UnknownFilesystemFormat(f'Filetype "{fs_type.value}" is not supported')
cmd = [f'mkfs.{mkfs_type}', *options, *additional_parted_options, str(path)]
if not command:
command = f'mkfs.{mkfs_type}'
cmd = [command, *options, *additional_parted_options, str(path)]
debug('Formatting filesystem:', ' '.join(cmd))
@ -278,17 +276,20 @@ class DeviceHandler:
def encrypt(
self,
dev_path: Path,
mapper_name: Optional[str],
enc_password: str,
lock_after_create: bool = True
mapper_name: str | None,
enc_password: Password | None,
lock_after_create: bool = True,
iter_time: int = DEFAULT_ITER_TIME,
) -> Luks2:
luks_handler = Luks2(
dev_path,
mapper_name=mapper_name,
password=enc_password
password=enc_password,
)
key_file = luks_handler.encrypt()
key_file = luks_handler.encrypt(iter_time=iter_time)
udev_sync()
luks_handler.unlock(key_file=key_file)
@ -304,17 +305,22 @@ class DeviceHandler:
def format_encrypted(
self,
dev_path: Path,
mapper_name: Optional[str],
mapper_name: str | None,
fs_type: FilesystemType,
enc_conf: DiskEncryption
enc_conf: DiskEncryption,
) -> None:
if not enc_conf.encryption_password:
raise ValueError('No encryption password provided')
luks_handler = Luks2(
dev_path,
mapper_name=mapper_name,
password=enc_conf.encryption_password
password=enc_conf.encryption_password,
)
key_file = luks_handler.encrypt()
key_file = luks_handler.encrypt(iter_time=enc_conf.iter_time)
udev_sync()
luks_handler.unlock(key_file=key_file)
@ -327,156 +333,16 @@ class DeviceHandler:
info(f'luks2 locking device: {dev_path}')
luks_handler.lock()
def _lvm_info(
self,
cmd: str,
info_type: Literal['lv', 'vg', 'pvseg']
) -> Optional[Any]:
raw_info = SysCommand(cmd).decode().split('\n')
# for whatever reason the output sometimes contains
# "File descriptor X leaked leaked on vgs invocation
data = '\n'.join([raw for raw in raw_info if 'File descriptor' not in raw])
debug(f'LVM info: {data}')
reports = json.loads(data)
for report in reports['report']:
if len(report[info_type]) != 1:
raise ValueError('Report does not contain any entry')
entry = report[info_type][0]
match info_type:
case 'pvseg':
return LvmPVInfo(
pv_name=Path(entry['pv_name']),
lv_name=entry['lv_name'],
vg_name=entry['vg_name'],
)
case 'lv':
return LvmVolumeInfo(
lv_name=entry['lv_name'],
vg_name=entry['vg_name'],
lv_size=Size(int(entry['lv_size'][:-1]), Unit.B, SectorSize.default())
)
case 'vg':
return LvmGroupInfo(
vg_uuid=entry['vg_uuid'],
vg_size=Size(int(entry['vg_size'][:-1]), Unit.B, SectorSize.default())
)
return None
def _lvm_info_with_retry(self, cmd: str, info_type: Literal['lv', 'vg', 'pvseg']) -> Optional[Any]:
while True:
try:
return self._lvm_info(cmd, info_type)
except ValueError:
time.sleep(3)
def lvm_vol_info(self, lv_name: str) -> Optional[LvmVolumeInfo]:
cmd = (
'lvs --reportformat json '
'--unit B '
f'-S lv_name={lv_name}'
)
return self._lvm_info_with_retry(cmd, 'lv')
def lvm_group_info(self, vg_name: str) -> Optional[LvmGroupInfo]:
cmd = (
'vgs --reportformat json '
'--unit B '
'-o vg_name,vg_uuid,vg_size '
f'-S vg_name={vg_name}'
)
return self._lvm_info_with_retry(cmd, 'vg')
def lvm_pvseg_info(self, vg_name: str, lv_name: str) -> Optional[LvmPVInfo]:
cmd = (
'pvs '
'--segments -o+lv_name,vg_name '
f'-S vg_name={vg_name},lv_name={lv_name} '
'--reportformat json '
)
return self._lvm_info_with_retry(cmd, 'pvseg')
def lvm_vol_change(self, vol: LvmVolume, activate: bool) -> None:
active_flag = 'y' if activate else 'n'
cmd = f'lvchange -a {active_flag} {vol.safe_dev_path}'
debug(f'lvchange volume: {cmd}')
SysCommand(cmd)
def lvm_export_vg(self, vg: LvmVolumeGroup) -> None:
cmd = f'vgexport {vg.name}'
debug(f'vgexport: {cmd}')
SysCommand(cmd)
def lvm_import_vg(self, vg: LvmVolumeGroup) -> None:
cmd = f'vgimport {vg.name}'
debug(f'vgimport: {cmd}')
SysCommand(cmd)
def lvm_vol_reduce(self, vol_path: Path, amount: Size) -> None:
val = amount.format_size(Unit.B, include_unit=False)
cmd = f'lvreduce -L -{val}B {vol_path}'
debug(f'Reducing LVM volume size: {cmd}')
SysCommand(cmd)
def lvm_pv_create(self, pvs: Iterable[Path]) -> None:
cmd = 'pvcreate ' + ' '.join([str(pv) for pv in pvs])
debug(f'Creating LVM PVS: {cmd}')
worker = SysCommandWorker(cmd)
worker.poll()
worker.write(b'y\n', line_ending=False)
def lvm_vg_create(self, pvs: Iterable[Path], vg_name: str) -> None:
pvs_str = ' '.join([str(pv) for pv in pvs])
cmd = f'vgcreate --yes {vg_name} {pvs_str}'
debug(f'Creating LVM group: {cmd}')
worker = SysCommandWorker(cmd)
worker.poll()
worker.write(b'y\n', line_ending=False)
def lvm_vol_create(self, vg_name: str, volume: LvmVolume, offset: Optional[Size] = None) -> None:
if offset is not None:
length = volume.length - offset
else:
length = volume.length
length_str = length.format_size(Unit.B, include_unit=False)
cmd = f'lvcreate --yes -L {length_str}B {vg_name} -n {volume.name}'
debug(f'Creating volume: {cmd}')
worker = SysCommandWorker(cmd)
worker.poll()
worker.write(b'y\n', line_ending=False)
volume.vg_name = vg_name
volume.dev_path = Path(f'/dev/{vg_name}/{volume.name}')
def _setup_partition(
self,
part_mod: PartitionModification,
block_device: BDevice,
disk: Disk,
requires_delete: bool
requires_delete: bool,
) -> None:
# when we require a delete and the partition to be (re)created
# already exists then we have to delete it first
if requires_delete and part_mod.status in [ModificationStatus.Modify, ModificationStatus.Delete]:
if requires_delete and part_mod.status in [ModificationStatus.MODIFY, ModificationStatus.DELETE]:
info(f'Delete existing partition: {part_mod.safe_dev_path}')
part_info = self.find_partition(part_mod.safe_dev_path)
@ -485,39 +351,40 @@ class DeviceHandler:
disk.deletePartition(part_info.partition)
if part_mod.status == ModificationStatus.Delete:
if part_mod.status == ModificationStatus.DELETE:
return
start_sector = part_mod.start.convert(
Unit.sectors,
block_device.device_info.sector_size
block_device.device_info.sector_size,
)
length_sector = part_mod.length.convert(
Unit.sectors,
block_device.device_info.sector_size
block_device.device_info.sector_size,
)
geometry = Geometry(
device=block_device.disk.device,
start=start_sector.value,
length=length_sector.value
length=length_sector.value,
)
filesystem = FileSystem(type=part_mod.safe_fs_type.value, geometry=geometry)
fs_value = part_mod.safe_fs_type.parted_value
filesystem = FileSystem(type=fs_value, geometry=geometry)
partition = Partition(
disk=disk,
type=part_mod.type.get_partition_code(),
fs=filesystem,
geometry=geometry
geometry=geometry,
)
for flag in part_mod.flags:
partition.setFlag(flag.value)
partition.setFlag(flag.flag_id)
debug(f'\tType: {part_mod.type.value}')
debug(f'\tFilesystem: {part_mod.safe_fs_type.value}')
debug(f'\tFilesystem: {fs_value}')
debug(f'\tGeometry: {start_sector.value} start sector, {length_sector.value} length')
try:
@ -525,9 +392,11 @@ class DeviceHandler:
except PartitionException as ex:
raise DiskError(f'Unable to add partition, most likely due to overlapping sectors: {ex}') from ex
if disk.type == PartitionTable.GPT.value and part_mod.is_root():
linux_root_x86_64 = "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709"
partition.type_uuid = uuid.UUID(linux_root_x86_64).bytes
if disk.type == PartitionTable.GPT.value:
if part_mod.is_root():
partition.type_uuid = PartitionGUID.LINUX_ROOT_X86_64.bytes
elif PartitionFlag.LINUX_HOME not in part_mod.flags and part_mod.is_home():
partition.setFlag(PartitionFlag.LINUX_HOME.flag_id)
# the partition has a path now that it has been added
part_mod.dev_path = Path(partition.path)
@ -547,26 +416,26 @@ class DeviceHandler:
debug(f'Unable to determine new uuid: {path}\n{lsblk_info}')
raise DiskError(f'Unable to determine new uuid: {path}')
debug(f'partition information found: {lsblk_info.json()}')
debug(f'partition information found: {lsblk_info.model_dump_json()}')
return lsblk_info
def create_lvm_btrfs_subvolumes(
self,
path: Path,
btrfs_subvols: List[SubvolumeModification],
mount_options: List[str]
btrfs_subvols: list[SubvolumeModification],
mount_options: list[str],
) -> None:
info(f'Creating subvolumes: {path}')
self.mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
mount(path, self._TMP_BTRFS_MOUNT, create_target_mountpoint=True)
for sub_vol in btrfs_subvols:
for sub_vol in sorted(btrfs_subvols, key=lambda x: x.name):
debug(f'Creating subvolume: {sub_vol.name}')
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
SysCommand(f"btrfs subvolume create {subvol_path}")
SysCommand(f'btrfs subvolume create -p {subvol_path}')
if BtrfsMountOption.nodatacow.value in mount_options:
try:
@ -580,12 +449,12 @@ class DeviceHandler:
except SysCallError as err:
raise DiskError(f'Could not set compress attribute at {subvol_path}: {err}')
self.umount(path)
umount(path)
def create_btrfs_volumes(
self,
part_mod: PartitionModification,
enc_conf: Optional['DiskEncryption'] = None
enc_conf: DiskEncryption | None = None,
) -> None:
info(f'Creating subvolumes: {part_mod.safe_dev_path}')
@ -594,10 +463,10 @@ class DeviceHandler:
if not part_mod.mapper_name:
raise ValueError('No device path specified for modification')
luks_handler = self.unlock_luks2_dev(
luks_handler = unlock_luks2_dev(
part_mod.safe_dev_path,
part_mod.mapper_name,
enc_conf.encryption_password
enc_conf.encryption_password,
)
if not luks_handler.mapper_dev:
@ -608,36 +477,25 @@ class DeviceHandler:
luks_handler = None
dev_path = part_mod.safe_dev_path
self.mount(
mount(
dev_path,
self._TMP_BTRFS_MOUNT,
create_target_mountpoint=True,
options=part_mod.mount_options
options=part_mod.mount_options,
)
for sub_vol in part_mod.btrfs_subvols:
for sub_vol in sorted(part_mod.btrfs_subvols, key=lambda x: x.name):
debug(f'Creating subvolume: {sub_vol.name}')
subvol_path = self._TMP_BTRFS_MOUNT / sub_vol.name
SysCommand(f"btrfs subvolume create {subvol_path}")
SysCommand(f'btrfs subvolume create -p {subvol_path}')
self.umount(dev_path)
umount(dev_path)
if luks_handler is not None and luks_handler.mapper_dev is not None:
luks_handler.lock()
def unlock_luks2_dev(self, dev_path: Path, mapper_name: str, enc_password: str) -> Luks2:
luks_handler = Luks2(dev_path, mapper_name=mapper_name, password=enc_password)
if not luks_handler.is_unlocked():
luks_handler.unlock()
if not luks_handler.is_unlocked():
raise DiskError(f'Failed to unlock luks2 device: {dev_path}')
return luks_handler
def umount_all_existing(self, device_path: Path) -> None:
debug(f'Unmounting all existing partitions: {device_path}')
@ -647,31 +505,28 @@ class DeviceHandler:
debug(f'Unmounting: {partition.path}')
# un-mount for existing encrypted partitions
if partition.fs_type == FilesystemType.Crypto_luks:
if partition.fs_type == FilesystemType.CRYPTO_LUKS:
Luks2(partition.path).lock()
else:
self.umount(partition.path, recursive=True)
umount(partition.path, recursive=True)
def partition(
self,
modification: DeviceModification,
partition_table: Optional[PartitionTable] = None
partition_table: PartitionTable | None = None,
) -> None:
"""
Create a partition table on the block device and create all partitions.
"""
if modification.wipe:
if partition_table is None:
raise ValueError('Modification is marked as wipe but no partitioning table was provided')
if partition_table.MBR and len(modification.partitions) > 3:
raise DiskError('Too many partitions on disk, MBR disks can only have 3 primary partitions')
partition_table = partition_table or self.partition_table
# WARNING: the entire device will be wiped and all data lost
if modification.wipe:
if partition_table.is_mbr() and len(modification.partitions) > 3:
raise DiskError('Too many partitions on disk, MBR disks can only have 3 primary partitions')
self.wipe_dev(modification.device)
part_table = partition_table.value if partition_table else None
disk = freshDisk(modification.device.disk.device, part_table)
disk = freshDisk(modification.device.disk.device, partition_table.value)
else:
info(f'Use existing device: {modification.device_path}')
disk = modification.device.disk
@ -689,67 +544,24 @@ class DeviceHandler:
disk.commit()
def mount(
self,
dev_path: Path,
target_mountpoint: Path,
mount_fs: Optional[str] = None,
create_target_mountpoint: bool = True,
options: List[str] = []
) -> None:
if create_target_mountpoint and not target_mountpoint.exists():
target_mountpoint.mkdir(parents=True, exist_ok=True)
# Wipe filesystem/LVM signatures from newly created partitions
# to prevent "signature detected" errors
for part_mod in filtered_part:
if part_mod.dev_path:
debug(f'Wiping signatures from: {part_mod.dev_path}')
SysCommand(f'wipefs --all {part_mod.dev_path}')
if not target_mountpoint.exists():
raise ValueError('Target mountpoint does not exist')
# Sync with udev after wiping signatures
if filtered_part:
udev_sync()
lsblk_info = get_lsblk_info(dev_path)
if target_mountpoint in lsblk_info.mountpoints:
info(f'Device already mounted at {target_mountpoint}')
return
cmd = ['mount']
if len(options):
cmd.extend(('-o', ','.join(options)))
if mount_fs:
cmd.extend(('-t', mount_fs))
cmd.extend((str(dev_path), str(target_mountpoint)))
command = ' '.join(cmd)
debug(f'Mounting {dev_path}: {command}')
try:
SysCommand(command)
except SysCallError as err:
raise DiskError(f'Could not mount {dev_path}: {command}\n{err.message}')
def umount(self, mountpoint: Path, recursive: bool = False) -> None:
lsblk_info = get_lsblk_info(mountpoint)
if not lsblk_info.mountpoints:
return
debug(f'Partition {mountpoint} is currently mounted at: {[str(m) for m in lsblk_info.mountpoints]}')
cmd = ['umount']
if recursive:
cmd.append('-R')
for path in lsblk_info.mountpoints:
debug(f'Unmounting mountpoint: {path}')
SysCommand(cmd + [str(path)])
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> List[DeviceModification]:
part_mods: Dict[Path, List[PartitionModification]] = {}
def detect_pre_mounted_mods(self, base_mountpoint: Path) -> list[DeviceModification]:
part_mods: dict[Path, list[PartitionModification]] = {}
for device in self.devices:
for part_info in device.partition_infos:
for mountpoint in part_info.mountpoints:
if is_subpath(mountpoint, base_mountpoint):
if mountpoint.is_relative_to(base_mountpoint):
path = Path(part_info.disk.device.path)
part_mods.setdefault(path, [])
part_mod = PartitionModification.from_existing_partition(part_info)
@ -762,14 +574,14 @@ class DeviceHandler:
part_mods[path].append(part_mod)
break
device_mods: List[DeviceModification] = []
device_mods: list[DeviceModification] = []
for device_path, mods in part_mods.items():
device_mod = DeviceModification(self._devices[device_path], False, mods)
device_mods.append(device_mod)
return device_mods
def partprobe(self, path: Optional[Path] = None) -> None:
def partprobe(self, path: Path | None = None) -> None:
if path is not None:
command = f'partprobe {path}'
else:
@ -780,15 +592,15 @@ class DeviceHandler:
SysCommand(command)
except SysCallError as err:
if 'have been written, but we have been unable to inform the kernel of the change' in str(err):
log(f"Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}", fg="gray", level=logging.INFO)
log(f'Partprobe was not able to inform the kernel of the new disk state (ignoring error): {err}', fg='gray', level=logging.INFO)
else:
error(f'"{command}" failed to run (continuing anyway): {err}')
def _wipe(self, dev_path: Path) -> None:
"""
Wipe a device (partition or otherwise) of meta-data, be it file system, LVM, etc.
@param dev_path: Device path of the partition to be wiped.
@type dev_path: str
@param dev_path: Device path of the partition to be wiped.
@type dev_path: str
"""
with open(dev_path, 'wb') as p:
p.write(bytearray(1024))
@ -800,6 +612,7 @@ class DeviceHandler:
auto-discovery tools don't recognize anything here.
"""
info(f'Wiping partitions and metadata: {block_device.device_info.path}')
for partition in block_device.partition_infos:
luks = Luks2(partition.path)
if luks.isLuks():
@ -809,24 +622,5 @@ class DeviceHandler:
self._wipe(block_device.device_info.path)
@staticmethod
def udev_sync() -> None:
try:
SysCommand('udevadm settle')
except SysCallError as err:
debug(f'Failed to synchronize with udev: {err}')
device_handler = DeviceHandler()
def disk_layouts() -> str:
try:
lsblk_info = get_all_lsblk_info()
return json.dumps(lsblk_info, indent=4, sort_keys=True, cls=JSON)
except SysCallError as err:
warn(f"Could not return disk layouts: {err}")
return ''
except json.decoder.JSONDecodeError as err:
warn(f"Could not return disk layouts: {err}")
return ''

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +1,130 @@
from typing import Any, TYPE_CHECKING
from dataclasses import dataclass
from pathlib import Path
from typing import override
from . import DiskLayoutConfiguration, DiskLayoutType
from .device_model import LvmConfiguration
from ..disk import (
DeviceModification
from archinstall.lib.disk.device_handler import device_handler
from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu
from archinstall.lib.disk.partitioning_menu import manual_partitioning
from archinstall.lib.log import debug
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Confirmation, Notify, Selection, Table
from archinstall.lib.menu.util import prompt_dir
from archinstall.lib.models.device import (
DEFAULT_ITER_TIME,
BDevice,
BtrfsMountOption,
BtrfsOptions,
DeviceModification,
DiskEncryption,
DiskLayoutConfiguration,
DiskLayoutType,
EncryptionType,
FilesystemType,
LvmConfiguration,
LvmLayoutType,
LvmVolume,
LvmVolumeGroup,
ModificationStatus,
PartitionFlag,
PartitionModification,
PartitionType,
SectorSize,
Size,
SnapshotConfig,
SnapshotType,
SubvolumeModification,
Unit,
_DeviceInfo,
)
from ..interactions import select_disk_config
from ..interactions.disk_conf import select_lvm_config
from ..output import FormattedOutput
from ..menu import AbstractSubMenu
from archinstall.tui import (
MenuItemGroup, MenuItem
)
if TYPE_CHECKING:
_: Any
from archinstall.lib.translationhandler import tr
from archinstall.lib.utils.format import as_table
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
class DiskLayoutConfigurationMenu(AbstractSubMenu):
def __init__(
self,
disk_layout_config: DiskLayoutConfiguration | None,
advanced: bool = False
):
self._disk_layout_config = disk_layout_config
self._advanced = advanced
self._data_store: dict[str, Any] = {}
@dataclass
class DiskMenuConfig:
disk_config: DiskLayoutConfiguration | None
lvm_config: LvmConfiguration | None
btrfs_snapshot_config: SnapshotConfig | None
disk_encryption: DiskEncryption | None
menu_optioons = self._define_menu_options()
self._item_group = MenuItemGroup(menu_optioons, sort_items=False, checkmarks=True)
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]):
def __init__(self, disk_layout_config: DiskLayoutConfiguration | None):
if not disk_layout_config:
self._disk_menu_config = DiskMenuConfig(
disk_config=None,
lvm_config=None,
btrfs_snapshot_config=None,
disk_encryption=None,
)
else:
snapshot_config = disk_layout_config.btrfs_options.snapshot_config if disk_layout_config.btrfs_options else None
self._disk_menu_config = DiskMenuConfig(
disk_config=disk_layout_config,
lvm_config=disk_layout_config.lvm_config,
disk_encryption=disk_layout_config.disk_encryption,
btrfs_snapshot_config=snapshot_config,
)
menu_options = self._define_menu_options()
self._item_group = MenuItemGroup(menu_options, sort_items=False, checkmarks=True)
super().__init__(
self._item_group,
self._disk_menu_config,
allow_reset=True,
)
def _define_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=str(_('Partitioning')),
action=lambda x: self._select_disk_layout_config(x),
value=self._disk_layout_config,
text=tr('Partitioning'),
action=self._select_disk_layout_config,
value=self._disk_menu_config.disk_config,
preview_action=self._prev_disk_layouts,
key='disk_config'
key='disk_config',
),
MenuItem(
text='LVM (BETA)',
action=lambda x: self._select_lvm_config(x),
value=self._disk_layout_config.lvm_config if self._disk_layout_config else None,
text='LVM',
action=self._select_lvm_config,
value=self._disk_menu_config.lvm_config,
preview_action=self._prev_lvm_config,
dependencies=[self._check_dep_lvm],
key='lvm_config'
key='lvm_config',
),
MenuItem(
text=tr('Disk encryption'),
action=self._select_disk_encryption,
preview_action=self._prev_disk_encryption,
dependencies=['disk_config'],
key='disk_encryption',
),
MenuItem(
text='Btrfs snapshots',
action=self._select_btrfs_snapshots,
value=self._disk_menu_config.btrfs_snapshot_config,
preview_action=self._prev_btrfs_snapshots,
dependencies=[self._check_dep_btrfs],
key='btrfs_snapshot_config',
),
]
def run(self) -> DiskLayoutConfiguration | None:
super().run()
@override
async def show(self) -> DiskLayoutConfiguration | None: # type: ignore[override]
config: DiskMenuConfig | None = await super().show()
if config is None:
return None
disk_layout_config: DiskLayoutConfiguration | None = self._data_store.get('disk_config', None)
if config.disk_config:
config.disk_config.lvm_config = self._disk_menu_config.lvm_config
config.disk_config.btrfs_options = BtrfsOptions(snapshot_config=self._disk_menu_config.btrfs_snapshot_config)
config.disk_config.disk_encryption = self._disk_menu_config.disk_encryption
return config.disk_config
if disk_layout_config:
disk_layout_config.lvm_config = self._data_store.get('lvm_config', None)
return disk_layout_config
return None
def _check_dep_lvm(self) -> bool:
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
@ -70,56 +134,104 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu):
return False
def _select_disk_layout_config(
self,
preset: DiskLayoutConfiguration | None
) -> DiskLayoutConfiguration | None:
disk_config = select_disk_config(preset, advanced_option=self._advanced)
def _check_dep_btrfs(self) -> bool:
disk_layout_conf: DiskLayoutConfiguration | None = self._menu_item_group.find_by_key('disk_config').value
if disk_layout_conf:
return disk_layout_conf.has_default_btrfs_vols()
return False
async def _select_disk_encryption(self, preset: DiskEncryption | None) -> DiskEncryption | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
lvm_config: LvmConfiguration | None = self._item_group.find_by_key('lvm_config').value
if not disk_config:
return preset
modifications = disk_config.device_modifications
if not DiskEncryption.validate_enc(modifications, lvm_config):
return None
disk_encryption = await DiskEncryptionMenu(modifications, lvm_config=lvm_config, preset=preset).show()
return disk_encryption
async def _select_disk_layout_config(self, preset: DiskLayoutConfiguration | None) -> DiskLayoutConfiguration | None:
disk_config = await select_disk_config(preset)
if disk_config != preset:
self._menu_item_group.find_by_key('lvm_config').value = None
self._menu_item_group.find_by_key('disk_encryption').value = None
return disk_config
def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
async def _select_lvm_config(self, preset: LvmConfiguration | None) -> LvmConfiguration | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
if disk_config:
return select_lvm_config(disk_config, preset=preset)
if not disk_config:
return preset
return preset
lvm_config = await select_lvm_config(disk_config, preset=preset)
if lvm_config != preset:
self._menu_item_group.find_by_key('disk_encryption').value = None
return lvm_config
async def _select_btrfs_snapshots(self, preset: SnapshotConfig | None) -> SnapshotConfig | None:
preset_type = preset.snapshot_type if preset else None
group = MenuItemGroup.from_enum(
SnapshotType,
sort_items=True,
preset=preset_type,
)
result = await Selection[SnapshotType](
group,
allow_reset=True,
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Reset:
return None
case ResultType.Selection:
return SnapshotConfig(snapshot_type=result.get_value())
def _prev_disk_layouts(self, item: MenuItem) -> str | None:
if not item.value:
return None
disk_layout_conf: DiskLayoutConfiguration = item.get_value()
disk_layout_conf = item.get_value()
if disk_layout_conf.config_type == DiskLayoutType.Pre_mount:
msg = str(_('Configuration type: {}')).format(disk_layout_conf.config_type.display_msg()) + '\n'
msg += str(_('Mountpoint')) + ': ' + str(disk_layout_conf.mountpoint)
msg = tr('Configuration type: {}').format(disk_layout_conf.config_type.display_msg()) + '\n'
msg += tr('Mountpoint') + ': ' + str(disk_layout_conf.mountpoint)
return msg
device_mods: list[DeviceModification] = \
list(filter(lambda x: len(x.partitions) > 0, disk_layout_conf.device_modifications))
device_mods = [d for d in disk_layout_conf.device_modifications if d.partitions]
if device_mods:
output_partition = '{}: {}\n'.format(str(_('Configuration')), disk_layout_conf.config_type.display_msg())
output_partition = '{}: {}\n'.format(tr('Configuration'), disk_layout_conf.config_type.display_msg())
output_btrfs = ''
for mod in device_mods:
# create partition table
partition_table = FormattedOutput.as_table(mod.partitions)
partition_table = as_table(mod.partitions)
output_partition += f'{mod.device_path}: {mod.device.device_info.model}\n'
output_partition += '{}: {}\n'.format(tr('Wipe'), mod.wipe)
output_partition += partition_table + '\n'
# create btrfs table
btrfs_partitions = list(
filter(lambda p: len(p.btrfs_subvols) > 0, mod.partitions)
)
btrfs_partitions = [p for p in mod.partitions if p.btrfs_subvols]
for partition in btrfs_partitions:
output_btrfs += FormattedOutput.as_table(partition.btrfs_subvols) + '\n'
output_btrfs += as_table(partition.btrfs_subvols) + '\n'
output = output_partition + output_btrfs
return output.rstrip()
@ -132,17 +244,633 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu):
lvm_config: LvmConfiguration = item.value
output = '{}: {}\n'.format(str(_('Configuration')), lvm_config.config_type.display_msg())
output = '{}: {}\n'.format(tr('Configuration'), lvm_config.config_type.display_msg())
for vol_gp in lvm_config.vol_groups:
pv_table = FormattedOutput.as_table(vol_gp.pvs)
output += '{}:\n{}'.format(str(_('Physical volumes')), pv_table)
pv_table = as_table(vol_gp.pvs)
output += '{}:\n{}'.format(tr('Physical volumes'), pv_table)
output += f'\nVolume Group: {vol_gp.name}'
lvm_volumes = FormattedOutput.as_table(vol_gp.volumes)
output += '\n\n{}:\n{}'.format(str(_('Volumes')), lvm_volumes)
lvm_volumes = as_table(vol_gp.volumes)
output += '\n\n{}:\n{}'.format(tr('Volumes'), lvm_volumes)
return output
return None
def _prev_btrfs_snapshots(self, item: MenuItem) -> str | None:
if not item.value:
return None
snapshot_config: SnapshotConfig = item.value
return tr('Snapshot type: {}').format(snapshot_config.snapshot_type.value)
def _prev_disk_encryption(self, item: MenuItem) -> str | None:
disk_config: DiskLayoutConfiguration | None = self._item_group.find_by_key('disk_config').value
lvm_config: LvmConfiguration | None = self._item_group.find_by_key('lvm_config').value
enc_config: DiskEncryption | None = item.value
if disk_config and not DiskEncryption.validate_enc(disk_config.device_modifications, lvm_config):
return tr('LVM disk encryption with more than 2 partitions is currently not supported')
if enc_config:
enc_type = enc_config.encryption_type
output = tr('Encryption type') + f': {enc_type.type_to_text()}\n'
if enc_config.encryption_password:
output += tr('Password') + f': {enc_config.encryption_password.hidden()}\n'
if enc_type != EncryptionType.NO_ENCRYPTION:
output += tr('Iteration time') + f': {enc_config.iter_time or DEFAULT_ITER_TIME}ms\n'
if enc_config.partitions:
output += f'Partitions: {len(enc_config.partitions)} selected\n'
elif enc_config.lvm_volumes:
output += f'LVM volumes: {len(enc_config.lvm_volumes)} selected\n'
if enc_config.hsm_device:
output += f'HSM: {enc_config.hsm_device.manufacturer}'
return output
return None
async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | None:
def _preview_device_selection(item: MenuItem) -> str | None:
device: _DeviceInfo = item.value # type: ignore[assignment]
dev = device_handler.get_device(device.path)
if dev and dev.partition_infos:
return as_table(dev.partition_infos)
return None
if preset is None:
preset = []
devices = device_handler.devices
items = [
MenuItem(
str(d.device_info.path),
d.device_info,
preview_action=_preview_device_selection,
)
for d in devices
]
presets = [p.device_info for p in preset]
group = MenuItemGroup(items)
group.set_selected_by_value(presets)
result = await Table[_DeviceInfo](
header=tr('Select disks for the installation'),
group=group,
presets=presets,
allow_skip=True,
multi=True,
preview_location='bottom',
preview_header=tr('Partitions'),
).show()
match result.type_:
case ResultType.Reset:
return None
case ResultType.Skip:
return None
case ResultType.Selection:
selected_device_info = result.get_values()
selected_devices = []
for device in devices:
if device.device_info in selected_device_info:
selected_devices.append(device)
return selected_devices
async def get_default_partition_layout(
devices: list[BDevice],
filesystem_type: FilesystemType | None = None,
) -> list[DeviceModification]:
if len(devices) == 1:
device_modification = await suggest_single_disk_layout(
devices[0],
filesystem_type=filesystem_type,
)
return [device_modification]
else:
return await suggest_multi_disk_layout(
devices,
filesystem_type=filesystem_type,
)
async def _manual_partitioning(
preset: list[DeviceModification],
devices: list[BDevice],
) -> list[DeviceModification] | None:
modifications: list[DeviceModification] = []
for device in devices:
mod = next(filter(lambda x: x.device == device, preset), None)
if not mod:
mod = DeviceModification(device, wipe=False)
device_mod = await manual_partitioning(mod, device_handler.partition_table)
if not device_mod:
return None
modifications.append(device_mod)
return modifications
async def select_disk_config(preset: DiskLayoutConfiguration | None = None) -> DiskLayoutConfiguration | None:
default_layout = DiskLayoutType.Default.display_msg()
manual_mode = DiskLayoutType.Manual.display_msg()
pre_mount_mode = DiskLayoutType.Pre_mount.display_msg()
items = [
MenuItem(default_layout, value=default_layout),
MenuItem(manual_mode, value=manual_mode),
MenuItem(pre_mount_mode, value=pre_mount_mode),
]
group = MenuItemGroup(items, sort_items=False)
if preset:
group.set_selected_by_value(preset.config_type.display_msg())
result = await Selection[str](
group,
header=tr('Select a disk configuration'),
allow_skip=True,
allow_reset=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Reset:
return None
case ResultType.Selection:
selection = result.get_value()
if selection == pre_mount_mode:
output = tr('Enter root mount directory') + '\n\n'
output += tr('You will use whatever drive-setup is mounted at the specified directory') + '\n'
output += tr("WARNING: Archinstall won't check the suitability of this setup")
path = await prompt_dir(output, allow_skip=True)
if path is None:
return None
mods = device_handler.detect_pre_mounted_mods(path)
return DiskLayoutConfiguration(
config_type=DiskLayoutType.Pre_mount,
device_modifications=mods,
mountpoint=path,
)
preset_devices = [mod.device for mod in preset.device_modifications] if preset else []
devices = await select_devices(preset_devices)
if devices is None:
return preset
if result.get_value() == default_layout:
modifications = await get_default_partition_layout(devices)
if modifications:
return DiskLayoutConfiguration(
config_type=DiskLayoutType.Default,
device_modifications=modifications,
)
elif result.get_value() == manual_mode:
preset_mods = preset.device_modifications if preset else []
partitions = await _manual_partitioning(preset_mods, devices)
if not partitions:
return preset
return DiskLayoutConfiguration(
config_type=DiskLayoutType.Manual,
device_modifications=partitions,
)
return None
async def select_lvm_config(
disk_config: DiskLayoutConfiguration,
preset: LvmConfiguration | None = None,
) -> LvmConfiguration | None:
preset_value = preset.config_type.display_msg() if preset else None
default_mode = LvmLayoutType.Default.display_msg()
items = [MenuItem(default_mode, value=default_mode)]
group = MenuItemGroup(items)
group.set_focus_by_value(preset_value)
result = await Selection[str](
group,
allow_reset=True,
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Reset:
return None
case ResultType.Selection:
if result.get_value() == default_mode:
return await suggest_lvm_layout(disk_config)
return None
def _boot_partition(sector_size: SectorSize, using_gpt: bool) -> PartitionModification:
flags = [PartitionFlag.BOOT]
size = Size(1, Unit.GiB, sector_size)
start = Size(1, Unit.MiB, sector_size)
if using_gpt:
flags.append(PartitionFlag.ESP)
# boot partition
return PartitionModification(
status=ModificationStatus.CREATE,
type=PartitionType.PRIMARY,
start=start,
length=size,
mountpoint=Path('/boot'),
fs_type=FilesystemType.FAT32,
flags=flags,
)
async def select_main_filesystem_format() -> FilesystemType:
items = [
MenuItem(FilesystemType.BTRFS.value, value=FilesystemType.BTRFS),
MenuItem(FilesystemType.EXT4.value, value=FilesystemType.EXT4),
MenuItem(FilesystemType.XFS.value, value=FilesystemType.XFS),
MenuItem(FilesystemType.F2FS.value, value=FilesystemType.F2FS),
]
group = MenuItemGroup(items, sort_items=False)
result = await Selection[FilesystemType](
group,
header=tr('Select main filesystem'),
allow_skip=False,
).show()
match result.type_:
case ResultType.Selection:
return result.get_value()
case _:
raise ValueError('Unhandled result type')
async def select_mount_options() -> list[str]:
prompt = tr('Would you like to use compression or disable CoW?') + '\n'
compression = tr('Use compression')
disable_cow = tr('Disable Copy-on-Write')
items = [
MenuItem(compression, value=BtrfsMountOption.compress.value),
MenuItem(disable_cow, value=BtrfsMountOption.nodatacow.value),
]
group = MenuItemGroup(items, sort_items=False)
result = await Selection[str](
group,
header=prompt,
allow_skip=True,
).show()
match result.type_:
case ResultType.Skip:
return []
case ResultType.Selection:
return [result.get_value()]
case _:
raise ValueError('Unhandled result type')
def process_root_partition_size(total_size: Size, sector_size: SectorSize) -> Size:
# root partition size processing
total_device_size = total_size.convert(Unit.GiB)
if total_device_size.value > 500:
# maximum size
return Size(value=50, unit=Unit.GiB, sector_size=sector_size)
elif total_device_size.value < 320:
# minimum size
return Size(value=32, unit=Unit.GiB, sector_size=sector_size)
else:
# 10% of total size
length = total_device_size.value // 10
return Size(value=length, unit=Unit.GiB, sector_size=sector_size)
def get_default_btrfs_subvols() -> list[SubvolumeModification]:
# https://btrfs.wiki.kernel.org/index.php/FAQ
# https://unix.stackexchange.com/questions/246976/btrfs-subvolume-uuid-clash
# https://github.com/classy-giraffe/easy-arch/blob/main/easy-arch.sh
return [
SubvolumeModification(Path('@'), Path('/')),
SubvolumeModification(Path('@home'), Path('/home')),
SubvolumeModification(Path('@log'), Path('/var/log')),
SubvolumeModification(Path('@pkg'), Path('/var/cache/pacman/pkg')),
]
async def suggest_single_disk_layout(
device: BDevice,
filesystem_type: FilesystemType | None = None,
separate_home: bool | None = None,
) -> DeviceModification:
if not filesystem_type:
filesystem_type = await select_main_filesystem_format()
sector_size = device.device_info.sector_size
total_size = device.device_info.total_size
available_space = total_size
min_size_to_allow_home_part = Size(64, Unit.GiB, sector_size)
if filesystem_type == FilesystemType.BTRFS:
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
result = await Confirmation(
header=prompt,
allow_skip=False,
preset=True,
).show()
using_subvolumes = result.item() == MenuItem.yes()
mount_options = await select_mount_options()
else:
using_subvolumes = False
mount_options = []
device_modification = DeviceModification(device, wipe=True)
using_gpt = device_handler.partition_table.is_gpt()
if using_gpt:
available_space = available_space.gpt_end()
available_space = available_space.align()
# Used for reference: https://wiki.archlinux.org/title/partitioning
boot_partition = _boot_partition(sector_size, using_gpt)
device_modification.add_partition(boot_partition)
if separate_home is False or using_subvolumes or total_size < min_size_to_allow_home_part:
using_home_partition = False
elif separate_home:
using_home_partition = True
else:
prompt = tr('Would you like to create a separate partition for /home?') + '\n'
result = await Confirmation(
header=prompt,
allow_skip=False,
preset=True,
).show()
using_home_partition = result.item() == MenuItem.yes()
# root partition
root_start = boot_partition.start + boot_partition.length
# Set a size for / (/root)
if using_home_partition:
root_length = process_root_partition_size(total_size, sector_size)
else:
root_length = available_space - root_start
root_partition = PartitionModification(
status=ModificationStatus.CREATE,
type=PartitionType.PRIMARY,
start=root_start,
length=root_length,
mountpoint=Path('/') if not using_subvolumes else None,
fs_type=filesystem_type,
mount_options=mount_options,
)
device_modification.add_partition(root_partition)
if using_subvolumes:
root_partition.btrfs_subvols = get_default_btrfs_subvols()
elif using_home_partition:
# If we don't want to use subvolumes,
# But we want to be able to reuse data between re-installs..
# A second partition for /home would be nice if we have the space for it
home_start = root_partition.start + root_partition.length
home_length = available_space - home_start
flags = []
if using_gpt:
flags.append(PartitionFlag.LINUX_HOME)
home_partition = PartitionModification(
status=ModificationStatus.CREATE,
type=PartitionType.PRIMARY,
start=home_start,
length=home_length,
mountpoint=Path('/home'),
fs_type=filesystem_type,
mount_options=mount_options,
flags=flags,
)
device_modification.add_partition(home_partition)
return device_modification
async def suggest_multi_disk_layout(
devices: list[BDevice],
filesystem_type: FilesystemType | None = None,
) -> list[DeviceModification]:
if not devices:
return []
# Not really a rock solid foundation of information to stand on, but it's a start:
# https://www.reddit.com/r/btrfs/comments/m287gp/partition_strategy_for_two_physical_disks/
# https://www.reddit.com/r/btrfs/comments/9us4hr/what_is_your_btrfs_partitionsubvolumes_scheme/
min_home_partition_size = Size(40, Unit.GiB, SectorSize.default())
# rough estimate taking in to account user desktops etc. TODO: Catch user packages to detect size?
desired_root_partition_size = Size(32, Unit.GiB, SectorSize.default())
mount_options = []
if not filesystem_type:
filesystem_type = await select_main_filesystem_format()
# find proper disk for /home
possible_devices = [d for d in devices if d.device_info.total_size >= min_home_partition_size]
home_device = max(possible_devices, key=lambda d: d.device_info.total_size) if possible_devices else None
# find proper device for /root
devices_delta = {}
for device in devices:
if device is not home_device:
delta = device.device_info.total_size - desired_root_partition_size
devices_delta[device] = delta
sorted_delta: list[tuple[BDevice, Size]] = sorted(devices_delta.items(), key=lambda x: x[1])
root_device: BDevice | None = sorted_delta[0][0]
if home_device is None or root_device is None:
text = tr('The selected drives do not have the minimum capacity required for an automatic suggestion\n')
text += tr('Minimum capacity for /home partition: {}GiB\n').format(min_home_partition_size.format_size(Unit.GiB))
text += tr('Minimum capacity for Arch Linux partition: {}GiB').format(desired_root_partition_size.format_size(Unit.GiB))
_ = await Notify(text).show()
return []
if filesystem_type == FilesystemType.BTRFS:
mount_options = await select_mount_options()
device_paths = ', '.join(str(d.device_info.path) for d in devices)
debug(f'Suggesting multi-disk-layout for devices: {device_paths}')
debug(f'/root: {root_device.device_info.path}')
debug(f'/home: {home_device.device_info.path}')
root_device_modification = DeviceModification(root_device, wipe=True)
home_device_modification = DeviceModification(home_device, wipe=True)
root_device_sector_size = root_device_modification.device.device_info.sector_size
home_device_sector_size = home_device_modification.device.device_info.sector_size
using_gpt = device_handler.partition_table.is_gpt()
# add boot partition to the root device
boot_partition = _boot_partition(root_device_sector_size, using_gpt)
root_device_modification.add_partition(boot_partition)
root_start = boot_partition.start + boot_partition.length
root_length = root_device.device_info.total_size - root_start
if using_gpt:
root_length = root_length.gpt_end()
root_length = root_length.align()
# add root partition to the root device
root_partition = PartitionModification(
status=ModificationStatus.CREATE,
type=PartitionType.PRIMARY,
start=root_start,
length=root_length,
mountpoint=Path('/'),
mount_options=mount_options,
fs_type=filesystem_type,
)
root_device_modification.add_partition(root_partition)
home_start = Size(1, Unit.MiB, home_device_sector_size)
home_length = home_device.device_info.total_size - home_start
flags = []
if using_gpt:
home_length = home_length.gpt_end()
flags.append(PartitionFlag.LINUX_HOME)
home_length = home_length.align()
# add home partition to home device
home_partition = PartitionModification(
status=ModificationStatus.CREATE,
type=PartitionType.PRIMARY,
start=home_start,
length=home_length,
mountpoint=Path('/home'),
mount_options=mount_options,
fs_type=filesystem_type,
flags=flags,
)
home_device_modification.add_partition(home_partition)
return [root_device_modification, home_device_modification]
async def suggest_lvm_layout(
disk_config: DiskLayoutConfiguration,
filesystem_type: FilesystemType | None = None,
vg_grp_name: str = 'ArchinstallVg',
) -> LvmConfiguration:
if disk_config.config_type != DiskLayoutType.Default:
raise ValueError('LVM suggested volumes are only available for default partitioning')
using_subvolumes = False
btrfs_subvols = []
home_volume = True
mount_options = []
if not filesystem_type:
filesystem_type = await select_main_filesystem_format()
if filesystem_type == FilesystemType.BTRFS:
prompt = tr('Would you like to use BTRFS subvolumes with a default structure?') + '\n'
result = await Confirmation(header=prompt, allow_skip=False, preset=True).show()
using_subvolumes = MenuItem.yes() == result.item()
mount_options = await select_mount_options()
if using_subvolumes:
btrfs_subvols = get_default_btrfs_subvols()
home_volume = False
boot_part: PartitionModification | None = None
other_part: list[PartitionModification] = []
for mod in disk_config.device_modifications:
for part in mod.partitions:
if part.is_boot():
boot_part = part
else:
other_part.append(part)
if not boot_part:
raise ValueError('Unable to find boot partition in partition modifications')
total_vol_available = sum(
[p.length for p in other_part],
Size(0, Unit.B, SectorSize.default()),
)
root_vol_size = process_root_partition_size(total_vol_available, SectorSize.default())
home_vol_size = total_vol_available - root_vol_size
lvm_vol_group = LvmVolumeGroup(vg_grp_name, pvs=other_part)
root_vol = LvmVolume(
status=ModificationStatus.CREATE,
name='root',
fs_type=filesystem_type,
length=root_vol_size,
mountpoint=Path('/'),
btrfs_subvols=btrfs_subvols,
mount_options=mount_options,
)
lvm_vol_group.volumes.append(root_vol)
if home_volume:
home_vol = LvmVolume(
status=ModificationStatus.CREATE,
name='home',
fs_type=filesystem_type,
length=home_vol_size,
mountpoint=Path('/home'),
)
lvm_vol_group.volumes.append(home_vol)
return LvmConfiguration(LvmLayoutType.Default, [lvm_vol_group])

View File

@ -1,140 +1,156 @@
from pathlib import Path
from typing import Any, TYPE_CHECKING
from typing import override
from . import LvmConfiguration, LvmVolume
from ..disk import (
DeviceModification,
DiskLayoutConfiguration,
PartitionModification,
DiskEncryption,
EncryptionType
)
from ..menu import AbstractSubMenu
from .fido import Fido2Device, Fido2
from ..output import FormattedOutput
from ..utils.util import get_password
from archinstall.tui import (
MenuItemGroup, MenuItem, SelectMenu,
FrameProperties, Alignment, ResultType
)
from archinstall.lib.disk.fido import Fido2
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.menu.helpers import Input, Selection, Table
from archinstall.lib.menu.menu_helper import MenuHelper
from archinstall.lib.menu.util import get_password
from archinstall.lib.models.device import (
DEFAULT_ITER_TIME,
DeviceModification,
DiskEncryption,
EncryptionType,
Fido2Device,
LvmConfiguration,
LvmVolume,
PartitionModification,
)
from archinstall.lib.models.users import Password
from archinstall.lib.translationhandler import tr
from archinstall.lib.utils.format import as_table
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
from archinstall.tui.result import ResultType
if TYPE_CHECKING:
_: Any
class DiskEncryptionMenu(AbstractSubMenu):
class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
def __init__(
self,
disk_config: DiskLayoutConfiguration,
preset: DiskEncryption | None = None
device_modifications: list[DeviceModification],
lvm_config: LvmConfiguration | None = None,
preset: DiskEncryption | None = None,
):
if preset:
self._preset = preset
self._enc_config = preset
else:
self._preset = DiskEncryption()
self._enc_config = DiskEncryption()
self._data_store: dict[str, Any] = {}
self._disk_config = disk_config
self._device_modifications = device_modifications
self._lvm_config = lvm_config
menu_optioons = self._define_menu_options()
self._item_group = MenuItemGroup(menu_optioons, sort_items=False, checkmarks=True)
menu_options = self._define_menu_options()
self._item_group = MenuItemGroup(menu_options, sort_items=False, checkmarks=True)
super().__init__(self._item_group, data_store=self._data_store, allow_reset=True)
super().__init__(
self._item_group,
self._enc_config,
allow_reset=True,
)
def _define_menu_options(self) -> list[MenuItem]:
return [
MenuItem(
text=str(_('Encryption type')),
action=lambda x: select_encryption_type(self._disk_config, x),
value=self._preset.encryption_type,
preview_action=self._preview,
key='encryption_type'
text=tr('Encryption type'),
action=lambda x: select_encryption_type(self._lvm_config, x),
value=self._enc_config.encryption_type,
preview_action=self._prev_type,
key='encryption_type',
),
MenuItem(
text=str(_('Encryption password')),
text=tr('Encryption password'),
action=lambda x: select_encrypted_password(),
value=self._preset.encryption_password,
value=self._enc_config.encryption_password,
dependencies=[self._check_dep_enc_type],
preview_action=self._preview,
key='encryption_password'
preview_action=self._prev_password,
key='encryption_password',
),
MenuItem(
text=str(_('Partitions')),
action=lambda x: select_partitions_to_encrypt(self._disk_config.device_modifications, x),
value=self._preset.partitions,
text=tr('Iteration time'),
action=select_iteration_time,
value=self._enc_config.iter_time,
dependencies=[self._check_dep_enc_type],
preview_action=self._prev_iter_time,
key='iter_time',
),
MenuItem(
text=tr('Partitions'),
action=lambda x: select_partitions_to_encrypt(self._device_modifications, x),
value=self._enc_config.partitions,
dependencies=[self._check_dep_partitions],
preview_action=self._preview,
key='partitions'
preview_action=self._prev_partitions,
key='partitions',
),
MenuItem(
text=str(_('LVM volumes')),
action=lambda x: self._select_lvm_vols(x),
value=self._preset.lvm_volumes,
text=tr('LVM volumes'),
action=self._select_lvm_vols,
value=self._enc_config.lvm_volumes,
dependencies=[self._check_dep_lvm_vols],
preview_action=self._preview,
key='lvm_vols'
preview_action=self._prev_lvm_vols,
key='lvm_volumes',
),
MenuItem(
text=str(_('HSM')),
action=lambda x: select_hsm(x),
value=self._preset.hsm_device,
text=tr('HSM'),
action=select_hsm,
value=self._enc_config.hsm_device,
dependencies=[self._check_dep_enc_type],
preview_action=self._preview,
key='HSM'
preview_action=self._prev_hsm,
key='hsm_device',
),
]
def _select_lvm_vols(self, preset: list[LvmVolume]) -> list[LvmVolume]:
if self._disk_config.lvm_config:
return select_lvm_vols_to_encrypt(self._disk_config.lvm_config, preset=preset)
async def _select_lvm_vols(self, preset: list[LvmVolume]) -> list[LvmVolume]:
if self._lvm_config:
return await select_lvm_vols_to_encrypt(self._lvm_config, preset=preset)
return []
def _check_dep_enc_type(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type != EncryptionType.NoEncryption:
if enc_type and enc_type != EncryptionType.NO_ENCRYPTION:
return True
return False
def _check_dep_partitions(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks]:
if enc_type and enc_type in [EncryptionType.LUKS, EncryptionType.LVM_ON_LUKS]:
return True
return False
def _check_dep_lvm_vols(self) -> bool:
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
if enc_type and enc_type == EncryptionType.LuksOnLvm:
if enc_type and enc_type == EncryptionType.LUKS_ON_LVM:
return True
return False
def run(self) -> DiskEncryption | None:
super().run()
@override
async def show(self) -> DiskEncryption | None:
enc_config = await super().show()
if enc_config is None:
return None
enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value
enc_password: str | None = self._item_group.find_by_key('encryption_password').value
enc_password: Password | None = self._item_group.find_by_key('encryption_password').value
iter_time: int | None = self._item_group.find_by_key('iter_time').value
enc_partitions = self._item_group.find_by_key('partitions').value
enc_lvm_vols = self._item_group.find_by_key('lvm_vols').value
enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value
assert enc_type is not None
assert enc_partitions is not None
assert enc_lvm_vols is not None
if enc_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks] and enc_partitions:
if enc_type in [EncryptionType.LUKS, EncryptionType.LVM_ON_LUKS] and enc_partitions:
enc_lvm_vols = []
if enc_type == EncryptionType.LuksOnLvm:
if enc_type == EncryptionType.LUKS_ON_LVM:
enc_partitions = []
if enc_type != EncryptionType.NoEncryption and enc_password and (enc_partitions or enc_lvm_vols):
if enc_type != EncryptionType.NO_ENCRYPTION and enc_password and (enc_partitions or enc_lvm_vols):
return DiskEncryption(
encryption_password=enc_password,
encryption_type=enc_type,
partitions=enc_partitions,
lvm_volumes=enc_lvm_vols,
hsm_device=self._data_store.get('HSM', None)
hsm_device=enc_config.hsm_device,
iter_time=iter_time or DEFAULT_ITER_TIME,
)
return None
@ -142,19 +158,22 @@ class DiskEncryptionMenu(AbstractSubMenu):
def _preview(self, item: MenuItem) -> str | None:
output = ''
if (enc_type := self._prev_type()) is not None:
if (enc_type := self._prev_type(item)) is not None:
output += enc_type
if (enc_pwd := self._prev_password()) is not None:
if (enc_pwd := self._prev_password(item)) is not None:
output += f'\n{enc_pwd}'
if (fido_device := self._prev_hsm()) is not None:
if (iter_time := self._prev_iter_time(item)) is not None:
output += f'\n{iter_time}'
if (fido_device := self._prev_hsm(item)) is not None:
output += f'\n{fido_device}'
if (partitions := self._prev_partitions()) is not None:
if (partitions := self._prev_partitions(item)) is not None:
output += f'\n\n{partitions}'
if (lvm := self._prev_lvm_vols()) is not None:
if (lvm := self._prev_lvm_vols(item)) is not None:
output += f'\n\n{lvm}'
if not output:
@ -162,75 +181,84 @@ class DiskEncryptionMenu(AbstractSubMenu):
return output
def _prev_type(self) -> str | None:
def _prev_type(self, item: MenuItem) -> str | None:
enc_type = self._item_group.find_by_key('encryption_type').value
if enc_type:
enc_text = EncryptionType.type_to_text(enc_type)
return f'{_("Encryption type")}: {enc_text}'
enc_text = enc_type.type_to_text()
return f'{tr("Encryption type")}: {enc_text}'
return None
def _prev_password(self) -> str | None:
enc_pwd = self._item_group.find_by_key('encryption_password').value
if enc_pwd:
pwd_text = '*' * len(enc_pwd)
return f'{_("Encryption password")}: {pwd_text}'
def _prev_password(self, item: MenuItem) -> str | None:
if item.value:
return f'{tr("Encryption password")}: {item.value.hidden()}'
return None
def _prev_partitions(self) -> str | None:
partitions: list[PartitionModification] | None = self._item_group.find_by_key('partitions').value
if partitions:
output = str(_('Partitions to be encrypted')) + '\n'
output += FormattedOutput.as_table(partitions)
def _prev_partitions(self, item: MenuItem) -> str | None:
if item.value:
output = tr('Partitions to be encrypted') + '\n'
output += as_table(item.value)
return output.rstrip()
return None
def _prev_lvm_vols(self) -> str | None:
volumes: list[PartitionModification] | None = self._item_group.find_by_key('lvm_vols').value
if volumes:
output = str(_('LVM volumes to be encrypted')) + '\n'
output += FormattedOutput.as_table(volumes)
def _prev_lvm_vols(self, item: MenuItem) -> str | None:
if item.value:
output = tr('LVM volumes to be encrypted') + '\n'
output += as_table(item.value)
return output.rstrip()
return None
def _prev_hsm(self) -> str | None:
fido_device: Fido2Device | None = self._item_group.find_by_key('HSM').value
if not fido_device:
def _prev_hsm(self, item: MenuItem) -> str | None:
if not item.value:
return None
fido_device: Fido2Device = item.value
output = str(fido_device.path)
output += f' ({fido_device.manufacturer}, {fido_device.product})'
return f'{_("HSM device")}: {output}'
return f'{tr("HSM device")}: {output}'
def _prev_iter_time(self, item: MenuItem) -> str | None:
if item.value:
iter_time = item.value
enc_type = self._item_group.find_by_key('encryption_type').value
if iter_time and enc_type != EncryptionType.NO_ENCRYPTION:
return f'{tr("Iteration time")}: {iter_time}ms'
return None
def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: EncryptionType) -> EncryptionType | None:
async def select_encryption_type(
lvm_config: LvmConfiguration | None = None,
preset: EncryptionType | None = None,
) -> EncryptionType | None:
options: list[EncryptionType] = []
preset_value = EncryptionType.type_to_text(preset)
if disk_config.lvm_config:
options = [EncryptionType.LvmOnLuks, EncryptionType.LuksOnLvm]
if lvm_config:
options = [EncryptionType.LVM_ON_LUKS, EncryptionType.LUKS_ON_LVM]
else:
options = [EncryptionType.Luks]
options = [EncryptionType.LUKS]
items = [MenuItem(EncryptionType.type_to_text(o), value=o) for o in options]
if not preset:
preset = options[0]
preset_value = preset.type_to_text()
items = [MenuItem(o.type_to_text(), value=o) for o in options]
group = MenuItemGroup(items)
group.set_focus_by_value(preset_value)
result = SelectMenu(
result = await Selection[EncryptionType](
group,
header=tr('Select encryption type'),
allow_skip=True,
allow_reset=True,
alignment=Alignment.CENTER,
frame=FrameProperties.min(str(_('Encryption type')))
).run()
).show()
match result.type_:
case ResultType.Reset:
@ -241,34 +269,32 @@ def select_encryption_type(disk_config: DiskLayoutConfiguration, preset: Encrypt
return result.get_value()
def select_encrypted_password() -> str | None:
header = str(_('Enter disk encryption password (leave blank for no encryption)')) + '\n'
password = get_password(
text=str(_('Disk encryption password')),
async def select_encrypted_password() -> Password | None:
header = tr('Enter disk encryption password (leave blank for no encryption)') + '\n'
password = await get_password(
header=header,
allow_skip=True
allow_skip=True,
)
return password
def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
header = str(_('Select a FIDO2 device to use for HSM'))
async def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
header = tr('Select a FIDO2 device to use for HSM') + '\n'
try:
fido_devices = Fido2.get_fido2_devices()
fido_devices = Fido2.get_cryptenroll_devices()
except ValueError:
return None
if fido_devices:
group, table_header = MenuHelper.create_table(data=fido_devices)
header = f'{header}\n\n{table_header}'
group = MenuHelper(data=fido_devices).create_menu_group()
result = SelectMenu(
result = await Selection[Fido2Device](
group,
header=header,
alignment=Alignment.CENTER,
).run()
allow_skip=True,
).show()
match result.type_:
case ResultType.Reset:
@ -281,28 +307,29 @@ def select_hsm(preset: Fido2Device | None = None) -> Fido2Device | None:
return None
def select_partitions_to_encrypt(
async def select_partitions_to_encrypt(
modification: list[DeviceModification],
preset: list[PartitionModification]
preset: list[PartitionModification],
) -> list[PartitionModification]:
partitions: list[PartitionModification] = []
# do not allow encrypting the boot partition
for mod in modification:
partitions += list(filter(lambda x: x.mountpoint != Path('/boot'), mod.partitions))
partitions += [p for p in mod.partitions if p.mountpoint != Path('/boot') and not p.is_swap()]
# do not allow encrypting existing partitions that are not marked as wipe
avail_partitions = list(filter(lambda x: not x.exists(), partitions))
avail_partitions = [p for p in partitions if not p.exists()]
if avail_partitions:
group, header = MenuHelper.create_table(data=avail_partitions)
group = MenuItemGroup.from_objects(avail_partitions)
group.set_selected_by_value(preset)
result = SelectMenu(
group,
header=header,
alignment=Alignment.CENTER,
multi=True
).run()
result = await Table[PartitionModification](
header=tr('Select disks for the installation'),
group=group,
allow_skip=True,
multi=True,
).show()
match result.type_:
case ResultType.Reset:
@ -316,21 +343,22 @@ def select_partitions_to_encrypt(
return []
def select_lvm_vols_to_encrypt(
async def select_lvm_vols_to_encrypt(
lvm_config: LvmConfiguration,
preset: list[LvmVolume]
preset: list[LvmVolume],
) -> list[LvmVolume]:
volumes: list[LvmVolume] = lvm_config.get_all_volumes()
if volumes:
group, header = MenuHelper.create_table(data=volumes)
group = MenuItemGroup.from_objects(volumes)
group.set_selected_by_value(preset)
result = SelectMenu(
group,
header=header,
alignment=Alignment.CENTER,
multi=True
).run()
result = await Table[LvmVolume](
header=tr('Select disks for the installation'),
group=group,
allow_skip=True,
multi=True,
).show()
match result.type_:
case ResultType.Reset:
@ -342,3 +370,37 @@ def select_lvm_vols_to_encrypt(
return volumes
return []
async def select_iteration_time(preset: int | None = None) -> int | None:
header = tr('Enter iteration time for LUKS encryption (in milliseconds)') + '\n'
header += tr('Higher values increase security but slow down boot time') + '\n'
header += tr('Default: {}ms, Recommended range: 1000-60000').format(DEFAULT_ITER_TIME) + '\n'
def validate_iter_time(value: str) -> str | None:
try:
iter_time = int(value)
if iter_time < 100:
return tr('Iteration time must be at least 100ms')
if iter_time > 120000:
return tr('Iteration time must be at most 120000ms')
return None
except ValueError:
return tr('Please enter a valid number')
result = await Input(
header=header,
allow_skip=True,
default_value=str(preset) if preset else str(DEFAULT_ITER_TIME),
validator_callback=validate_iter_time,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
if not result.get_value():
return preset
return int(result.get_value())
case ResultType.Reset:
return None

View File

@ -1,20 +1,52 @@
from __future__ import annotations
import getpass
from pathlib import Path
from typing import ClassVar
from .device_model import Fido2Device
from ..general import SysCommand, SysCommandWorker, clear_vt100_escape_codes
from ..output import error, info
from ..exceptions import SysCallError
from archinstall.lib.command import SysCommand, SysCommandWorker
from archinstall.lib.exceptions import SysCallError
from archinstall.lib.log import error, info
from archinstall.lib.models.device import Fido2Device
from archinstall.lib.models.users import Password
from archinstall.lib.utils.encoding import clear_vt100_escape_codes_from_str
class Fido2:
_loaded: bool = False
_fido2_devices: list[Fido2Device] = []
_loaded_cryptsetup: bool = False
_loaded_u2f: bool = False
_cryptenroll_devices: ClassVar[list[Fido2Device]] = []
_u2f_devices: ClassVar[list[Fido2Device]] = []
@classmethod
def get_fido2_devices(cls, reload: bool = False) -> list[Fido2Device]:
def get_fido2_devices(cls) -> list[Fido2Device]:
"""
fido2-tool output example:
/dev/hidraw4: vendor=0x1050, product=0x0407 (Yubico YubiKey OTP+FIDO+CCID)
"""
if not cls._loaded_u2f:
cls._loaded_u2f = True
try:
ret = SysCommand('fido2-token -L').decode()
except Exception as e:
error(f'failed to read fido2 devices: {e}')
return []
fido_devices = clear_vt100_escape_codes_from_str(ret)
if not fido_devices:
return []
for line in fido_devices.splitlines():
path, details = line.replace(',', '').split(':', maxsplit=1)
_, product, manufacturer = details.strip().split(' ', maxsplit=2)
cls._u2f_devices.append(Fido2Device(Path(path.strip()), manufacturer.strip(), product.strip().split('=')[1]))
return cls._u2f_devices
@classmethod
def get_cryptenroll_devices(cls, reload: bool = False) -> list[Fido2Device]:
"""
Uses systemd-cryptenroll to list the FIDO2 devices
connected that supports FIDO2.
@ -29,20 +61,20 @@ class Fido2:
Output example:
PATH MANUFACTURER PRODUCT
/dev/hidraw1 Yubico YubiKey OTP+FIDO+CCID
PATH MANUFACTURER PRODUCT
/dev/hidraw1 Yubico YubiKey OTP+FIDO+CCID
"""
# to prevent continuous reloading which will slow
# down moving the cursor in the menu
if not cls._loaded or reload:
if not cls._loaded_cryptsetup or reload:
try:
ret = SysCommand("systemd-cryptenroll --fido2-device=list").decode()
ret = SysCommand('systemd-cryptenroll --fido2-device=list').decode()
except SysCallError:
error('fido2 support is most likely not installed')
raise ValueError('HSM devices can not be detected, is libfido2 installed?')
fido_devices: str = clear_vt100_escape_codes(ret) # type: ignore
fido_devices = clear_vt100_escape_codes_from_str(ret)
manufacturer_pos = 0
product_pos = 0
@ -59,33 +91,28 @@ class Fido2:
product = line[product_pos:]
devices.append(
Fido2Device(Path(path), manufacturer, product)
Fido2Device(Path(path), manufacturer, product),
)
cls._loaded = True
cls._fido2_devices = devices
cls._loaded_cryptsetup = True
cls._cryptenroll_devices = devices
return cls._fido2_devices
return cls._cryptenroll_devices
@classmethod
def fido2_enroll(
cls,
hsm_device: Fido2Device,
dev_path: Path,
password: str
) -> None:
worker = SysCommandWorker(f"systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}", peek_output=True)
@staticmethod
def fido2_enroll(hsm_device: Fido2Device, dev_path: Path, password: Password) -> None:
worker = SysCommandWorker(f'systemd-cryptenroll --fido2-device={hsm_device.path} {dev_path}', peek_output=True)
pw_inputted = False
pin_inputted = False
info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds')
while worker.is_alive():
if pw_inputted is False:
if bytes(f"please enter current passphrase for disk {dev_path}", 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(password, 'UTF-8'))
if bytes(f'please enter current passphrase for disk {dev_path}', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(password.plaintext, 'UTF-8'))
pw_inputted = True
elif pin_inputted is False:
if bytes("please enter security token pin", 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(" "), 'UTF-8'))
if bytes('please enter security token pin', 'UTF-8') in worker._trace_log.lower():
worker.write(bytes(getpass.getpass(' '), 'UTF-8'))
pin_inputted = True
info('You might need to touch the FIDO2 device to unlock it if no prompt comes up after 3 seconds')

View File

@ -1,92 +1,81 @@
from __future__ import annotations
import math
import time
from pathlib import Path
from typing import Any, TYPE_CHECKING
from ..interactions.general_conf import ask_abort
from .device_handler import device_handler
from .device_model import (
DiskLayoutConfiguration, DiskLayoutType, PartitionTable,
FilesystemType, DiskEncryption, LvmVolumeGroup,
Size, Unit, SectorSize, PartitionModification, EncryptionType,
LvmVolume, LvmConfiguration
from archinstall.lib.disk.device_handler import device_handler
from archinstall.lib.disk.luks import Luks2
from archinstall.lib.disk.lvm import (
lvm_group_info,
lvm_pv_create,
lvm_vg_create,
lvm_vol_create,
lvm_vol_info,
lvm_vol_reduce,
)
from ..hardware import SysInfo
from ..luks import Luks2
from ..output import debug, info
from archinstall.tui import (
Tui
from archinstall.lib.disk.utils import udev_sync
from archinstall.lib.log import debug, info
from archinstall.lib.models.device import (
DiskEncryption,
DiskLayoutConfiguration,
DiskLayoutType,
EncryptionType,
FilesystemType,
LvmConfiguration,
LvmVolume,
LvmVolumeGroup,
PartitionModification,
SectorSize,
Size,
Unit,
)
if TYPE_CHECKING:
_: Any
class FilesystemHandler:
def __init__(
self,
disk_config: DiskLayoutConfiguration,
enc_conf: DiskEncryption | None = None
):
def __init__(self, disk_config: DiskLayoutConfiguration):
self._disk_config = disk_config
self._enc_config = enc_conf
self._enc_config = disk_config.disk_encryption
def perform_filesystem_operations(self, show_countdown: bool = True) -> None:
def perform_filesystem_operations(self) -> None:
if self._disk_config.config_type == DiskLayoutType.Pre_mount:
debug('Disk layout configuration is set to pre-mount, not performing any operations')
return
device_mods = list(filter(lambda x: len(x.partitions) > 0, self._disk_config.device_modifications))
device_mods = [d for d in self._disk_config.device_modifications if d.partitions]
if not device_mods:
debug('No modifications required')
return
device_paths = ', '.join([str(mod.device.device_info.path) for mod in device_mods])
if show_countdown:
self._final_warning(device_paths)
# Setup the blockdevice, filesystem (and optionally encryption).
# Once that's done, we'll hand over to perform_installation()
partition_table = PartitionTable.GPT
if SysInfo.has_uefi() is False:
partition_table = PartitionTable.MBR
# make sure all devices are unmounted
for mod in device_mods:
device_handler.umount_all_existing(mod.device_path)
for mod in device_mods:
device_handler.partition(mod, partition_table=partition_table)
device_handler.partition(mod)
udev_sync()
if self._disk_config.lvm_config:
for mod in device_mods:
if boot_part := mod.get_boot_partition():
debug(f'Formatting boot partition: {boot_part.dev_path}')
self._format_partitions(
[boot_part],
mod.device_path
)
self._format_partitions([boot_part])
self.perform_lvm_operations()
else:
for mod in device_mods:
self._format_partitions(
mod.partitions,
mod.device_path
)
self._format_partitions(mod.partitions)
for part_mod in mod.partitions:
if part_mod.fs_type == FilesystemType.Btrfs:
if part_mod.fs_type == FilesystemType.BTRFS and part_mod.is_create_or_modify():
device_handler.create_btrfs_volumes(part_mod, enc_conf=self._enc_config)
def _format_partitions(
self,
partitions: list[PartitionModification],
device_path: Path
) -> None:
"""
Format can be given an overriding path, for instance /dev/null to test
@ -105,13 +94,13 @@ class FilesystemHandler:
part_mod.safe_dev_path,
part_mod.mapper_name,
part_mod.safe_fs_type,
self._enc_config
self._enc_config,
)
else:
device_handler.format(part_mod.safe_fs_type, part_mod.safe_dev_path)
# synchronize with udev before using lsblk
device_handler.udev_sync()
udev_sync()
lsblk_info = device_handler.fetch_part_info(part_mod.safe_dev_path)
@ -124,10 +113,9 @@ class FilesystemHandler:
# verify that all partitions have a path set (which implies that they have been created)
lambda x: x.dev_path is None: ValueError('When formatting, all partitions must have a path set'),
# crypto luks is not a valid file system type
lambda x: x.fs_type is FilesystemType.Crypto_luks: ValueError(
'Crypto luks cannot be set as a filesystem type'),
lambda x: x.fs_type is FilesystemType.CRYPTO_LUKS: ValueError('Crypto luks cannot be set as a filesystem type'),
# file system type must be set
lambda x: x.fs_type is None: ValueError('File system type must be set for modification')
lambda x: x.fs_type is None: ValueError('File system type must be set for modification'),
}
for check, exc in checks.items():
@ -144,55 +132,46 @@ class FilesystemHandler:
if self._enc_config:
self._setup_lvm_encrypted(
self._disk_config.lvm_config,
self._enc_config
self._enc_config,
)
else:
self._setup_lvm(self._disk_config.lvm_config)
self._format_lvm_vols(self._disk_config.lvm_config)
def _setup_lvm_encrypted(self, lvm_config: LvmConfiguration, enc_config: DiskEncryption) -> None:
if enc_config.encryption_type == EncryptionType.LvmOnLuks:
if enc_config.encryption_type == EncryptionType.LVM_ON_LUKS:
enc_mods = self._encrypt_partitions(enc_config, lock_after_create=False)
self._setup_lvm(lvm_config, enc_mods)
self._format_lvm_vols(lvm_config)
# export the lvm group safely otherwise the Luks cannot be closed
self._safely_close_lvm(lvm_config)
for luks in enc_mods.values():
luks.lock()
elif enc_config.encryption_type == EncryptionType.LuksOnLvm:
# Don't close LVM or LUKS during setup - keep everything active
# The installation phase will handle unlocking and mounting
# Closing causes "parent leaked" and lvchange errors
elif enc_config.encryption_type == EncryptionType.LUKS_ON_LVM:
self._setup_lvm(lvm_config)
enc_vols = self._encrypt_lvm_vols(lvm_config, enc_config, False)
self._format_lvm_vols(lvm_config, enc_vols)
# Lock LUKS devices but keep LVM active
# LVM volumes must remain active for later re-unlock during installation
for luks in enc_vols.values():
luks.lock()
self._safely_close_lvm(lvm_config)
def _safely_close_lvm(self, lvm_config: LvmConfiguration) -> None:
for vg in lvm_config.vol_groups:
for vol in vg.volumes:
device_handler.lvm_vol_change(vol, False)
device_handler.lvm_export_vg(vg)
def _setup_lvm(
self,
lvm_config: LvmConfiguration,
enc_mods: dict[PartitionModification, Luks2] = {}
enc_mods: dict[PartitionModification, Luks2] = {},
) -> None:
self._lvm_create_pvs(lvm_config, enc_mods)
for vg in lvm_config.vol_groups:
pv_dev_paths = self._get_all_pv_dev_paths(vg.pvs, enc_mods)
device_handler.lvm_vg_create(pv_dev_paths, vg.name)
lvm_vg_create(pv_dev_paths, vg.name)
# figure out what the actual available size in the group is
vg_info = device_handler.lvm_group_info(vg.name)
vg_info = lvm_group_info(vg.name)
if not vg_info:
raise ValueError('Unable to fetch VG info')
@ -206,7 +185,15 @@ class FilesystemHandler:
desired_size = sum([vol.length for vol in vg.volumes], Size(0, Unit.B, SectorSize.default()))
delta = desired_size - avail_size
max_vol_offset = delta.convert(Unit.B)
delta_bytes = delta.convert(Unit.B)
# Round the offset up to the next physical extent (PE, 4 MiB by default)
# to ensure lvcreate`s internal rounding doesn`t consume space reserved
# for subsequent logical volumes.
pe_bytes = Size(4, Unit.MiB, SectorSize.default()).convert(Unit.B)
pe_count = math.ceil(delta_bytes.value / pe_bytes.value)
rounded_offset = pe_count * pe_bytes.value
max_vol_offset = Size(rounded_offset, Unit.B, SectorSize.default())
max_vol = max(vg.volumes, key=lambda x: x.length)
@ -214,11 +201,11 @@ class FilesystemHandler:
offset = max_vol_offset if lv == max_vol else None
debug(f'vg: {vg.name}, vol: {lv.name}, offset: {offset}')
device_handler.lvm_vol_create(vg.name, lv, offset)
lvm_vol_create(vg.name, lv, offset)
while True:
debug('Fetching LVM volume info')
lv_info = device_handler.lvm_vol_info(lv.name)
lv_info = lvm_vol_info(lv.name)
if lv_info is not None:
break
@ -229,7 +216,7 @@ class FilesystemHandler:
def _format_lvm_vols(
self,
lvm_config: LvmConfiguration,
enc_vols: dict[LvmVolume, Luks2] = {}
enc_vols: dict[LvmVolume, Luks2] = {},
) -> None:
for vol in lvm_config.get_all_volumes():
if enc_vol := enc_vols.get(vol, None):
@ -243,25 +230,25 @@ class FilesystemHandler:
# find the mapper device yet
device_handler.format(vol.fs_type, path)
if vol.fs_type == FilesystemType.Btrfs:
if vol.fs_type == FilesystemType.BTRFS:
device_handler.create_lvm_btrfs_subvolumes(path, vol.btrfs_subvols, vol.mount_options)
def _lvm_create_pvs(
self,
lvm_config: LvmConfiguration,
enc_mods: dict[PartitionModification, Luks2] = {}
enc_mods: dict[PartitionModification, Luks2] = {},
) -> None:
pv_paths: set[Path] = set()
for vg in lvm_config.vol_groups:
pv_paths |= self._get_all_pv_dev_paths(vg.pvs, enc_mods)
device_handler.lvm_pv_create(pv_paths)
lvm_pv_create(pv_paths)
def _get_all_pv_dev_paths(
self,
pvs: list[PartitionModification],
enc_mods: dict[PartitionModification, Luks2] = {}
enc_mods: dict[PartitionModification, Luks2] = {},
) -> set[Path]:
pv_paths: set[Path] = set()
@ -278,7 +265,7 @@ class FilesystemHandler:
self,
lvm_config: LvmConfiguration,
enc_config: DiskEncryption,
lock_after_create: bool = True
lock_after_create: bool = True,
) -> dict[LvmVolume, Luks2]:
enc_vols: dict[LvmVolume, Luks2] = {}
@ -288,7 +275,8 @@ class FilesystemHandler:
vol.safe_dev_path,
vol.mapper_name,
enc_config.encryption_password,
lock_after_create
lock_after_create,
iter_time=enc_config.iter_time,
)
enc_vols[vol] = luks_handler
@ -298,7 +286,7 @@ class FilesystemHandler:
def _encrypt_partitions(
self,
enc_config: DiskEncryption,
lock_after_create: bool = True
lock_after_create: bool = True,
) -> dict[PartitionModification, Luks2]:
enc_mods: dict[PartitionModification, Luks2] = {}
@ -318,7 +306,8 @@ class FilesystemHandler:
part_mod.safe_dev_path,
part_mod.mapper_name,
enc_config.encryption_password,
lock_after_create=lock_after_create
lock_after_create=lock_after_create,
iter_time=enc_config.iter_time,
)
enc_mods[part_mod] = luks_handler
@ -329,27 +318,10 @@ class FilesystemHandler:
# from arch wiki:
# If a logical volume will be formatted with ext4, leave at least 256 MiB
# free space in the volume group to allow using e2scrub
if any([vol.fs_type == FilesystemType.Ext4 for vol in vol_gp.volumes]):
if any([vol.fs_type == FilesystemType.EXT4 for vol in vol_gp.volumes]):
largest_vol = max(vol_gp.volumes, key=lambda x: x.length)
device_handler.lvm_vol_reduce(
lvm_vol_reduce(
largest_vol.safe_dev_path,
Size(256, Unit.MiB, SectorSize.default())
Size(256, Unit.MiB, SectorSize.default()),
)
def _final_warning(self, device_paths: str) -> bool:
# Issue a final warning before we continue with something un-revertable.
# We mention the drive one last time, and count from 5 to 0.
out = str(_(' ! Formatting {} in ')).format(device_paths)
Tui.print(out, row=0, endl='', clear_screen=True)
try:
countdown = '\n5...4...3...2...1'
for c in countdown:
Tui.print(c, row=0, endl='')
time.sleep(0.25)
except KeyboardInterrupt:
with Tui():
ask_abort()
return True

Some files were not shown because too many files have changed in this diff Show More