Merge master into colored-install-preview
This commit is contained in:
commit
6e9bdc36c2
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
- run: cat /etc/os-release
|
||||
- run: pacman-key --init
|
||||
- run: pacman --noconfirm -Sy archlinux-keyring
|
||||
- run: ./build_iso.sh
|
||||
- run: ./test_tooling/mkarchiso/build_iso.sh
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: Arch Live ISO
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -41,3 +41,7 @@ requirements.txt
|
|||
/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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
default_stages: ['pre-commit']
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.12
|
||||
rev: v0.15.14
|
||||
hooks:
|
||||
# fix unused imports and sort them
|
||||
- id: ruff
|
||||
|
|
@ -41,6 +41,7 @@ repos:
|
|||
additional_dependencies:
|
||||
- pydantic
|
||||
- pytest
|
||||
- hypothesis
|
||||
- cryptography
|
||||
- textual
|
||||
- repo: local
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import Firewall, FirewallConfiguration
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import FontsConfiguration
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import PowerManagement, PowerManagementConfiguration
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import TYPE_CHECKING, Self, override
|
||||
|
||||
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.output import info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from archinstall.default_profiles.profile import CustomSetting, DisplayServerTyp
|
|||
class NiriProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Niri',
|
||||
'niri',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Place cursor configuration here.
|
||||
// Example:
|
||||
// cursor {
|
||||
// xcursor-theme "Adwaita"
|
||||
// xcursor-size 24
|
||||
// }
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Place per-output configuration here.
|
||||
// Example:
|
||||
// output "DP-1" {
|
||||
// mode "2560x1440@165"
|
||||
// position x=0 y=0
|
||||
// scale 1
|
||||
// }
|
||||
|
|
@ -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"
|
||||
|
|
@ -37,6 +37,7 @@ class GreeterType(Enum):
|
|||
Ly = 'ly'
|
||||
CosmicSession = 'cosmic-greeter'
|
||||
PlasmaLoginManager = 'plasma-login-manager'
|
||||
GreetdDms = 'dms-greeter'
|
||||
|
||||
|
||||
class SelectResult(Enum):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import TYPE_CHECKING, Self, override
|
||||
|
||||
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.output import info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import urllib.error
|
|||
import urllib.parse
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
from enum import Enum, StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Any, Self
|
||||
from urllib.request import Request, urlopen
|
||||
|
|
@ -14,6 +14,7 @@ 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
|
||||
|
|
@ -28,13 +29,16 @@ 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.output import debug, error, logger, warn
|
||||
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
|
||||
|
|
@ -58,6 +62,8 @@ class Arguments:
|
|||
advanced: bool = False
|
||||
verbose: bool = False
|
||||
|
||||
command: SubCommand | None = None
|
||||
|
||||
|
||||
class ArchConfigType(StrEnum):
|
||||
VERSION = 'version'
|
||||
|
|
@ -365,13 +371,13 @@ class ArchConfig:
|
|||
class ArchConfigHandler:
|
||||
def __init__(self) -> None:
|
||||
self._parser: ArgumentParser = self._define_arguments()
|
||||
args: Arguments = self._parse_args()
|
||||
self._args = args
|
||||
self._add_sub_parsers()
|
||||
|
||||
self._args: Arguments = self._parse_args()
|
||||
config = self._parse_config()
|
||||
|
||||
try:
|
||||
self._config = ArchConfig.from_config(config, args)
|
||||
self._config = ArchConfig.from_config(config, self._args)
|
||||
self._config.version = get_version()
|
||||
except ValueError as err:
|
||||
warn(str(err))
|
||||
|
|
@ -397,8 +403,13 @@ class ArchConfigHandler:
|
|||
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',
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ 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.output import debug, info
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ 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.output import FormattedOutput
|
||||
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
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
|
|||
users: list[User] | None = item.value
|
||||
|
||||
if users:
|
||||
return FormattedOutput.as_table(users)
|
||||
return as_table(users)
|
||||
return None
|
||||
|
||||
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from typing import ClassVar, Self
|
|||
|
||||
from archinstall.lib.command import SysCommand, SysCommandWorker
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.output import error
|
||||
from archinstall.lib.log import error
|
||||
|
||||
|
||||
class Boot:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from types import TracebackType
|
|||
from typing import Any, Self, override
|
||||
|
||||
from archinstall.lib.exceptions import RequirementError, SysCallError
|
||||
from archinstall.lib.output import debug, error, logger
|
||||
from archinstall.lib.log import debug, error, logger
|
||||
from archinstall.lib.utils.encoding import clear_vt100_escape_codes
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ from pydantic import TypeAdapter
|
|||
|
||||
from archinstall.lib.args import ArchConfig, ArchConfigType
|
||||
from archinstall.lib.crypt import encrypt
|
||||
from archinstall.lib.log import debug, logger, warn
|
||||
from archinstall.lib.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.output import as_key_value_pair, debug, logger, warn
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
|
||||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
libcrypt = ctypes.CDLL('libcrypt.so')
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from archinstall.lib.disk.utils import (
|
|||
umount,
|
||||
)
|
||||
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,
|
||||
|
|
@ -35,7 +36,6 @@ from archinstall.lib.models.device import (
|
|||
_PartitionInfo,
|
||||
)
|
||||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.output import debug, error, info, log
|
||||
from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import override
|
|||
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
|
||||
|
|
@ -35,8 +36,8 @@ from archinstall.lib.models.device import (
|
|||
Unit,
|
||||
_DeviceInfo,
|
||||
)
|
||||
from archinstall.lib.output import FormattedOutput, debug
|
||||
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
|
||||
|
||||
|
|
@ -221,7 +222,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]):
|
|||
|
||||
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)
|
||||
|
|
@ -230,7 +231,7 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]):
|
|||
# create btrfs table
|
||||
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()
|
||||
|
|
@ -246,12 +247,12 @@ class DiskLayoutConfigurationMenu(AbstractSubMenu[DiskMenuConfig]):
|
|||
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)
|
||||
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)
|
||||
lvm_volumes = as_table(vol_gp.volumes)
|
||||
output += '\n\n{}:\n{}'.format(tr('Volumes'), lvm_volumes)
|
||||
|
||||
return output
|
||||
|
|
@ -302,7 +303,7 @@ async def select_devices(preset: list[BDevice] | None = []) -> list[BDevice] | N
|
|||
dev = device_handler.get_device(device.path)
|
||||
|
||||
if dev and dev.partition_infos:
|
||||
return FormattedOutput.as_table(dev.partition_infos)
|
||||
return as_table(dev.partition_infos)
|
||||
return None
|
||||
|
||||
if preset is None:
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ from archinstall.lib.models.device import (
|
|||
PartitionModification,
|
||||
)
|
||||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
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
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
def _prev_partitions(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
output = tr('Partitions to be encrypted') + '\n'
|
||||
output += FormattedOutput.as_table(item.value)
|
||||
output += as_table(item.value)
|
||||
return output.rstrip()
|
||||
|
||||
return None
|
||||
|
|
@ -207,7 +207,7 @@ class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
|||
def _prev_lvm_vols(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
output = tr('LVM volumes to be encrypted') + '\n'
|
||||
output += FormattedOutput.as_table(item.value)
|
||||
output += as_table(item.value)
|
||||
return output.rstrip()
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from typing import ClassVar
|
|||
|
||||
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.output import error, info
|
||||
from archinstall.lib.utils.encoding import clear_vt100_escape_codes_from_str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from archinstall.lib.disk.lvm import (
|
|||
lvm_vol_reduce,
|
||||
)
|
||||
from archinstall.lib.disk.utils import udev_sync
|
||||
from archinstall.lib.log import debug, info
|
||||
from archinstall.lib.models.device import (
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
|
|
@ -27,7 +28,6 @@ from archinstall.lib.models.device import (
|
|||
Size,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.lib.output import debug, info
|
||||
|
||||
|
||||
class FilesystemHandler:
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ from types import TracebackType
|
|||
from archinstall.lib.command import SysCommand, SysCommandWorker, run
|
||||
from archinstall.lib.disk.utils import get_lsblk_info, umount
|
||||
from archinstall.lib.exceptions import DiskError, SysCallError
|
||||
from archinstall.lib.log import debug, info
|
||||
from archinstall.lib.models.device import DEFAULT_ITER_TIME
|
||||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.output import debug, info
|
||||
from archinstall.lib.utils.util import generate_password
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Literal, overload
|
|||
from archinstall.lib.command import SysCommand, SysCommandWorker
|
||||
from archinstall.lib.disk.utils import udev_sync
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.device import (
|
||||
LvmGroupInfo,
|
||||
LvmPVInfo,
|
||||
|
|
@ -17,7 +18,6 @@ from archinstall.lib.models.device import (
|
|||
Size,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
|
||||
def _lvm_info(
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from archinstall.lib.models.device import (
|
|||
Size,
|
||||
Unit,
|
||||
)
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
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
|
||||
|
||||
|
|
@ -479,7 +479,7 @@ class PartitioningList(ListManager[DiskSegment]):
|
|||
sector_size = device_info.sector_size
|
||||
|
||||
text = tr('Selected free space segment on device {}:').format(device_info.path) + '\n\n'
|
||||
free_space_table = FormattedOutput.as_table([free_space])
|
||||
free_space_table = as_table([free_space])
|
||||
prompt = text + free_space_table + '\n'
|
||||
|
||||
max_sectors = free_space.length.format_size(Unit.sectors, sector_size)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from pydantic import BaseModel
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import DiskError, SysCallError
|
||||
from archinstall.lib.log import debug, info, warn
|
||||
from archinstall.lib.models.device import LsblkInfo
|
||||
from archinstall.lib.output import debug, info, warn
|
||||
|
||||
|
||||
class LsblkOutput(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from enum import Enum
|
||||
|
||||
from archinstall.lib.locale.utils import list_timezones
|
||||
from archinstall.lib.log import warn
|
||||
from archinstall.lib.menu.helpers import Confirmation, Input, Selection
|
||||
from archinstall.lib.output import warn
|
||||
from archinstall.lib.translationhandler import Language, tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ from archinstall.lib.models.packages import Repository
|
|||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.network.network_menu import select_network
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.packages.packages import list_available_packages, select_additional_packages
|
||||
from archinstall.lib.pacman.config import PacmanConfig
|
||||
from archinstall.lib.pacman.pacman_menu import PacmanMenu
|
||||
from archinstall.lib.translationhandler import Language, tr, translation_handler
|
||||
from archinstall.lib.utils.format import as_table
|
||||
from archinstall.tui.components import tui
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult
|
||||
|
||||
|
|
@ -299,7 +299,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
if item.value:
|
||||
network_config: NetworkConfiguration = item.value
|
||||
if network_config.type == NicType.MANUAL:
|
||||
output = FormattedOutput.as_table(network_config.nics)
|
||||
output = as_table(network_config.nics)
|
||||
else:
|
||||
output = f'{tr("Network configuration")}:\n{network_config.type.display_msg()}'
|
||||
|
||||
|
|
@ -321,7 +321,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
output += f'{tr("Root password")}: {auth_config.root_enc_password.hidden()}\n'
|
||||
|
||||
if auth_config.users:
|
||||
output += FormattedOutput.as_table(auth_config.users) + '\n'
|
||||
output += as_table(auth_config.users) + '\n'
|
||||
|
||||
if auth_config.u2f_config:
|
||||
u2f_config = auth_config.u2f_config
|
||||
|
|
@ -631,7 +631,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
if mirror_config.custom_repositories:
|
||||
title = tr('Custom repositories')
|
||||
table = FormattedOutput.as_table(mirror_config.custom_repositories)
|
||||
table = as_table(mirror_config.custom_repositories)
|
||||
output += f'{title}:\n\n{table}'
|
||||
|
||||
return output.strip()
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from typing import Self
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.networking import enrich_iface_types, list_interfaces
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from archinstall.lib.exceptions import DiskError, HardwareIncompatibilityError,
|
|||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.linux_path import LPath
|
||||
from archinstall.lib.locale.utils import verify_keyboard_layout, verify_x11_keyboard_layout
|
||||
from archinstall.lib.log import debug, error, info, log, logger, warn
|
||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||
from archinstall.lib.models.application import ZramAlgorithm
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
|
|
@ -52,7 +53,6 @@ from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel
|
|||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.output import debug, error, info, log, logger, warn
|
||||
from archinstall.lib.packages.packages import installed_package
|
||||
from archinstall.lib.pacman.config import PacmanConfig
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
|
|
@ -375,7 +375,12 @@ class Installer:
|
|||
# it would be none if it's btrfs as the subvolumes will have the mountpoints defined
|
||||
if part_mod.mountpoint:
|
||||
target = self.target / part_mod.relative_mountpoint
|
||||
mount(part_mod.dev_path, target, options=part_mod.mount_options)
|
||||
options = part_mod.mount_options
|
||||
|
||||
if part_mod.is_efi():
|
||||
options = list(dict.fromkeys(options + ['fmask=0077', 'dmask=0077']))
|
||||
|
||||
mount(part_mod.dev_path, target, options=options)
|
||||
elif part_mod.fs_type == FilesystemType.BTRFS:
|
||||
# Only mount BTRFS subvolumes that have mountpoints specified
|
||||
subvols_with_mountpoints = [sv for sv in part_mod.btrfs_subvols if sv.mountpoint is not None]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from pathlib import Path
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import ServiceException, SysCallError
|
||||
from archinstall.lib.output import error
|
||||
from archinstall.lib.log import error
|
||||
from archinstall.lib.utils.util import running_from_iso
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,259 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.utils.util import timestamp
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, path: Path | None = None) -> None:
|
||||
if path is None:
|
||||
path = Path('/var/log/archinstall')
|
||||
|
||||
self._path: Path = path
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return self._path / 'install.log'
|
||||
|
||||
@path.setter
|
||||
def path(self, value: Path) -> None:
|
||||
self._path = value
|
||||
|
||||
@property
|
||||
def directory(self) -> Path:
|
||||
return self._path
|
||||
|
||||
def _check_permissions(self) -> None:
|
||||
log_file = self.path
|
||||
|
||||
try:
|
||||
self._path.mkdir(exist_ok=True, parents=True)
|
||||
log_file.touch(exist_ok=True)
|
||||
|
||||
with log_file.open('a') as f:
|
||||
f.write('')
|
||||
except PermissionError:
|
||||
# Fallback to creating the log file in the current folder
|
||||
logger._path = Path('./').absolute()
|
||||
|
||||
warn(f'Not enough permission to place log file at {log_file}, creating it in {logger.path} instead')
|
||||
|
||||
def log(self, level: int, content: str) -> None:
|
||||
self._check_permissions()
|
||||
|
||||
with self.path.open('a') as f:
|
||||
ts = timestamp()
|
||||
level_name = logging.getLevelName(level)
|
||||
f.write(f'[{ts}] - {level_name} - {content}\n')
|
||||
|
||||
def get_content(self, max_bytes: int | None = None) -> bytes:
|
||||
content = self.path.read_bytes()
|
||||
|
||||
if max_bytes is not None:
|
||||
size = self.path.stat().st_size
|
||||
|
||||
if size > max_bytes:
|
||||
content = content[-max_bytes:]
|
||||
|
||||
return content
|
||||
|
||||
|
||||
logger = Logger()
|
||||
|
||||
|
||||
def _supports_color() -> bool:
|
||||
"""
|
||||
Found first reference here:
|
||||
https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python
|
||||
And re-used this:
|
||||
https://github.com/django/django/blob/master/django/core/management/color.py#L12
|
||||
|
||||
Return True if the running system's terminal supports color,
|
||||
and False otherwise.
|
||||
"""
|
||||
supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
|
||||
|
||||
# isatty is not always implemented, #6223.
|
||||
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
||||
return supported_platform and is_a_tty
|
||||
|
||||
|
||||
class Font(Enum):
|
||||
bold = '1'
|
||||
italic = '3'
|
||||
underscore = '4'
|
||||
blink = '5'
|
||||
reverse = '7'
|
||||
conceal = '8'
|
||||
|
||||
|
||||
def _stylize_output(
|
||||
text: str,
|
||||
fg: str,
|
||||
bg: str | None,
|
||||
reset: bool,
|
||||
font: list[Font] = [],
|
||||
) -> str:
|
||||
"""
|
||||
Heavily influenced by:
|
||||
https://github.com/django/django/blob/ae8338daf34fd746771e0678081999b656177bae/django/utils/termcolors.py#L13
|
||||
Color options here:
|
||||
https://askubuntu.com/questions/528928/how-to-do-underline-bold-italic-strikethrough-color-background-and-size-i
|
||||
|
||||
Adds styling to a text given a set of color arguments.
|
||||
"""
|
||||
colors = {
|
||||
'black': '0',
|
||||
'red': '1',
|
||||
'green': '2',
|
||||
'yellow': '3',
|
||||
'blue': '4',
|
||||
'magenta': '5',
|
||||
'cyan': '6',
|
||||
'white': '7',
|
||||
'teal': '8;5;109', # Extended 256-bit colors (not always supported)
|
||||
'orange': '8;5;208', # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
||||
'darkorange': '8;5;202',
|
||||
'gray': '8;5;246',
|
||||
'grey': '8;5;246',
|
||||
'darkgray': '8;5;240',
|
||||
'lightgray': '8;5;256',
|
||||
}
|
||||
|
||||
foreground = {key: f'3{colors[key]}' for key in colors}
|
||||
background = {key: f'4{colors[key]}' for key in colors}
|
||||
code_list = []
|
||||
|
||||
if text == '' and reset:
|
||||
return '\x1b[0m'
|
||||
|
||||
code_list.append(foreground[str(fg)])
|
||||
|
||||
if bg:
|
||||
code_list.append(background[str(bg)])
|
||||
|
||||
for o in font:
|
||||
code_list.append(o.value)
|
||||
|
||||
ansi = ';'.join(code_list)
|
||||
|
||||
return f'\033[{ansi}m{text}\033[0m'
|
||||
|
||||
|
||||
def journal_log(message: str, level: int = logging.DEBUG) -> None:
|
||||
try:
|
||||
import systemd.journal # type: ignore[import-not-found]
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_fmt = logging.Formatter('[%(levelname)s]: %(message)s')
|
||||
log_ch = systemd.journal.JournalHandler()
|
||||
log_ch.setFormatter(log_fmt)
|
||||
log_adapter.addHandler(log_ch)
|
||||
log_adapter.setLevel(logging.DEBUG)
|
||||
|
||||
log_adapter.log(level, message)
|
||||
|
||||
|
||||
def info(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def debug(
|
||||
*msgs: str,
|
||||
level: int = logging.DEBUG,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def error(
|
||||
*msgs: str,
|
||||
level: int = logging.ERROR,
|
||||
fg: str = 'red',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def warn(
|
||||
*msgs: str,
|
||||
level: int = logging.WARNING,
|
||||
fg: str = 'yellow',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def log(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
text = ' '.join(str(x) for x in msgs)
|
||||
|
||||
logger.log(level, text)
|
||||
|
||||
# Attempt to colorize the output if supported
|
||||
# Insert default colors and override with **kwargs
|
||||
if _supports_color():
|
||||
text = _stylize_output(text, fg, bg, reset, font)
|
||||
|
||||
journal_log(text, level=level)
|
||||
|
||||
if level != logging.DEBUG:
|
||||
print(text)
|
||||
|
||||
|
||||
def share_install_log(
|
||||
paste_url: str,
|
||||
max_bytes: int | None = None,
|
||||
) -> str | None:
|
||||
log_path = logger.path
|
||||
|
||||
if not log_path.exists():
|
||||
info(f'Log file not found: {log_path}')
|
||||
return None
|
||||
|
||||
content = logger.get_content(max_bytes=max_bytes)
|
||||
|
||||
if len(content) == 0:
|
||||
info(f'Log file is empty: {log_path}')
|
||||
return None
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(paste_url, data=content)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
url = response.read().decode().strip()
|
||||
except urllib.error.URLError as e:
|
||||
info(f'Upload failed: {e}')
|
||||
return None
|
||||
|
||||
if not url.startswith('http'):
|
||||
info(f'Unexpected response from {paste_url}: {url[:200]!r}')
|
||||
return None
|
||||
|
||||
return url
|
||||
|
|
@ -2,8 +2,8 @@ from enum import Enum
|
|||
from types import TracebackType
|
||||
from typing import Any, Self, override
|
||||
|
||||
from archinstall.lib.log import error
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.output import error
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.components import InstanceRunnable
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.utils.format import as_table
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ class MenuHelper[ValueT]:
|
|||
display_data: dict[str, ValueT | str | None] = {}
|
||||
|
||||
if data:
|
||||
table = FormattedOutput.as_table(data)
|
||||
table = as_table(data)
|
||||
rows = table.split('\n')
|
||||
|
||||
# these are the header rows of the table
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import time
|
|||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.log import debug, info
|
||||
from archinstall.lib.models import MirrorRegion
|
||||
from archinstall.lib.models.mirrors import MirrorStatusEntryV3, MirrorStatusListV3
|
||||
from archinstall.lib.networking import fetch_data_from_url
|
||||
from archinstall.lib.output import debug, info
|
||||
from archinstall.lib.pathnames import MIRRORLIST
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ from archinstall.lib.models.mirrors import (
|
|||
SignOption,
|
||||
)
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
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
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
|||
|
||||
async def _add_custom_repository(self, preset: CustomRepository | None = None) -> CustomRepository | None:
|
||||
edit_result = await Input(
|
||||
header=tr('Enter a respository name'),
|
||||
header=tr('Enter a repository name'),
|
||||
allow_skip=True,
|
||||
default_value=preset.name if preset else None,
|
||||
).show()
|
||||
|
|
@ -281,7 +281,7 @@ class MirrorMenu(AbstractSubMenu[MirrorConfiguration]):
|
|||
return None
|
||||
|
||||
custom_mirrors: list[CustomRepository] = item.value
|
||||
output = FormattedOutput.as_table(custom_mirrors)
|
||||
output = as_table(custom_mirrors)
|
||||
return output.strip()
|
||||
|
||||
def _prev_custom_servers(self, item: MenuItem) -> str | None:
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from dataclasses import dataclass
|
|||
from enum import Enum
|
||||
from typing import Any, Self, override
|
||||
|
||||
from archinstall.lib.log import warn
|
||||
from archinstall.lib.models.config import SubConfig
|
||||
from archinstall.lib.output import warn
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ from parted import Disk, Geometry, Partition
|
|||
from pydantic import BaseModel, Field, ValidationInfo, field_serializer, field_validator
|
||||
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.config import SubConfig
|
||||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
ENC_IDENTIFIER = 'ainst'
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ from typing import TYPE_CHECKING, Any, Self, TypedDict, override
|
|||
|
||||
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.config import SubConfig
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.networking import DownloadTimer, ping
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ from dataclasses import dataclass, field
|
|||
from enum import Enum
|
||||
from typing import NotRequired, Self, TypedDict, override
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.config import SubConfig
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ class NicType(Enum):
|
|||
ISO = 'iso'
|
||||
NM = 'nm'
|
||||
NM_IWD = 'nm_iwd'
|
||||
IWD = 'iwd'
|
||||
MANUAL = 'manual'
|
||||
|
||||
def display_msg(self) -> str:
|
||||
|
|
@ -22,6 +23,8 @@ class NicType(Enum):
|
|||
return tr('Use Network Manager (default backend)')
|
||||
case NicType.NM_IWD:
|
||||
return tr('Use Network Manager (iwd backend)')
|
||||
case NicType.IWD:
|
||||
return tr('Use standalone iwd')
|
||||
case NicType.MANUAL:
|
||||
return tr('Manual configuration')
|
||||
|
||||
|
|
@ -131,6 +134,10 @@ class NetworkConfiguration(SubConfig):
|
|||
return cls(NicType.ISO)
|
||||
case NicType.NM:
|
||||
return cls(NicType.NM)
|
||||
case NicType.NM_IWD:
|
||||
return cls(NicType.NM_IWD)
|
||||
case NicType.IWD:
|
||||
return cls(NicType.IWD)
|
||||
case NicType.MANUAL:
|
||||
nics_arg = config.get('nics', [])
|
||||
if nics_arg:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import textwrap
|
||||
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.network import NetworkConfiguration, NicType
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
|
|
@ -32,6 +34,13 @@ def install_network_config(
|
|||
_configure_nm_iwd(installation)
|
||||
installation.disable_service('iwd.service')
|
||||
|
||||
case NicType.IWD:
|
||||
installation.add_additional_packages(['iwd'])
|
||||
_configure_iwd_standalone(installation)
|
||||
installation.enable_service('iwd.service')
|
||||
installation.enable_service('systemd-networkd.service')
|
||||
installation.enable_service('systemd-resolved.service')
|
||||
|
||||
case NicType.MANUAL:
|
||||
for nic in network_config.nics:
|
||||
installation.configure_nic(nic)
|
||||
|
|
@ -45,3 +54,36 @@ def _configure_nm_iwd(installation: Installer) -> None:
|
|||
|
||||
iwd_backend_conf = nm_conf_dir / 'wifi_backend.conf'
|
||||
_ = iwd_backend_conf.write_text('[device]\nwifi.backend=iwd\n')
|
||||
|
||||
|
||||
def _configure_iwd_standalone(installation: Installer) -> None:
|
||||
# iwd manages wireless only; systemd-networkd handles wired DHCP.
|
||||
iwd_conf_dir = installation.target / 'etc/iwd'
|
||||
iwd_conf_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
main_conf = iwd_conf_dir / 'main.conf'
|
||||
main_conf_content = textwrap.dedent("""\
|
||||
[General]
|
||||
EnableNetworkConfiguration=true
|
||||
|
||||
[Network]
|
||||
NameResolvingService=systemd
|
||||
""")
|
||||
_ = main_conf.write_text(main_conf_content)
|
||||
|
||||
networkd_dir = installation.target / 'etc/systemd/network'
|
||||
networkd_dir.mkdir(parents=True, exist_ok=True)
|
||||
wired_conf = networkd_dir / '20-wired.network'
|
||||
wired_conf_content = textwrap.dedent("""\
|
||||
[Match]
|
||||
Type=ether
|
||||
Kind=!*
|
||||
|
||||
[Network]
|
||||
DHCP=yes
|
||||
""")
|
||||
_ = wired_conf.write_text(wired_conf_content)
|
||||
|
||||
resolv = installation.target / 'etc/resolv.conf'
|
||||
resolv.unlink(missing_ok=True)
|
||||
resolv.symlink_to('/run/systemd/resolve/stub-resolv.conf')
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@ async def select_network(preset: NetworkConfiguration | None) -> NetworkConfigur
|
|||
return NetworkConfiguration(NicType.NM)
|
||||
case NicType.NM_IWD:
|
||||
return NetworkConfiguration(NicType.NM_IWD)
|
||||
case NicType.IWD:
|
||||
return NetworkConfiguration(NicType.IWD)
|
||||
case NicType.MANUAL:
|
||||
preset_nics = preset.nics if preset else []
|
||||
nics = await ManualNetworkConfig(tr('Configure interfaces'), preset_nics).show()
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ from typing import assert_never, override
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.network import WifiConfiguredNetwork, WifiNetwork
|
||||
from archinstall.lib.network.wpa_supplicant import WpaSupplicantConfig
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.network import WifiNetwork
|
||||
from archinstall.lib.output import debug
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from urllib.parse import urlencode
|
|||
from urllib.request import urlopen
|
||||
|
||||
from archinstall.lib.exceptions import DownloadTimeout, SysCallError
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.log import debug, error, info
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,415 +0,0 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from collections.abc import Callable
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from datetime import UTC, datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust
|
||||
from archinstall.tui.rich import BaseRichTable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import DataclassInstance
|
||||
|
||||
|
||||
class FormattedOutput:
|
||||
@staticmethod
|
||||
def _get_values(
|
||||
o: DataclassInstance,
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument]
|
||||
filter_list: list[str] = [],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
the original values returned a dataclass as dict thru the call to some specific methods
|
||||
this version allows thru the parameter class_formatter to call a dynamically selected formatting method.
|
||||
Can transmit a filter list to the class_formatter,
|
||||
"""
|
||||
if class_formatter:
|
||||
# if invoked per reference it has to be a standard function or a classmethod.
|
||||
# A method of an instance does not make sense
|
||||
if callable(class_formatter):
|
||||
return class_formatter(o, filter_list)
|
||||
# if is invoked by name we restrict it to a method of the class. No need to mess more
|
||||
elif hasattr(o, class_formatter) and callable(getattr(o, class_formatter)):
|
||||
func = getattr(o, class_formatter)
|
||||
return func(filter_list)
|
||||
|
||||
raise ValueError('Unsupported formatting call')
|
||||
elif hasattr(o, 'table_data'):
|
||||
return o.table_data()
|
||||
elif hasattr(o, 'json'):
|
||||
return o.json()
|
||||
elif is_dataclass(o):
|
||||
return asdict(o)
|
||||
else:
|
||||
return o.__dict__ # type: ignore[unreachable]
|
||||
|
||||
@classmethod
|
||||
def as_table(
|
||||
cls,
|
||||
obj: list[Any],
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
||||
filter_list: list[str] = [],
|
||||
capitalize: bool = False,
|
||||
) -> str:
|
||||
"""variant of as_table (subtly different code) which has two additional parameters
|
||||
filter which is a list of fields which will be shown
|
||||
class_formatter a special method to format the outgoing data
|
||||
|
||||
A general comment, the format selected for the output (a string where every data record is separated by newline)
|
||||
is for compatibility with a print statement
|
||||
As_table_filter can be a drop in replacement for as_table
|
||||
"""
|
||||
raw_data = [cls._get_values(o, class_formatter, filter_list) for o in obj]
|
||||
|
||||
# determine the maximum column size
|
||||
column_width: dict[str, int] = {}
|
||||
for o in raw_data:
|
||||
for k, v in o.items():
|
||||
if not filter_list or k in filter_list:
|
||||
column_width.setdefault(k, 0)
|
||||
column_width[k] = max([column_width[k], len(str(v)), len(k)])
|
||||
|
||||
if not filter_list:
|
||||
filter_list = list(column_width.keys())
|
||||
|
||||
# create the header lines
|
||||
output = ''
|
||||
key_list = []
|
||||
for key in filter_list:
|
||||
width = column_width[key]
|
||||
key = key.replace('!', '').replace('_', ' ')
|
||||
|
||||
if capitalize:
|
||||
key = key.capitalize()
|
||||
|
||||
key_list.append(unicode_ljust(key, width))
|
||||
|
||||
output += ' | '.join(key_list) + '\n'
|
||||
output += '-' * len(output) + '\n'
|
||||
|
||||
# create the data lines
|
||||
for record in raw_data:
|
||||
obj_data = []
|
||||
for key in filter_list:
|
||||
width = column_width.get(key, len(key))
|
||||
value = record.get(key, '')
|
||||
|
||||
if '!' in key:
|
||||
value = '*' * len(value)
|
||||
|
||||
if isinstance(value, (int, float)) or (isinstance(value, str) and value.isnumeric()):
|
||||
obj_data.append(unicode_rjust(str(value), width))
|
||||
else:
|
||||
obj_data.append(unicode_ljust(str(value), width))
|
||||
|
||||
output += ' | '.join(obj_data) + '\n'
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def as_columns(entries: list[str], cols: int) -> str:
|
||||
"""
|
||||
Will format a list into a given number of columns
|
||||
"""
|
||||
chunks = []
|
||||
output = ''
|
||||
|
||||
for i in range(0, len(entries), cols):
|
||||
chunks.append(entries[i : i + cols])
|
||||
|
||||
for row in chunks:
|
||||
out_fmt = '{: <30} ' * len(row)
|
||||
output += out_fmt.format(*row) + '\n'
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def as_key_value_pair(
|
||||
entries: dict[str, str | list[str] | bool],
|
||||
ignore_empty: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Formats key-values as a Rich Table:
|
||||
key1 : value1
|
||||
key2 : value2
|
||||
...
|
||||
"""
|
||||
table = BaseRichTable()
|
||||
table.add_column('key', style='bold', no_wrap=True)
|
||||
table.add_column('value', style='white', max_width=70)
|
||||
|
||||
for label, value in entries.items():
|
||||
if ignore_empty and not value:
|
||||
continue
|
||||
|
||||
if isinstance(value, bool):
|
||||
value = 'Yes' if value else 'No'
|
||||
|
||||
if isinstance(value, list):
|
||||
value = '\n '.join(str(val) for val in value)
|
||||
|
||||
table.add_row(label.title(), f': {value}')
|
||||
|
||||
return table.stringify()
|
||||
|
||||
|
||||
class Journald:
|
||||
@staticmethod
|
||||
def log(message: str, level: int = logging.DEBUG) -> None:
|
||||
try:
|
||||
import systemd.journal # type: ignore[import-not-found]
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_fmt = logging.Formatter('[%(levelname)s]: %(message)s')
|
||||
log_ch = systemd.journal.JournalHandler()
|
||||
log_ch.setFormatter(log_fmt)
|
||||
log_adapter.addHandler(log_ch)
|
||||
log_adapter.setLevel(logging.DEBUG)
|
||||
|
||||
log_adapter.log(level, message)
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, path: Path = Path('/var/log/archinstall')) -> None:
|
||||
self._path = path
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return self._path / 'install.log'
|
||||
|
||||
@property
|
||||
def directory(self) -> Path:
|
||||
return self._path
|
||||
|
||||
def _check_permissions(self) -> None:
|
||||
log_file = self.path
|
||||
|
||||
try:
|
||||
self._path.mkdir(exist_ok=True, parents=True)
|
||||
log_file.touch(exist_ok=True)
|
||||
|
||||
with log_file.open('a') as f:
|
||||
f.write('')
|
||||
except PermissionError:
|
||||
# Fallback to creating the log file in the current folder
|
||||
logger._path = Path('./').absolute()
|
||||
|
||||
warn(f'Not enough permission to place log file at {log_file}, creating it in {logger.path} instead')
|
||||
|
||||
def log(self, level: int, content: str) -> None:
|
||||
self._check_permissions()
|
||||
|
||||
with self.path.open('a') as f:
|
||||
ts = _timestamp()
|
||||
level_name = logging.getLevelName(level)
|
||||
f.write(f'[{ts}] - {level_name} - {content}\n')
|
||||
|
||||
|
||||
logger = Logger()
|
||||
|
||||
|
||||
def _supports_color() -> bool:
|
||||
"""
|
||||
Found first reference here:
|
||||
https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python
|
||||
And re-used this:
|
||||
https://github.com/django/django/blob/master/django/core/management/color.py#L12
|
||||
|
||||
Return True if the running system's terminal supports color,
|
||||
and False otherwise.
|
||||
"""
|
||||
supported_platform = sys.platform != 'win32' or 'ANSICON' in os.environ
|
||||
|
||||
# isatty is not always implemented, #6223.
|
||||
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
||||
return supported_platform and is_a_tty
|
||||
|
||||
|
||||
class Font(Enum):
|
||||
bold = '1'
|
||||
italic = '3'
|
||||
underscore = '4'
|
||||
blink = '5'
|
||||
reverse = '7'
|
||||
conceal = '8'
|
||||
|
||||
|
||||
def _stylize_output(
|
||||
text: str,
|
||||
fg: str,
|
||||
bg: str | None,
|
||||
reset: bool,
|
||||
font: list[Font] = [],
|
||||
) -> str:
|
||||
"""
|
||||
Heavily influenced by:
|
||||
https://github.com/django/django/blob/ae8338daf34fd746771e0678081999b656177bae/django/utils/termcolors.py#L13
|
||||
Color options here:
|
||||
https://askubuntu.com/questions/528928/how-to-do-underline-bold-italic-strikethrough-color-background-and-size-i
|
||||
|
||||
Adds styling to a text given a set of color arguments.
|
||||
"""
|
||||
colors = {
|
||||
'black': '0',
|
||||
'red': '1',
|
||||
'green': '2',
|
||||
'yellow': '3',
|
||||
'blue': '4',
|
||||
'magenta': '5',
|
||||
'cyan': '6',
|
||||
'white': '7',
|
||||
'teal': '8;5;109', # Extended 256-bit colors (not always supported)
|
||||
'orange': '8;5;208', # https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
|
||||
'darkorange': '8;5;202',
|
||||
'gray': '8;5;246',
|
||||
'grey': '8;5;246',
|
||||
'darkgray': '8;5;240',
|
||||
'lightgray': '8;5;256',
|
||||
}
|
||||
|
||||
foreground = {key: f'3{colors[key]}' for key in colors}
|
||||
background = {key: f'4{colors[key]}' for key in colors}
|
||||
code_list = []
|
||||
|
||||
if text == '' and reset:
|
||||
return '\x1b[0m'
|
||||
|
||||
code_list.append(foreground[str(fg)])
|
||||
|
||||
if bg:
|
||||
code_list.append(background[str(bg)])
|
||||
|
||||
for o in font:
|
||||
code_list.append(o.value)
|
||||
|
||||
ansi = ';'.join(code_list)
|
||||
|
||||
return f'\033[{ansi}m{text}\033[0m'
|
||||
|
||||
|
||||
def info(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def _timestamp() -> str:
|
||||
now = datetime.now(tz=UTC)
|
||||
return now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def debug(
|
||||
*msgs: str,
|
||||
level: int = logging.DEBUG,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def error(
|
||||
*msgs: str,
|
||||
level: int = logging.ERROR,
|
||||
fg: str = 'red',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def warn(
|
||||
*msgs: str,
|
||||
level: int = logging.WARNING,
|
||||
fg: str = 'yellow',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
log(*msgs, level=level, fg=fg, bg=bg, reset=reset, font=font)
|
||||
|
||||
|
||||
def log(
|
||||
*msgs: str,
|
||||
level: int = logging.INFO,
|
||||
fg: str = 'white',
|
||||
bg: str | None = None,
|
||||
reset: bool = False,
|
||||
font: list[Font] = [],
|
||||
) -> None:
|
||||
text = ' '.join(str(x) for x in msgs)
|
||||
|
||||
logger.log(level, text)
|
||||
|
||||
# Attempt to colorize the output if supported
|
||||
# Insert default colors and override with **kwargs
|
||||
if _supports_color():
|
||||
text = _stylize_output(text, fg, bg, reset, font)
|
||||
|
||||
Journald.log(text, level=level)
|
||||
|
||||
if level != logging.DEBUG:
|
||||
print(text)
|
||||
|
||||
|
||||
def share_install_log(
|
||||
paste_url: str = 'https://paste.rs',
|
||||
max_size: int = 10 * 1024 * 1024,
|
||||
confirm: Callable[[str], bool] = lambda _: True,
|
||||
) -> int:
|
||||
log_path = logger.path
|
||||
|
||||
if not log_path.exists():
|
||||
info(f'Log file not found: {log_path}')
|
||||
return 1
|
||||
|
||||
size = log_path.stat().st_size
|
||||
if size == 0:
|
||||
info(f'Log file is empty: {log_path}')
|
||||
return 1
|
||||
|
||||
if size > max_size:
|
||||
info(f'Log file exceeds {max_size} bytes, uploading last {max_size} bytes')
|
||||
content = log_path.read_bytes()[-max_size:]
|
||||
else:
|
||||
content = log_path.read_bytes()
|
||||
|
||||
header = f'About to upload {log_path} ({len(content)} bytes) to {paste_url}\n\n'
|
||||
header += 'The log may contain hostname, mirror URLs, package list and partition layout.\n'
|
||||
header += 'The uploaded paste is public.\n\n'
|
||||
header += 'Continue?'
|
||||
|
||||
if not confirm(header):
|
||||
info('Cancelled.')
|
||||
return 1
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(paste_url, data=content)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
url = response.read().decode().strip()
|
||||
except urllib.error.URLError as e:
|
||||
info(f'Upload failed: {e}')
|
||||
return 1
|
||||
|
||||
if not url.startswith('http'):
|
||||
info(f'Unexpected response from {paste_url}: {url[:200]!r}')
|
||||
return 1
|
||||
|
||||
# raw print so the URL is pipe-friendly (no ANSI colors, no log prefix)
|
||||
print(url)
|
||||
return 0
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
from functools import lru_cache
|
||||
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.menu.helpers import Loading, Notify, Selection
|
||||
from archinstall.lib.models.packages import AvailablePackage, LocalPackage, PackageGroup, Repository
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from functools import lru_cache
|
||||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.packages.packages import check_package_upgrade
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import RequirementError
|
||||
from archinstall.lib.output import error, info, warn
|
||||
from archinstall.lib.log import error, info, warn
|
||||
from archinstall.lib.pathnames import PACMAN_CONF
|
||||
from archinstall.lib.plugins import plugins
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import urllib.request
|
|||
from importlib import metadata
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.output import error, info, warn
|
||||
from archinstall.lib.log import error, info, warn
|
||||
from archinstall.lib.version import get_version
|
||||
|
||||
plugins = {}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ from typing import TYPE_CHECKING, NotRequired, TypedDict
|
|||
|
||||
from archinstall.default_profiles.profile import CustomSetting, GreeterType, Profile
|
||||
from archinstall.lib.hardware import GfxDriver, GfxPackage
|
||||
from archinstall.lib.log import debug, error, info
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.networking import fetch_data_from_url
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -175,6 +175,9 @@ class ProfileHandler:
|
|||
case GreeterType.PlasmaLoginManager:
|
||||
packages = ['plasma-login-manager']
|
||||
service = ['plasmalogin']
|
||||
case GreeterType.GreetdDms:
|
||||
packages = ['greetd']
|
||||
service = ['greetd']
|
||||
|
||||
if packages:
|
||||
install_session.add_additional_packages(packages)
|
||||
|
|
@ -194,6 +197,26 @@ class ProfileHandler:
|
|||
with open(path, 'w') as file:
|
||||
file.write(filedata)
|
||||
|
||||
if greeter == GreeterType.GreetdDms:
|
||||
greetd_config = install_session.target / 'etc/greetd/config.toml'
|
||||
greetd_config.parent.mkdir(parents=True, exist_ok=True)
|
||||
greetd_config.write_text(
|
||||
'[terminal]\n'
|
||||
'vt = 1\n'
|
||||
'\n'
|
||||
'[default_session]\n'
|
||||
'user = "greeter"\n'
|
||||
'command = "/usr/share/quickshell/dms/Modules/Greetd/assets/dms-greeter --command niri -p /usr/share/quickshell/dms"\n',
|
||||
)
|
||||
|
||||
tmpfiles = install_session.target / 'etc/tmpfiles.d/dms-greeter.conf'
|
||||
tmpfiles.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmpfiles.write_text(
|
||||
'# Path Mode User Group Age Argument\n'
|
||||
'd /var/cache/dms-greeter 0750 greeter greeter -\n'
|
||||
'd /var/lib/greeter 0755 greeter greeter -\n',
|
||||
)
|
||||
|
||||
def install_gfx_driver(self, install_session: Installer, driver: GfxDriver) -> None:
|
||||
debug(f'Installing GFX driver: {driver.value}')
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from typing import override
|
|||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.utils.util import running_from_iso
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
from collections.abc import Callable
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from archinstall.lib.utils.encoding import unicode_ljust, unicode_rjust
|
||||
from archinstall.tui.rich import BaseRichTable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import DataclassInstance
|
||||
|
||||
|
||||
def as_key_value_pair(
|
||||
entries: dict[str, str | list[str] | bool],
|
||||
ignore_empty: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Formats key-values as a Rich Table:
|
||||
key1 : value1
|
||||
key2 : value2
|
||||
...
|
||||
"""
|
||||
table = BaseRichTable()
|
||||
table.add_column('key', style='bold', no_wrap=True)
|
||||
table.add_column('value', style='white', max_width=70)
|
||||
|
||||
for label, value in entries.items():
|
||||
if ignore_empty and not value:
|
||||
continue
|
||||
|
||||
if isinstance(value, bool):
|
||||
value = 'Yes' if value else 'No'
|
||||
|
||||
if isinstance(value, list):
|
||||
value = '\n '.join(str(val) for val in value)
|
||||
|
||||
table.add_row(label.title(), f': {value}')
|
||||
|
||||
return table.stringify()
|
||||
|
||||
|
||||
def as_columns(entries: list[str], cols: int) -> str:
|
||||
"""
|
||||
Will format a list into a given number of columns
|
||||
"""
|
||||
chunks: list[list[str]] = []
|
||||
output = ''
|
||||
|
||||
for i in range(0, len(entries), cols):
|
||||
chunks.append(entries[i : i + cols])
|
||||
|
||||
for row in chunks:
|
||||
out_fmt = '{: <30} ' * len(row)
|
||||
output += out_fmt.format(*row) + '\n'
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _get_values(
|
||||
o: DataclassInstance,
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg] # pyright: ignore[reportMissingTypeArgument]
|
||||
filter_list: list[str] = [],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
the original values returned a dataclass as dict thru the call to some specific methods
|
||||
this version allows thru the parameter class_formatter to call a dynamically selected formatting method.
|
||||
Can transmit a filter list to the class_formatter,
|
||||
"""
|
||||
if class_formatter:
|
||||
# if invoked per reference it has to be a standard function or a classmethod.
|
||||
# A method of an instance does not make sense
|
||||
if callable(class_formatter):
|
||||
return class_formatter(o, filter_list)
|
||||
# if is invoked by name we restrict it to a method of the class. No need to mess more
|
||||
elif hasattr(o, class_formatter) and callable(getattr(o, class_formatter)):
|
||||
func = getattr(o, class_formatter)
|
||||
return func(filter_list)
|
||||
|
||||
raise ValueError('Unsupported formatting call')
|
||||
elif hasattr(o, 'table_data'):
|
||||
return o.table_data()
|
||||
elif hasattr(o, 'json'):
|
||||
return o.json()
|
||||
elif is_dataclass(o):
|
||||
return asdict(o)
|
||||
else:
|
||||
return o.__dict__ # type: ignore[unreachable]
|
||||
|
||||
|
||||
def as_table(
|
||||
obj: list[Any],
|
||||
class_formatter: str | Callable | None = None, # type: ignore[type-arg]
|
||||
filter_list: list[str] = [],
|
||||
capitalize: bool = False,
|
||||
) -> str:
|
||||
"""variant of as_table (subtly different code) which has two additional parameters
|
||||
filter which is a list of fields which will be shown
|
||||
class_formatter a special method to format the outgoing data
|
||||
|
||||
A general comment, the format selected for the output (a string where every data record is separated by newline)
|
||||
is for compatibility with a print statement
|
||||
As_table_filter can be a drop in replacement for as_table
|
||||
"""
|
||||
raw_data = [_get_values(o, class_formatter, filter_list) for o in obj]
|
||||
|
||||
# determine the maximum column size
|
||||
column_width: dict[str, int] = {}
|
||||
for o in raw_data:
|
||||
for k, v in o.items():
|
||||
if not filter_list or k in filter_list:
|
||||
column_width.setdefault(k, 0)
|
||||
column_width[k] = max([column_width[k], len(str(v)), len(k)])
|
||||
|
||||
if not filter_list:
|
||||
filter_list = list(column_width.keys())
|
||||
|
||||
# create the header lines
|
||||
output = ''
|
||||
key_list = []
|
||||
for key in filter_list:
|
||||
width = column_width[key]
|
||||
key = key.replace('!', '').replace('_', ' ')
|
||||
|
||||
if capitalize:
|
||||
key = key.capitalize()
|
||||
|
||||
key_list.append(unicode_ljust(key, width))
|
||||
|
||||
output += ' | '.join(key_list) + '\n'
|
||||
output += '-' * len(output) + '\n'
|
||||
|
||||
# create the data lines
|
||||
for record in raw_data:
|
||||
obj_data = []
|
||||
for key in filter_list:
|
||||
width = column_width.get(key, len(key))
|
||||
value = record.get(key, '')
|
||||
|
||||
if '!' in key:
|
||||
value = '*' * len(value)
|
||||
|
||||
if isinstance(value, (int, float)) or (isinstance(value, str) and value.isnumeric()):
|
||||
obj_data.append(unicode_rjust(str(value), width))
|
||||
else:
|
||||
obj_data.append(unicode_ljust(str(value), width))
|
||||
|
||||
output += ' | '.join(obj_data) + '\n'
|
||||
|
||||
return output
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
import secrets
|
||||
import string
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.pathnames import ARCHISO_MOUNTPOINT
|
||||
from archinstall.lib.utils.format import as_columns
|
||||
|
||||
|
||||
def timestamp() -> str:
|
||||
now = datetime.now(tz=UTC)
|
||||
return now.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def running_from_iso() -> bool:
|
||||
|
|
@ -36,7 +42,7 @@ def format_cols(items: list[str], header: str | None = None) -> str:
|
|||
else:
|
||||
col = 4
|
||||
|
||||
text += FormattedOutput.as_columns(items, col)
|
||||
text += as_columns(items, col)
|
||||
# remove whitespaces on each row
|
||||
text = '\n'.join(t.strip() for t in text.split('\n'))
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -1245,7 +1245,7 @@ msgid "Product"
|
|||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Invalid configuration: {error}"
|
||||
msgid "Invalid configuration: {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ready to install"
|
||||
|
|
@ -2304,3 +2304,162 @@ msgstr ""
|
|||
#, python-brace-format
|
||||
msgid "Choose an option how to give {} access to your hardware"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "About to upload \"{}\" to the publicly accessible {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Do you want to continue?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Log uploaded successfully. URL: {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to upload log."
|
||||
msgstr ""
|
||||
|
||||
msgid "ArchInstall Language"
|
||||
msgstr ""
|
||||
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation Script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
msgid "Services"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom commands"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Root encrypted password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Zram enabled"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Zram algorithm {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bluetooth enabled"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Audio server \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Power management \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Print service enabled"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Firewall \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Extra fonts \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Root password set"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Configured {} user(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "U2F set up"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Bootloader \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "UKI enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pre-mount"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} layout"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Devices {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "LVM set up"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} encryption"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Btrfs snapshot \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Keyboard layout \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Locale language \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Locale encoding \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Console font \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Mirror regions \"{}\""
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Optional repositories \"{}\""
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom servers set up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom repositories set up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Use standalone iwd"
|
||||
msgstr ""
|
||||
|
||||
msgid "Color enabled"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} grphics driver"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} greeter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter a repository name"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -2190,7 +2190,7 @@ msgstr "Valittu työpöydän profiili vaati tavallisen käyttäjän kirjautumise
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {} {}"
|
||||
msgstr "Ympäristötyyppi: {}"
|
||||
msgstr "Ympäristötyyppi: {} {}"
|
||||
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "Syöttö ei voi olla tyhjä"
|
||||
|
|
@ -2245,4 +2245,4 @@ msgstr "Määritetään U2F laitetta käyttäjälle: {}"
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr "Oletusarvo: 10000ms, suositeltu 1000-60000ms"
|
||||
msgstr "Oletusarvo: {}ms, suositeltu 1000-60000ms"
|
||||
|
|
|
|||
|
|
@ -1502,9 +1502,6 @@ msgstr "सक्षम"
|
|||
msgid "Disabled"
|
||||
msgstr "अक्षम"
|
||||
|
||||
msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues"
|
||||
msgstr "कृपया इस समस्या (और फ़ाइल) को https://github.com/archlinux/archinstall/issues पर सबमिट करें।"
|
||||
|
||||
msgid "Mirror name"
|
||||
msgstr "मिरर का नाम"
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2026-04-12 10:26+0200\n"
|
||||
"PO-Revision-Date: 2026-05-16 12:52+0200\n"
|
||||
"Last-Translator: Van Matten\n"
|
||||
"Language-Team: Alessio Cuccovillo <alessio.cuccovillo.dev@gmail.com>, Van Matten\n"
|
||||
"Language: it\n"
|
||||
|
|
@ -56,7 +56,7 @@ msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr
|
|||
msgstr "Vengono installati solo pacchetti come base, base-devel, linux, linux-firmware, efibootmgr e pacchetti di profilo opzionali."
|
||||
|
||||
msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools."
|
||||
msgstr "Nota: base-devel non è più installato di default. Aggiungilo qui se ti servono i build tools."
|
||||
msgstr "Nota: base-devel non è più installato come predefinito. Aggiungilo qui se ti servono i build tools."
|
||||
|
||||
msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt."
|
||||
msgstr "Se desideri un browser web, come Firefox o Chromium, puoi specificarlo nel seguente prompt."
|
||||
|
|
@ -118,7 +118,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona per indice quali partizioni eliminare"
|
||||
"Seleziona per indice le partizioni da eliminare"
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -127,13 +127,13 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona per indice quale partizione montare dove"
|
||||
"Seleziona per indice la partizione dove montare"
|
||||
|
||||
msgid " * Partition mount-points are relative to inside the installation, the boot would be /boot as an example."
|
||||
msgstr " * I punti di mount della partizione sono relativi all'interno dell'installazione, per esempio l'avvio sarebbe /boot."
|
||||
msgstr " * I punti di montaggio della partizione sono relativi all'interno dell'installazione, per esempio l'avvio sarebbe /boot."
|
||||
|
||||
msgid "Select where to mount partition (leave blank to remove mountpoint): "
|
||||
msgstr "Seleziona dove montare la partizione (lascia vuoto per rimuovere il punto di mount): "
|
||||
msgstr "Seleziona dove montare la partizione (lascia vuoto per rimuovere il punto di montaggio): "
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -142,7 +142,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona quale partizione mascherare per la formattazione"
|
||||
"Seleziona la partizione da mascherare per la formattazione"
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -151,7 +151,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona quale partizione contrassegnare come crittografata"
|
||||
"Seleziona la partizione da contrassegnare come crittografata"
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -160,7 +160,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona quale partizione contrassegnare come avviabile"
|
||||
"Seleziona la partizione da contrassegnare come avviabile"
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -169,7 +169,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona su quale partizione impostare un filesystem"
|
||||
"Seleziona la partizione su cui impostare un filesystem"
|
||||
|
||||
msgid "Enter a desired filesystem type for the partition: "
|
||||
msgstr "Inserisci un tipo di filesystem desiderato per la partizione: "
|
||||
|
|
@ -187,7 +187,7 @@ msgid "Select what you wish to do with the selected block devices"
|
|||
msgstr "Seleziona cosa desideri fare con i dispositivi a blocchi selezionati"
|
||||
|
||||
msgid "This is a list of pre-programmed profiles, they might make it easier to install things like desktop environments"
|
||||
msgstr "Questo è un elenco di profili pre-programmati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop"
|
||||
msgstr "Questo è un elenco di profili preprogrammati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop"
|
||||
|
||||
msgid "Select keyboard layout"
|
||||
msgstr "Seleziona il layout della tastiera"
|
||||
|
|
@ -196,7 +196,7 @@ msgid "Select one of the regions to download packages from"
|
|||
msgstr "Seleziona una delle regioni da cui scaricare i pacchetti"
|
||||
|
||||
msgid "Select one or more hard drives to use and configure"
|
||||
msgstr "Seleziona uno o più dischi rigidi da utilizzare e configurare"
|
||||
msgstr "Seleziona uno o più unità da utilizzare e configurare"
|
||||
|
||||
msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options."
|
||||
msgstr "Per la migliore compatibilità con il tuo hardware AMD, potresti voler utilizzare tutte le opzioni open source o AMD / ATI."
|
||||
|
|
@ -220,13 +220,13 @@ msgid "All open-source (default)"
|
|||
msgstr "Tutti gli open source (predefinito)"
|
||||
|
||||
msgid "Choose which kernels to use or leave blank for default \"{}\""
|
||||
msgstr "Scegli quali kernel usare o lascia vuoto per il predefinito \"{}\""
|
||||
msgstr "Scegli i kernel da usare o lascia vuoto per il predefinito \"{}\""
|
||||
|
||||
msgid "Choose which locale language to use"
|
||||
msgstr "Scegli quale lingua locale utilizzare"
|
||||
msgstr "Scegli la lingua da usare"
|
||||
|
||||
msgid "Choose which locale encoding to use"
|
||||
msgstr "Scegli quale codifica locale utilizzare"
|
||||
msgstr "Scegli la codifica da usare"
|
||||
|
||||
msgid "Select one of the values shown below: "
|
||||
msgstr "Seleziona uno dei valori mostrati di seguito: "
|
||||
|
|
@ -235,7 +235,7 @@ msgid "Select one or more of the options below: "
|
|||
msgstr "Seleziona una o più delle seguenti opzioni "
|
||||
|
||||
msgid "Adding partition...."
|
||||
msgstr "Aggiungendo la partizione...."
|
||||
msgstr "Aggiunta della partizione in corso..."
|
||||
|
||||
msgid "You need to enter a valid fs-type in order to continue. See `man parted` for valid fs-type's."
|
||||
msgstr "Devi inserire un tipo di filesystem valido per continuare. Vedi `man parted` per tipi di filesystem validi."
|
||||
|
|
@ -253,13 +253,16 @@ msgid "Mirror region"
|
|||
msgstr "Regione dei mirror"
|
||||
|
||||
msgid "Locale language"
|
||||
msgstr "Lingua locale"
|
||||
msgstr "Lingua"
|
||||
|
||||
msgid "Locale encoding"
|
||||
msgstr "Codifica locale"
|
||||
msgstr "Codifica"
|
||||
|
||||
msgid "Console font"
|
||||
msgstr "Font della console"
|
||||
|
||||
msgid "Drive(s)"
|
||||
msgstr "Dischi"
|
||||
msgstr "Unità"
|
||||
|
||||
msgid "Disk layout"
|
||||
msgstr "Layout del disco"
|
||||
|
|
@ -301,7 +304,7 @@ msgid "Automatic time sync (NTP)"
|
|||
msgstr "Sincronizzazione automatica dell'ora (NTP)"
|
||||
|
||||
msgid "Install ({} config(s) missing)"
|
||||
msgstr "Installa ({} configurazioni mancanti)"
|
||||
msgstr "Installa ({} configurazione/i mancante/i)"
|
||||
|
||||
msgid ""
|
||||
"You decided to skip harddrive selection\n"
|
||||
|
|
@ -309,7 +312,7 @@ msgid ""
|
|||
"WARNING: Archinstall won't check the suitability of this setup\n"
|
||||
"Do you wish to continue?"
|
||||
msgstr ""
|
||||
"Hai deciso di saltare la selezione del disco rigido\n"
|
||||
"Hai deciso di saltare la selezione dell'unità\n"
|
||||
"e utilizzerà qualsiasi configurazione dell'unità montata su {} (sperimentale)\n"
|
||||
"ATTENZIONE: Archinstall non verificherà l'idoneità di questa configurazione\n"
|
||||
"Vuoi continuare?"
|
||||
|
|
@ -327,7 +330,7 @@ msgid "Clear/Delete all partitions"
|
|||
msgstr "Cancella/Elimina tutte le partizioni"
|
||||
|
||||
msgid "Assign mount-point for a partition"
|
||||
msgstr "Assegna punto di mount per una partizione"
|
||||
msgstr "Assegna punto di montaggio per una partizione"
|
||||
|
||||
msgid "Mark/Unmark a partition to be formatted (wipes data)"
|
||||
msgstr "Seleziona/Deseleziona una partizione da formattare (cancella i dati)"
|
||||
|
|
@ -376,7 +379,7 @@ msgid "Enter a encryption password for {}"
|
|||
msgstr "Inserisci una password di crittografia per {}"
|
||||
|
||||
msgid "Enter disk encryption password (leave blank for no encryption): "
|
||||
msgstr "Inserisci la password di crittografia del disco (lasciare vuoto per nessuna crittografia): "
|
||||
msgstr "Inserisci la password di crittografia del disco (lascia vuoto per nessuna crittografia): "
|
||||
|
||||
msgid "Create a required super-user with sudo privileges: "
|
||||
msgstr "Crea un superuser richiesto con privilegi sudo: "
|
||||
|
|
@ -454,7 +457,7 @@ msgid "Pre-existing pacman lock never exited. Please clean up any existing pacma
|
|||
msgstr "Il lock di pacman preesistente non è mai terminato. Rimuovi ogni sessione pacman esistente prima di usare archinstall."
|
||||
|
||||
msgid "Choose which optional additional repositories to enable"
|
||||
msgstr "Scegli quali repository aggiuntivi facoltativi abilitare"
|
||||
msgstr "Scegli i repository aggiuntivi facoltativi da abilitare"
|
||||
|
||||
msgid "Add a user"
|
||||
msgstr "Aggiungi un utente"
|
||||
|
|
@ -476,7 +479,7 @@ msgstr ""
|
|||
"Definisci un nuovo utente\n"
|
||||
|
||||
msgid "User Name : "
|
||||
msgstr "Nome utente : "
|
||||
msgstr "Nome utente: "
|
||||
|
||||
msgid "Should {} be a superuser (sudoer)?"
|
||||
msgstr "{} dovrebbe essere un superuser (sudoer)?"
|
||||
|
|
@ -497,7 +500,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"{}\n"
|
||||
"\n"
|
||||
"Seleziona su quale partizione impostare i sottovolumi"
|
||||
"Seleziona la partizione su cui impostare i sottovolumi"
|
||||
|
||||
msgid "Manage btrfs subvolumes for current partition"
|
||||
msgstr "Gestisci i sottovolumi btrfs per la partizione corrente"
|
||||
|
|
@ -518,7 +521,7 @@ msgid "Save all"
|
|||
msgstr "Salva tutto"
|
||||
|
||||
msgid "Choose which configuration to save"
|
||||
msgstr "Scegli quale configurazione salvare"
|
||||
msgstr "Scegli la configurazione da salvare"
|
||||
|
||||
msgid "Enter a directory for the configuration(s) to be saved: "
|
||||
msgstr "Inserisci una cartella in cui salvare le configurazioni: "
|
||||
|
|
@ -570,7 +573,7 @@ msgid "Subvolume name "
|
|||
msgstr "Nome del sottovolume "
|
||||
|
||||
msgid "Subvolume mountpoint"
|
||||
msgstr "Punto di mount del sottovolume"
|
||||
msgstr "Punto di montaggio del sottovolume"
|
||||
|
||||
msgid "Subvolume options"
|
||||
msgstr "Opzioni del sottovolume"
|
||||
|
|
@ -579,10 +582,10 @@ msgid "Save"
|
|||
msgstr "Salva"
|
||||
|
||||
msgid "Subvolume name :"
|
||||
msgstr "Nome del sottovolume :"
|
||||
msgstr "Nome del sottovolume:"
|
||||
|
||||
msgid "Select a mount point :"
|
||||
msgstr "Seleziona un punto di mount:"
|
||||
msgstr "Seleziona un punto di montaggio:"
|
||||
|
||||
msgid "Select the desired subvolume options "
|
||||
msgstr "Seleziona le opzioni del sottovolume desiderate "
|
||||
|
|
@ -607,10 +610,10 @@ msgid "The selected drives do not have the minimum capacity required for an auto
|
|||
msgstr "Le unità selezionate non hanno la capacità minima richiesta per un suggerimento automatico\n"
|
||||
|
||||
msgid "Minimum capacity for /home partition: {}GB\n"
|
||||
msgstr "Capacità minima per la partizione /home: {}GB\n"
|
||||
msgstr "Capacità minima per la partizione /home: {} GB\n"
|
||||
|
||||
msgid "Minimum capacity for Arch Linux partition: {}GB"
|
||||
msgstr "Capacità minima per la partizione Arch Linux: {}GB"
|
||||
msgstr "Capacità minima per la partizione Arch Linux: {} GB"
|
||||
|
||||
msgid "Continue"
|
||||
msgstr "Continua"
|
||||
|
|
@ -661,13 +664,13 @@ msgid "Select your desired desktop environment"
|
|||
msgstr "Seleziona l'ambiente desktop desiderato"
|
||||
|
||||
msgid "A very basic installation that allows you to customize Arch Linux as you see fit."
|
||||
msgstr "Un'installazione molto semplice che ti consente di personalizzare Arch Linux come meglio credi."
|
||||
msgstr "Un'installazione molto semplificata che ti consente di personalizzare Arch Linux come meglio credi."
|
||||
|
||||
msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb"
|
||||
msgstr "Fornisce una selezione di vari pacchetti server da installare e abilitare, per esempio httpd, nginx, mariadb"
|
||||
|
||||
msgid "Choose which servers to install, if none then a minimal installation will be done"
|
||||
msgstr "Scegli quali server installare, se nessuno verrà eseguita un'installazione minima"
|
||||
msgstr "Scegli i server da installare, se non ne sarà selezionato nessuno, verrà eseguita un'installazione minima"
|
||||
|
||||
msgid "Installs a minimal system as well as xorg and graphics drivers."
|
||||
msgstr "Installa un sistema minimo oltre a xorg e driver grafici."
|
||||
|
|
@ -682,13 +685,13 @@ msgid "Are you sure you want to reset this setting?"
|
|||
msgstr "Sei sicuro di voler ripristinare questa impostazione?"
|
||||
|
||||
msgid "Select one or more hard drives to use and configure\n"
|
||||
msgstr "Seleziona uno o più dischi rigidi da utilizzare e configurare\n"
|
||||
msgstr "Seleziona uno o più unità da utilizzare e configurare\n"
|
||||
|
||||
msgid "Any modifications to the existing setting will reset the disk layout!"
|
||||
msgstr "Qualsiasi modifica all'impostazione esistente ripristinerà il layout del disco!"
|
||||
|
||||
msgid "If you reset the harddrive selection this will also reset the current disk layout. Are you sure?"
|
||||
msgstr "Se si ripristina la selezione del disco rigido, verrà ripristinato anche il layout del disco corrente. Sei sicuro?"
|
||||
msgstr "Se ripristini la selezione dell'unità, verrà ripristinato anche il layout del disco corrente. Sei sicuro?"
|
||||
|
||||
msgid "Save and exit"
|
||||
msgstr "Salva ed esci"
|
||||
|
|
@ -741,7 +744,7 @@ msgid "Select one of the disks or skip and use /mnt as default"
|
|||
msgstr "Seleziona uno dei dischi o salta e usa /mnt come predefinito"
|
||||
|
||||
msgid "Select which partitions to mark for formatting:"
|
||||
msgstr "Seleziona quali partizioni contrassegnare per la formattazione:"
|
||||
msgstr "Seleziona le partizioni da contrassegnare per la formattazione:"
|
||||
|
||||
msgid "Use HSM to unlock encrypted drive"
|
||||
msgstr "Utilizza HSM per sbloccare l'unità crittografata"
|
||||
|
|
@ -798,14 +801,14 @@ msgid "Configured {} interfaces"
|
|||
msgstr "Interfacce {} configurate"
|
||||
|
||||
msgid "This option enables the number of parallel downloads that can occur during installation"
|
||||
msgstr "Questa opzione consente di impostare il numero di download paralleli che possono avvenire durante l'installazione"
|
||||
msgstr "Questa opzione consente di impostare il numero di download in parallelo che possono avvenire durante l'installazione"
|
||||
|
||||
msgid ""
|
||||
"Enter the number of parallel downloads to be enabled.\n"
|
||||
" (Enter a value between 1 to {})\n"
|
||||
"Note:"
|
||||
msgstr ""
|
||||
"Inserisci il numero di download paralleli da abilitare.\n"
|
||||
"Inserisci il numero di download in parallelo da abilitare.\n"
|
||||
" (Inserisci un valore compreso tra 1 e {})\n"
|
||||
"Nota:"
|
||||
|
||||
|
|
@ -816,20 +819,33 @@ msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads a
|
|||
msgstr " - Valore minimo : 1 ( Consente 1 download parallelo, consente 2 download alla volta )"
|
||||
|
||||
msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )"
|
||||
msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download parallelo, consente solo 1 download alla volta )"
|
||||
msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download in parallelo, consente solo 1 download alla volta )"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]"
|
||||
msgstr "Input non valido! Riprova con un input valido [da 1 a {max_downloads}, o 0 per disabilitare]."
|
||||
|
||||
msgid "Parallel Downloads"
|
||||
msgstr "Download paralleli"
|
||||
msgstr "Download in parallelo"
|
||||
|
||||
msgid "Pacman"
|
||||
msgstr "Pacman"
|
||||
|
||||
msgid "Color"
|
||||
msgstr "Colore"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Enter the number of parallel downloads (1-{})"
|
||||
msgstr "Inserisci il numero di download in parallelo (1-{})"
|
||||
|
||||
msgid "Enable colored output for pacman"
|
||||
msgstr "Attiva l'output colorato di pacman"
|
||||
|
||||
msgid "ESC to skip"
|
||||
msgstr "ESC per saltare"
|
||||
|
||||
msgid "CTRL+C to reset"
|
||||
msgstr "CTRL+C per resettare"
|
||||
msgstr "CTRL+C per ripristinare"
|
||||
|
||||
msgid "TAB to select"
|
||||
msgstr "TAB per selezionare"
|
||||
|
|
@ -861,27 +877,27 @@ msgid "Select one or more devices to use and configure"
|
|||
msgstr "Seleziona uno o più dispositivi da utilizzare e configurare"
|
||||
|
||||
msgid "If you reset the device selection this will also reset the current disk layout. Are you sure?"
|
||||
msgstr "Se si ripristina la selezione del disco rigido, verrà ripristinato anche il layout del disco corrente. Sei sicuro?"
|
||||
msgstr "Se ripristini la selezione del dispositivo, verrà ripristinato anche il layout del disco corrente. Sei sicuro?"
|
||||
|
||||
msgid "Existing Partitions"
|
||||
msgstr "Partizioni esistenti"
|
||||
|
||||
msgid "Select a partitioning option"
|
||||
msgstr "Selezione opzione di partizionamento"
|
||||
msgstr "Seleziona un'opzione di partizionamento"
|
||||
|
||||
msgid "Enter the root directory of the mounted devices: "
|
||||
msgstr "Inserisci la cartella principale dei dispositivi montati: "
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Minimum capacity for /home partition: {}GiB\n"
|
||||
msgstr "Capacità minima per la partizione /home: {}GiB\n"
|
||||
msgstr "Capacità minima per la partizione /home: {} GiB\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Minimum capacity for Arch Linux partition: {}GiB"
|
||||
msgstr "Capacità minima per la partizione Arch Linux: {}GiB"
|
||||
msgstr "Capacità minima per la partizione Arch Linux: {} GiB"
|
||||
|
||||
msgid "This is a list of pre-programmed profiles_bck, they might make it easier to install things like desktop environments"
|
||||
msgstr "Questo è un elenco di profili pre-programmati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop"
|
||||
msgstr "Questo è un elenco di profiles_bck preprogrammati, che potrebbero semplificare l'installazione di elementi come gli ambienti desktop"
|
||||
|
||||
msgid "Current profile selection"
|
||||
msgstr "Selezione profilo corrente"
|
||||
|
|
@ -890,7 +906,7 @@ msgid "Remove all newly added partitions"
|
|||
msgstr "Elimina tutte le partizioni appena aggiunte"
|
||||
|
||||
msgid "Assign mountpoint"
|
||||
msgstr "Assegna punto di mount"
|
||||
msgstr "Assegna punto di montaggio"
|
||||
|
||||
msgid "Mark/Unmark to be formatted (wipes data)"
|
||||
msgstr "Seleziona/Deseleziona come da formattare (cancella i dati)"
|
||||
|
|
@ -917,13 +933,13 @@ msgid "This partition is currently encrypted, to format it a filesystem has to b
|
|||
msgstr "Questa partizione è attualmente crittografata, per formattarla è necessario specificare un filesystem"
|
||||
|
||||
msgid "Partition mount-points are relative to inside the installation, the boot would be /boot as an example."
|
||||
msgstr "I punti di mount della partizione sono relativi all'interno dell'installazione, l'avvio sarebbe per esempio /boot."
|
||||
msgstr "I punti di montaggio della partizione sono relativi all'interno dell'installazione, l'avvio sarebbe per esempio /boot."
|
||||
|
||||
msgid "If mountpoint /boot is set, then the partition will also be marked as bootable."
|
||||
msgstr "Se il punto di mount /boot è impostato, anche la partizione sarà contrassegnata come avviabile."
|
||||
msgstr "Se il punto di montaggio /boot è impostato, anche la partizione sarà contrassegnata come avviabile."
|
||||
|
||||
msgid "Mountpoint: "
|
||||
msgstr "Punto di mount: "
|
||||
msgstr "Punto di montaggio: "
|
||||
|
||||
msgid "Current free sectors on device {}:"
|
||||
msgstr "Settori attualmente liberi sul dispositivo {}:"
|
||||
|
|
@ -985,7 +1001,7 @@ msgid "Partitions to be encrypted"
|
|||
msgstr "Partizioni da crittografare"
|
||||
|
||||
msgid "Select disk encryption option"
|
||||
msgstr "Selezione opzione per crittografia disco"
|
||||
msgstr "Seleziona un'opzione per la crittografia del disco"
|
||||
|
||||
msgid "Select a FIDO2 device to use for HSM"
|
||||
msgstr "Seleziona un dispositivo FIDO2 da utilizzare per HSM"
|
||||
|
|
@ -1028,7 +1044,7 @@ msgid "Back"
|
|||
msgstr "Indietro"
|
||||
|
||||
msgid "Please chose which greeter to install for the chosen profiles: {}"
|
||||
msgstr "Scegli quale greeter installare per i profili scelti: {}"
|
||||
msgstr "Scegli il greeter da installare per i profili scelti: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {}"
|
||||
|
|
@ -1075,7 +1091,7 @@ msgstr ""
|
|||
"Seleziona un driver grafico o lascia vuoto per installare tutti i driver open source"
|
||||
|
||||
msgid "Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "Sway ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
msgstr "Sway richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
|
||||
msgid ""
|
||||
"\n"
|
||||
|
|
@ -1093,13 +1109,13 @@ msgid "Greeter"
|
|||
msgstr "Greeter"
|
||||
|
||||
msgid "Please chose which greeter to install"
|
||||
msgstr "Scegli quale greeter installare"
|
||||
msgstr "Scegli il greeter da installare"
|
||||
|
||||
msgid "This is a list of pre-programmed default_profiles"
|
||||
msgstr "Questo è un elenco di default_profiles preprogrammati"
|
||||
|
||||
msgid "Disk configuration"
|
||||
msgstr "Configurazione disco"
|
||||
msgstr "Configurazione del disco"
|
||||
|
||||
msgid "Profiles"
|
||||
msgstr "Profili"
|
||||
|
|
@ -1144,7 +1160,7 @@ msgid ""
|
|||
"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n"
|
||||
"Save directory: "
|
||||
msgstr ""
|
||||
"Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab abilitato)\n"
|
||||
"Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab attivo)\n"
|
||||
"Cartella di salvataggio: "
|
||||
|
||||
msgid ""
|
||||
|
|
@ -1166,7 +1182,7 @@ msgid "Mirror regions"
|
|||
msgstr "Regione dei mirror"
|
||||
|
||||
msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )"
|
||||
msgstr " - Valore massimo : {} ( Consente {} download paralleli, consente {max_downloads+1} downloads alla volta )"
|
||||
msgstr " - Valore massimo : {} ( Consente {} download in parallelo, consente {max_downloads+1} download alla volta )"
|
||||
|
||||
msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]"
|
||||
msgstr "Input non valido! Riprova con un input valido [da 1 a {}, o 0 per disabilitare]."
|
||||
|
|
@ -1208,33 +1224,48 @@ msgstr "Prodotto"
|
|||
msgid "Invalid configuration: {error}"
|
||||
msgstr "Configurazione non valida: {error}"
|
||||
|
||||
msgid "Ready to install"
|
||||
msgstr "Pronto per l'installazione"
|
||||
|
||||
msgid "Disks"
|
||||
msgstr "Dischi"
|
||||
|
||||
msgid "Packages"
|
||||
msgstr "Pacchetti"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Rete"
|
||||
|
||||
msgid "Locale"
|
||||
msgstr "Localizzazione"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
msgid "This option enables the number of parallel downloads that can occur during package downloads"
|
||||
msgstr "Questa opzione consente di impostare il numero di download paralleli che possono avvenire durante l'installazione"
|
||||
msgstr "Questa opzione consente di impostare il numero di download in parallelo che possono avvenire durante l'installazione"
|
||||
|
||||
msgid ""
|
||||
"Enter the number of parallel downloads to be enabled.\n"
|
||||
"\n"
|
||||
"Note:\n"
|
||||
msgstr ""
|
||||
"Inserisci il numero di download paralleli da abilitare.\n"
|
||||
"Inserisci il numero di download in parallelo da abilitare.\n"
|
||||
"\n"
|
||||
"Nota:\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )"
|
||||
msgstr " - Valore massimo raccomandato : {} ( Consente {} download paralleli)"
|
||||
msgstr " - Valore massimo raccomandato : {} ( Consente {} download in parallelo)"
|
||||
|
||||
msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n"
|
||||
msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download parallelo, consente solo 1 download alla volta )\n"
|
||||
msgstr " - Disabilita/Predefinito : 0 ( Disabilita il download in parallelo, consente solo 1 download alla volta )\n"
|
||||
|
||||
msgid "Invalid input! Try again with a valid input [or 0 to disable]"
|
||||
msgstr "Input non valido! Riprova con un input valido [0 per disabilitare]."
|
||||
|
||||
msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "Hyprland ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
msgstr "Hyprland richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
|
||||
msgid ""
|
||||
"\n"
|
||||
|
|
@ -1270,7 +1301,7 @@ msgid "Selected profiles: "
|
|||
msgstr "Profili selezionati: "
|
||||
|
||||
msgid "Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/"
|
||||
msgstr "La sincronizzazione dell’orario non si sta completando, in attesa, leggi la documentazione alla ricerca di una soluzione: https://archinstall.readthedocs.io/"
|
||||
msgstr "La sincronizzazione dell’orario non si sta completando, mentre aspetti leggi la documentazione alla ricerca di una soluzione: https://archinstall.readthedocs.io/"
|
||||
|
||||
msgid "Mark/Unmark as nodatacow"
|
||||
msgstr "Seleziona/Deseleziona come nodatacow"
|
||||
|
|
@ -1322,7 +1353,7 @@ msgid "LVM volumes to be encrypted"
|
|||
msgstr "Volumi LVM da crittografare"
|
||||
|
||||
msgid "Select which LVM volumes to encrypt"
|
||||
msgstr "Seleziona volumi LVM da crittografare"
|
||||
msgstr "Seleziona i volumi LVM da crittografare"
|
||||
|
||||
msgid "Default layout"
|
||||
msgstr "Layout predefinito"
|
||||
|
|
@ -1361,7 +1392,7 @@ msgid "Seat access"
|
|||
msgstr "Accesso al posto"
|
||||
|
||||
msgid "Mountpoint"
|
||||
msgstr "Punto di mount"
|
||||
msgstr "Punto di montaggio"
|
||||
|
||||
msgid "HSM"
|
||||
msgstr "HSM"
|
||||
|
|
@ -1479,7 +1510,7 @@ msgid "Directory"
|
|||
msgstr "Cartella"
|
||||
|
||||
msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)"
|
||||
msgstr "Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab abilitato)"
|
||||
msgstr "Inserisci una cartella in cui salvare le configurazioni (completamento col tasto tab attivo)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Do you want to save the configuration file(s) to {}?"
|
||||
|
|
@ -1513,7 +1544,7 @@ msgid "Choose an option to give Hyprland access to your hardware"
|
|||
msgstr "Scegli un’opzione per concedere a Hyprland l'accesso al tuo hardware"
|
||||
|
||||
msgid "Additional repositories"
|
||||
msgstr "Repository aggiuntive"
|
||||
msgstr "Repository aggiuntivi"
|
||||
|
||||
msgid "NTP"
|
||||
msgstr "NTP"
|
||||
|
|
@ -1543,7 +1574,7 @@ msgid "HSM device"
|
|||
msgstr "Dispositivo HSM"
|
||||
|
||||
msgid "Some packages could not be found in the repository"
|
||||
msgstr "Alcuni pacchetti non sono stati trovati nella repository"
|
||||
msgstr "Alcuni pacchetti non sono stati trovati nel repository"
|
||||
|
||||
msgid "User"
|
||||
msgstr "Utente"
|
||||
|
|
@ -1564,16 +1595,16 @@ msgid "Select any packages from the below list that should be installed addition
|
|||
msgstr "Seleziona dalla lista sotto i pacchetti aggiuntivi da installare"
|
||||
|
||||
msgid "Add a custom repository"
|
||||
msgstr "Aggiungi una repository personalizzata"
|
||||
msgstr "Aggiungi un repository personalizzato"
|
||||
|
||||
msgid "Change custom repository"
|
||||
msgstr "Cambia repository personalizzata"
|
||||
msgstr "Cambia repository personalizzato"
|
||||
|
||||
msgid "Delete custom repository"
|
||||
msgstr "Elimina repository personalizzata"
|
||||
msgstr "Elimina repository personalizzato"
|
||||
|
||||
msgid "Repository name"
|
||||
msgstr "Nome repository"
|
||||
msgstr "Nome del repository"
|
||||
|
||||
msgid "Add a custom server"
|
||||
msgstr "Aggiungi un server personalizzato"
|
||||
|
|
@ -1594,7 +1625,7 @@ msgid "Add custom servers"
|
|||
msgstr "Aggiungi server personalizzati"
|
||||
|
||||
msgid "Add custom repository"
|
||||
msgstr "Aggiungi repository personalizzate"
|
||||
msgstr "Aggiungi repository personalizzato"
|
||||
|
||||
msgid "Loading mirror regions..."
|
||||
msgstr "Caricamento regioni dei mirror in corso..."
|
||||
|
|
@ -1609,7 +1640,7 @@ msgid "Custom servers"
|
|||
msgstr "Server personalizzati"
|
||||
|
||||
msgid "Custom repositories"
|
||||
msgstr "Repository personalizzate"
|
||||
msgstr "Repository personalizzati"
|
||||
|
||||
msgid "Only ASCII characters are supported"
|
||||
msgstr "Sono supportati solo caratteri ASCII"
|
||||
|
|
@ -1756,7 +1787,7 @@ msgid "Toggle option"
|
|||
msgstr "Opzioni di attivazione"
|
||||
|
||||
msgid "Scroll Up"
|
||||
msgstr "Scorri Su"
|
||||
msgstr "Scorri su"
|
||||
|
||||
msgid "Scroll Down"
|
||||
msgstr "Scorri giù"
|
||||
|
|
@ -1783,13 +1814,13 @@ msgid "Copy selected text"
|
|||
msgstr "Copia il testo selezionato"
|
||||
|
||||
msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "labwc ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
msgstr "labwc richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
|
||||
msgid "Choose an option to give labwc access to your hardware"
|
||||
msgstr "Scegli un’opzione per concedere a labwc l’accesso al tuo hardware"
|
||||
|
||||
msgid "niri needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "niri ha bisogno dell’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
msgstr "niri richiede l’accesso al tuo posto (insieme di dispositivi hardware, ad esempio tastiera, mouse, ecc.)"
|
||||
|
||||
msgid "Choose an option to give niri access to your hardware"
|
||||
msgstr "Scegli un’opzione per concedere a niri l’accesso al tuo hardware"
|
||||
|
|
@ -1832,7 +1863,7 @@ msgid "Do you want to encrypt the user_credentials.json file?"
|
|||
msgstr "Vuoi criptare il file di user_credentials.json?"
|
||||
|
||||
msgid "Credentials file encryption password"
|
||||
msgstr "Password di crittazione del file delle credenziali"
|
||||
msgstr "Password di crittografia del file delle credenziali"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Repositories: {}"
|
||||
|
|
@ -1917,7 +1948,7 @@ msgid "No network connection found"
|
|||
msgstr "Nessuna connessione di rete trovata"
|
||||
|
||||
msgid "Would you like to connect to a Wifi?"
|
||||
msgstr "Desideri connetterti ad una Wi-FI?"
|
||||
msgstr "Desideri connetterti ad una Wi-Fi?"
|
||||
|
||||
msgid "No wifi interface found"
|
||||
msgstr "Nessuna interfaccia Wi-Fi trovata"
|
||||
|
|
@ -2000,11 +2031,26 @@ msgstr "Usa NetworkManager (backend iwd)"
|
|||
msgid "Firewall"
|
||||
msgstr "Firewall"
|
||||
|
||||
msgid "Additional fonts"
|
||||
msgstr "Font aggiuntivi"
|
||||
|
||||
msgid "Select font packages to install"
|
||||
msgstr "Seleziona i pacchetti di font da installare"
|
||||
|
||||
msgid "Unicode font coverage for most languages"
|
||||
msgstr "Copertura Unicode del font per la maggior parte delle lingue"
|
||||
|
||||
msgid "color emoji for browsers and apps"
|
||||
msgstr "emoji a colori per browser e app"
|
||||
|
||||
msgid "Chinese, Japanese, Korean characters"
|
||||
msgstr "Caratteri cinesi, giapponesi, coreani"
|
||||
|
||||
msgid "Select audio configuration"
|
||||
msgstr "Seleziona la configurazione audio"
|
||||
|
||||
msgid "Enter credentials file decryption password"
|
||||
msgstr "Inserici la password di decrittazione del file delle credenziali"
|
||||
msgstr "Inserisci la password di decrittazione del file delle credenziali"
|
||||
|
||||
msgid "Enter root password"
|
||||
msgstr "Inserisci la password di root"
|
||||
|
|
@ -2025,7 +2071,7 @@ msgid "Select disks for the installation"
|
|||
msgstr "Seleziona il disco per l'installazione"
|
||||
|
||||
msgid "Enter a mountpoint"
|
||||
msgstr "Inserisci un punto di mount"
|
||||
msgstr "Inserisci un punto di montaggio"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Enter a size (default: {}): "
|
||||
|
|
@ -2035,7 +2081,7 @@ msgid "Enter subvolume name"
|
|||
msgstr "Inserisci il nome del sottovolume"
|
||||
|
||||
msgid "Enter subvolume mountpoint"
|
||||
msgstr "Inserisci il punto di mount del sottovolume"
|
||||
msgstr "Inserisci il punto di montaggio del sottovolume"
|
||||
|
||||
msgid "Select a disk configuration"
|
||||
msgstr "Seleziona una configurazione del disco"
|
||||
|
|
@ -2047,7 +2093,7 @@ msgid "You will use whatever drive-setup is mounted at the specified directory"
|
|||
msgstr "Userai qualunque configurazione del disco che sia montata nella cartella specificata"
|
||||
|
||||
msgid "WARNING: Archinstall won't check the suitability of this setup"
|
||||
msgstr "ATTENZIONE: Archinstall non verificherà la compatibilità di di questa configurazione"
|
||||
msgstr "ATTENZIONE: Archinstall non verificherà la compatibilità di questa configurazione"
|
||||
|
||||
msgid "Select main filesystem"
|
||||
msgstr "Seleziona il filesystem principale"
|
||||
|
|
@ -2066,22 +2112,22 @@ msgid "Value must be between 1 and {}"
|
|||
msgstr "Il valore deve essere tra 1 e {}"
|
||||
|
||||
msgid "Select which kernel(s) to install"
|
||||
msgstr "Scegli quali kernel installare"
|
||||
msgstr "Seleziona i kernel da installare"
|
||||
|
||||
msgid "Enter a respository name"
|
||||
msgstr "Inserisci il nome di un repository"
|
||||
|
||||
msgid "Enter the repository url"
|
||||
msgstr "Inserisci l'URL della repository"
|
||||
msgstr "Inserisci l'URL del repository"
|
||||
|
||||
msgid "Enter server url"
|
||||
msgstr "Inserisci l'URL del server"
|
||||
|
||||
msgid "Select mirror regions to be enabled"
|
||||
msgstr "Seleziona quali regioni dei mirror abilitare"
|
||||
msgstr "Seleziona le regioni dei mirror da abilitare"
|
||||
|
||||
msgid "Select optional repositories to be enabled"
|
||||
msgstr "Seleziona quali repository aggiuntive opzionali abilitare"
|
||||
msgstr "Seleziona i repository opzionali da abilitare"
|
||||
|
||||
msgid "Select an interface"
|
||||
msgstr "Seleziona un'interfaccia"
|
||||
|
|
@ -2089,11 +2135,17 @@ msgstr "Seleziona un'interfaccia"
|
|||
msgid "Choose network configuration"
|
||||
msgstr "Scegli la configurazione di rete"
|
||||
|
||||
msgid "Recommended: Network Manager for desktop, Manual for server"
|
||||
msgstr "Raccomandato: Network Manager per computer, Manuale per server"
|
||||
|
||||
msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system."
|
||||
msgstr "Attenzione: nessuna configurazione di rete selezionata. La rete dovrà essere impostata manualmente sul sistema installato."
|
||||
|
||||
msgid "No packages found"
|
||||
msgstr "Nessun pacchetto trovato"
|
||||
|
||||
msgid "Select which greeter to install"
|
||||
msgstr "Seleziona quale greeter installare"
|
||||
msgstr "Seleziona il greeter da installare"
|
||||
|
||||
msgid "Select a profile type"
|
||||
msgstr "Seleziona un tipo di profilo"
|
||||
|
|
@ -2126,6 +2178,9 @@ msgstr "Il profilo desktop selezionato richiede un utente regolare per accedere
|
|||
msgid "Environment type: {} {}"
|
||||
msgstr "Tipo di ambiente: {} {}"
|
||||
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "Il campo non può essere vuoto"
|
||||
|
||||
msgid "Recommended"
|
||||
msgstr "Raccomandato"
|
||||
|
||||
|
|
@ -2156,5 +2211,194 @@ msgstr "Descrizione"
|
|||
msgid "Select a flavor of KDE Plasma to install"
|
||||
msgstr "Seleziona la configurazione di KDE Plasma da installare"
|
||||
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "Il campo non può essere vuoto"
|
||||
msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games"
|
||||
msgstr "Sostituto di Arial/Times/Courier, supporta il cirillico per Steam/giochi"
|
||||
|
||||
msgid "wide Unicode coverage, good fallback font"
|
||||
msgstr "ampia copertura Unicode, ottimo font di riserva"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}"
|
||||
msgstr "Impostazione accesso U2F: {u2f_config.u2f_login_method.value}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000"
|
||||
msgstr "Predefinito: {DEFAULT_ITER_TIME} ms, Intervallo consigliato: 1000-60000"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {}"
|
||||
msgstr "Impostazione accesso U2F: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr "Predefinito: {} ms, Intervallo consigliato: 1000-60000"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} needs access to your seat"
|
||||
msgstr "{} richiede l’accesso al tuo posto"
|
||||
|
||||
msgid "collection of hardware devices i.e. keyboard, mouse"
|
||||
msgstr "insieme di dispositivi hardware, ad esempio tastiera e mouse"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Choose an option how to give {} access to your hardware"
|
||||
msgstr "Scegli un’opzione per concedere a {} l’accesso al tuo hardware"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "About to upload \"{}\" to the publicly accessible {}"
|
||||
msgstr "\"{}\" sta per essere caricato per essere pubblicamente accessibile {}"
|
||||
|
||||
msgid "Do you want to continue?"
|
||||
msgstr "Vuoi continuare?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Log uploaded successfully. URL: {}"
|
||||
msgstr "Log caricato con successo. URL: {}"
|
||||
|
||||
msgid "Failed to upload log."
|
||||
msgstr "Impossibile caricare il log."
|
||||
|
||||
msgid "ArchInstall Language"
|
||||
msgstr "Lingua di ArchInstall"
|
||||
|
||||
msgid "Version"
|
||||
msgstr "Versione"
|
||||
|
||||
msgid "Installation Script"
|
||||
msgstr "Script di installazione"
|
||||
|
||||
msgid "Application"
|
||||
msgstr "Applicazione"
|
||||
|
||||
msgid "Services"
|
||||
msgstr "Servizi"
|
||||
|
||||
msgid "Custom commands"
|
||||
msgstr "Comandi personalizzati"
|
||||
|
||||
msgid "Users"
|
||||
msgstr "Utenti"
|
||||
|
||||
msgid "Root encrypted password"
|
||||
msgstr "Password di root crittografata"
|
||||
|
||||
msgid "Zram enabled"
|
||||
msgstr "Zram attiva"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Zram algorithm {}"
|
||||
msgstr "Algoritmo Zram {}"
|
||||
|
||||
msgid "Bluetooth enabled"
|
||||
msgstr "Bluetooth attivo"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Audio server \"{}\""
|
||||
msgstr "Server audio \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Power management \"{}\""
|
||||
msgstr "Gestione energetica \"{}\""
|
||||
|
||||
msgid "Print service enabled"
|
||||
msgstr "Servizio di stampa attivo"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Firewall \"{}\""
|
||||
msgstr "Firewall \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Extra fonts \"{}\""
|
||||
msgstr "Font aggiuntivi \"{}\""
|
||||
|
||||
msgid "Root password set"
|
||||
msgstr "Imposta la password di root"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Configured {} user(s)"
|
||||
msgstr "{} utente/i configurato/i"
|
||||
|
||||
msgid "U2F set up"
|
||||
msgstr "Imposta U2F"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Bootloader \"{}\""
|
||||
msgstr "Bootloader \"{}\""
|
||||
|
||||
msgid "UKI enabled"
|
||||
msgstr "UKI attiva"
|
||||
|
||||
msgid "Default"
|
||||
msgstr "Predefinito"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Manuale"
|
||||
|
||||
msgid "Pre-mount"
|
||||
msgstr "Premontaggio"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} layout"
|
||||
msgstr "Layout {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Devices {}"
|
||||
msgstr "Dispositivi {}"
|
||||
|
||||
msgid "LVM set up"
|
||||
msgstr "Imposta LVM"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} encryption"
|
||||
msgstr "Crittografia {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Btrfs snapshot \"{}\""
|
||||
msgstr "Snapshot Btrfs \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Keyboard layout \"{}\""
|
||||
msgstr "Layout della tastiera \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Locale language \"{}\""
|
||||
msgstr "Lingua \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Locale encoding \"{}\""
|
||||
msgstr "Codifica \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Console font \"{}\""
|
||||
msgstr "Font della console \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Mirror regions \"{}\""
|
||||
msgstr "Regioni dei mirror \"{}\""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Optional repositories \"{}\""
|
||||
msgstr "Repository opzionali \"{}\""
|
||||
|
||||
msgid "Custom servers set up"
|
||||
msgstr "Imposta server personalizzati"
|
||||
|
||||
msgid "Custom repositories set up"
|
||||
msgstr "Imposta repository personalizzati"
|
||||
|
||||
msgid "Use standalone iwd"
|
||||
msgstr "Usa iwd standalone"
|
||||
|
||||
msgid "Color enabled"
|
||||
msgstr "Colore attivo"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} grphics driver"
|
||||
msgstr "Driver grafici {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{} greeter"
|
||||
msgstr "Greeter {}"
|
||||
|
||||
msgid "Enter a repository name"
|
||||
msgstr "Inserisci il nome di un repository"
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -1,48 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd $(dirname "$0")/..
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
function update_lang() {
|
||||
file=${1}
|
||||
usage() {
|
||||
echo "Usage: ${0} <command>"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " all Regenerate base.pot and update all languages"
|
||||
echo " <lang> Regenerate base.pot and update a single language"
|
||||
echo " check Run translation validation checks"
|
||||
echo " -h, --help Show this help"
|
||||
}
|
||||
|
||||
generate_pot() {
|
||||
find . -type f -iname '*.py' | sort \
|
||||
| xargs xgettext --no-location --omit-header --keyword='tr' \
|
||||
-d base -o locales/base.pot
|
||||
}
|
||||
|
||||
update_lang() {
|
||||
local file=${1}
|
||||
echo "Updating: ${file}"
|
||||
local path
|
||||
path=$(dirname "${file}")
|
||||
msgmerge --quiet --no-location --width 512 --backup none --update "${file}" locales/base.pot
|
||||
msgfmt -o "${path}/base.mo" "${file}"
|
||||
}
|
||||
|
||||
|
||||
function generate_all() {
|
||||
cmd_generate_all() {
|
||||
generate_pot
|
||||
for file in $(find locales/ -name "base.po"); do
|
||||
update_lang "${file}"
|
||||
done
|
||||
}
|
||||
|
||||
function generate_single_lang() {
|
||||
lang_file="locales/${1}/LC_MESSAGES/base.po"
|
||||
|
||||
cmd_generate_single() {
|
||||
local lang_file="locales/${1}/LC_MESSAGES/base.po"
|
||||
if [ ! -f "${lang_file}" ]; then
|
||||
echo "Language files not found: ${lang_file}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
generate_pot
|
||||
update_lang "${lang_file}"
|
||||
}
|
||||
|
||||
cmd_check_po_syntax() {
|
||||
echo "Checking .po syntax..."
|
||||
local failed=0
|
||||
while IFS= read -r po; do
|
||||
if ! msgfmt --check --output-file=/dev/null "$po" 2>&1; then
|
||||
echo "FAIL: $po"
|
||||
failed=1
|
||||
fi
|
||||
done < <(find locales/ -name '*.po')
|
||||
if [ "$failed" -eq 1 ]; then
|
||||
echo "ERROR: some .po files have syntax errors" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "All .po files passed syntax check."
|
||||
}
|
||||
|
||||
cmd_check_no_tr_fstring() {
|
||||
echo "Checking for tr(f-string) anti-pattern..."
|
||||
if grep -rnE "tr\(\s*f['\"]" . --include='*.py'; then
|
||||
echo "ERROR: use tr('...{}').format(...) instead of tr(f'...')" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "No tr(f-string) anti-pattern found."
|
||||
}
|
||||
|
||||
cmd_check_pot_freshness() {
|
||||
# msgcmp (not diff) because base.pot carries legacy stale entries from
|
||||
# --join-existing; diff would always fail until a full cleanup is done.
|
||||
echo "Checking base.pot for missing strings..."
|
||||
find . -type f -iname '*.py' | sort \
|
||||
| xargs xgettext --no-location --omit-header --keyword='tr' \
|
||||
-d base -o /tmp/generated.pot
|
||||
if ! msgcmp --use-untranslated locales/base.pot /tmp/generated.pot; then
|
||||
echo "ERROR: base.pot is missing strings - run: locales_generator.sh all" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "base.pot contains all translatable strings."
|
||||
}
|
||||
|
||||
cmd_check() {
|
||||
local failed=0
|
||||
cmd_check_po_syntax || failed=1
|
||||
cmd_check_no_tr_fstring || failed=1
|
||||
cmd_check_pot_freshness || failed=1
|
||||
if [ "$failed" -eq 1 ]; then
|
||||
echo "Some translation checks failed." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "All translation checks passed."
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: ${0} <language_abbr>"
|
||||
echo "Special case 'all' for <language_abbr> builds all languages."
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lang=${1}
|
||||
|
||||
# Update the base file containing all translatable strings
|
||||
find . -type f -iname "*.py" | xargs xgettext --join-existing --no-location --omit-header --keyword='tr' -d base -o locales/base.pot
|
||||
|
||||
case "${lang}" in
|
||||
"all") generate_all;;
|
||||
*) generate_single_lang "${lang}"
|
||||
case "${1}" in
|
||||
check) cmd_check ;;
|
||||
all) cmd_generate_all ;;
|
||||
-h|--help) usage ;;
|
||||
*) cmd_generate_single "${1}" ;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import time
|
|||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.args import ArchConfigHandler
|
||||
from archinstall.lib.args import ArchConfigHandler, SubCommand
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.log import debug, error, info, logger, share_install_log, warn
|
||||
from archinstall.lib.menu.helpers import Confirmation
|
||||
from archinstall.lib.network.wifi_handler import WifiHandler
|
||||
from archinstall.lib.networking import ping
|
||||
from archinstall.lib.output import debug, error, info, share_install_log, warn
|
||||
from archinstall.lib.packages.util import check_version_upgrade
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
from archinstall.lib.translationhandler import tr, translation_handler
|
||||
|
|
@ -75,17 +75,36 @@ def _list_scripts() -> str:
|
|||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def _tui_confirm(header: str) -> bool:
|
||||
async def _ask() -> bool:
|
||||
def _share_log_command() -> None:
|
||||
paste_url: str = 'https://paste.rs'
|
||||
log_path = logger.path
|
||||
max_size = 10 * 1024 * 1024 # max supported size by paste.rs
|
||||
content = logger.get_content(max_bytes=max_size).decode()
|
||||
|
||||
header = tr('About to upload "{}" to the publicly accessible {}').format(log_path, paste_url) + '\n\n'
|
||||
header += tr('Do you want to continue?')
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_preview_for_all(lambda _: content)
|
||||
|
||||
async def _confirm() -> bool:
|
||||
result = await Confirmation(
|
||||
group=MenuItemGroup.yes_no(),
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preset=False,
|
||||
group=group,
|
||||
preview_header='Log content',
|
||||
preview_location='bottom',
|
||||
).show()
|
||||
return result.get_value()
|
||||
|
||||
return tui.run(_ask)
|
||||
result = tui.run(_confirm)
|
||||
|
||||
if result is True:
|
||||
res = share_install_log(paste_url=paste_url, max_bytes=max_size)
|
||||
if res is not None:
|
||||
info(tr('Log uploaded successfully. URL: {}').format(res))
|
||||
else:
|
||||
error(tr('Failed to upload log.'))
|
||||
|
||||
|
||||
def run() -> int:
|
||||
|
|
@ -94,15 +113,19 @@ def run() -> int:
|
|||
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 'share-log' in sys.argv:
|
||||
return share_install_log(confirm=_tui_confirm)
|
||||
|
||||
arch_config_handler = ArchConfigHandler()
|
||||
|
||||
if '--help' in sys.argv or '-h' in sys.argv:
|
||||
arch_config_handler.print_help()
|
||||
return 0
|
||||
|
||||
match arch_config_handler.args.command:
|
||||
case SubCommand.SHARE_LOG:
|
||||
_share_log_command()
|
||||
exit(0)
|
||||
case None:
|
||||
pass
|
||||
|
||||
script = arch_config_handler.get_script()
|
||||
|
||||
if script == 'list':
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ from archinstall.lib.disk.utils import disk_layouts
|
|||
from archinstall.lib.general.general_menu import PostInstallationAction, select_post_installation
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer, accessibility_tools_in_use, run_custom_user_commands
|
||||
from archinstall.lib.log import debug, error, info
|
||||
from archinstall.lib.menu.util import delayed_warning
|
||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||
from archinstall.lib.models import Bootloader
|
||||
from archinstall.lib.models.device import DiskLayoutType, EncryptionType
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.network.network_handler import install_network_config
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.packages.util import check_version_upgrade
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ from archinstall.lib.configuration import ConfigurationOutput
|
|||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.log import debug, error, info
|
||||
from archinstall.lib.menu.util import delayed_warning
|
||||
from archinstall.lib.models import Bootloader
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.network.network_handler import install_network_config
|
||||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.components import tui
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ from archinstall.lib.disk.filesystem import FilesystemHandler
|
|||
from archinstall.lib.disk.utils import disk_layouts
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.log import debug, error
|
||||
from archinstall.lib.menu.util import delayed_warning
|
||||
from archinstall.lib.output import debug, error
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from textual.widgets.option_list import Option
|
|||
from textual.widgets.selection_list import Selection
|
||||
from textual.worker import WorkerCancelled
|
||||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult
|
||||
from archinstall.tui.result import Result, ResultType
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ dependencies = [
|
|||
"pyparted==3.13.0",
|
||||
"pydantic==2.13.4",
|
||||
"cryptography==48.0.0",
|
||||
"textual==8.2.5",
|
||||
"textual==8.2.7",
|
||||
"markdown-it-py==4.0.0",
|
||||
"linkify-it-py==2.1.0",
|
||||
]
|
||||
|
|
@ -37,9 +37,10 @@ dev = [
|
|||
"mypy==2.1.0",
|
||||
"flake8==7.3.0",
|
||||
"pre-commit==4.6.0",
|
||||
"ruff==0.15.12",
|
||||
"ruff==0.15.14",
|
||||
"pylint==4.0.5",
|
||||
"pytest==9.0.3",
|
||||
"hypothesis>=6.152.4",
|
||||
]
|
||||
doc = ["sphinx"]
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ include-package-data = true
|
|||
"**/*.po",
|
||||
"**/*.pot",
|
||||
"**/*.json",
|
||||
"**/*.kdl",
|
||||
]
|
||||
|
||||
[tool.setuptools.package-dir]
|
||||
|
|
@ -68,7 +70,10 @@ archinstall = "archinstall"
|
|||
[tool.mypy]
|
||||
python_version = "3.14"
|
||||
files = "."
|
||||
exclude = "^build/"
|
||||
exclude = [
|
||||
"^build/",
|
||||
"^test_tooling/",
|
||||
]
|
||||
disallow_any_explicit = false
|
||||
disallow_any_expr = false
|
||||
disallow_any_unimported = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# To build
|
||||
|
||||
mkosi build -B
|
||||
|
||||
# To run
|
||||
|
||||
mkosi qemu \
|
||||
--drive=archinstall_small:25G \
|
||||
-- \
|
||||
-device nvme,serial=archinstall_small,drive=archinstall_small
|
||||
|
||||
*note: in order to boot the installation, we need to disable UKI being added to -kernel. I don't know of a way to do this yet, unless we tinker with mkosi/qemu.py - https://github.com/Torxed/mkosi/commit/6f3c20802bd73f88b672cc96ef0db1e542084316 - in which case we could do: `mkosi qemu --drive=archinstall_small:25G -- -device nvme,serial=archinstall_small,drive=archinstall_small,bootindex=0 -kernel none` but it still won't boot properly.*
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
[Distribution]
|
||||
Distribution=arch
|
||||
# LocalMirror=file:///var/lib/localmirror
|
||||
|
||||
[Output]
|
||||
Format=uki
|
||||
# Format=disk
|
||||
|
||||
[Include]
|
||||
Include=mkosi-vm
|
||||
|
||||
[Validation]
|
||||
SecureBoot=false
|
||||
SecureBootAutoEnroll=false
|
||||
Sign=false
|
||||
# Signing artifacts (hashsums etc) using a GPG key:
|
||||
# Key=D4B58E897A929F2E
|
||||
# Signing secure boot using PIV on yubikey:
|
||||
# SecureBoot=true
|
||||
# SecureBootKey=pkcs11:
|
||||
# SecureBootCertificate=secureboot.crt
|
||||
|
||||
[Content]
|
||||
Packages=
|
||||
pacman
|
||||
archlinux-keyring
|
||||
amd-ucode
|
||||
intel-ucode
|
||||
# tpm2-tss
|
||||
# libfido2
|
||||
# libp11-kit
|
||||
WithDocs=false
|
||||
RootPassword=toor
|
||||
Timezone=Europe/Stockholm
|
||||
Keymap=sv-latin1
|
||||
InitrdProfiles=network
|
||||
|
||||
[Config]
|
||||
Profiles=archinstall
|
||||
|
||||
[Build]
|
||||
Incremental=true
|
||||
ToolsTree=default
|
||||
ToolsTreeProfiles=devel,misc,package-manager,runtime,gui
|
||||
WithNetwork=yes
|
||||
|
||||
[Runtime]
|
||||
Console=gui
|
||||
CPUs=4
|
||||
RAM=8G
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
[Match]
|
||||
# Matching with "Type=ether" causes issues with containers because it also matches virtual Ethernet interfaces (veth*).
|
||||
# See https://bugs.archlinux.org/task/70892
|
||||
# Instead match by globbing the network interface name.
|
||||
Name=en*
|
||||
Name=eth*
|
||||
|
||||
[Link]
|
||||
RequiredForOnline=routable
|
||||
|
||||
[Network]
|
||||
DHCP=yes
|
||||
MulticastDNS=yes
|
||||
|
||||
# systemd-networkd does not set per-interface-type default route metrics
|
||||
# https://github.com/systemd/systemd/issues/17698
|
||||
# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband.
|
||||
# Use values from NetworkManager. From nm_device_get_route_metric_default in
|
||||
# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c
|
||||
[DHCPv4]
|
||||
RouteMetric=100
|
||||
|
||||
[IPv6AcceptRA]
|
||||
RouteMetric=100
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/usr/bin/agetty --noreset --noclear --autologin root - ${TERM}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
cd archinstall-git
|
||||
rm -rf dist
|
||||
|
||||
uv build --no-build-isolation --wheel
|
||||
uv pip install dist/*.whl --break-system-packages --system --no-build --no-deps
|
||||
|
|
@ -0,0 +1 @@
|
|||
enable systemd-networkd.service
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
git clone https://github.com/archlinux/archinstall.git /root/archinstall-git
|
||||
(cd /root/archinstall-git && git checkout master)
|
||||
|
||||
# TODO: Set geo-mirrors statically instead
|
||||
curl -s "https://archlinux.org/mirrorlist/?country=SE&protocol=https&use_mirror_status=on" | sed -e 's/^#Server/Server/' -e '/^#/d' | rankmirrors -n 5 - > /etc/pacman.d/mirrorlist
|
||||
|
||||
pacman-key --init
|
||||
pacman-key --populate archlinux
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
[Content]
|
||||
Packages=
|
||||
acpid
|
||||
intel-media-driver
|
||||
linux-firmware
|
||||
linux-firmware-intel
|
||||
ca-certificates-mozilla
|
||||
ca-certificates-utils
|
||||
nano
|
||||
gvfs
|
||||
noto-fonts
|
||||
cantarell-fonts
|
||||
ttf-dejavu
|
||||
polkit
|
||||
bash
|
||||
bzip2
|
||||
coreutils
|
||||
file
|
||||
filesystem
|
||||
findutils
|
||||
gawk
|
||||
gcc-libs
|
||||
gettext
|
||||
glibc
|
||||
grep
|
||||
gzip
|
||||
iproute2
|
||||
iputils
|
||||
licenses
|
||||
pciutils
|
||||
procps-ng
|
||||
psmisc
|
||||
sed
|
||||
tar
|
||||
linux
|
||||
diffutils
|
||||
less
|
||||
strace
|
||||
util-linux
|
||||
xz
|
||||
pacman-contrib
|
||||
gcc
|
||||
git
|
||||
pkgconfig
|
||||
python
|
||||
python-pip
|
||||
python-uv
|
||||
python-setuptools
|
||||
python-pyparted
|
||||
python-pydantic
|
||||
python-textual
|
||||
dosfstools
|
||||
btrfs-progs
|
||||
arch-install-scripts
|
||||
WithDocs=false
|
||||
KernelCommandLine=quiet splash
|
||||
|
|
@ -0,0 +1 @@
|
|||
13
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Qemu helper
|
||||
|
||||
Can be used with the `mkosi` test tooling
|
||||
|
||||
After `mkosi -B build` has been executed, run the following:
|
||||
|
||||
python test_tooling/qemu/qemu.py \
|
||||
--uki ./test_tooling/mkosi/mkosi.output/image_13.efi \
|
||||
--harddrive ~/test.qcow2:15G \
|
||||
--harddrive ~/test_large.qcow2:25G
|
||||
|
||||
And install using `archinstall`, after the machine has been shutdown, run:
|
||||
|
||||
python test_tooling/qemu/qemu.py \
|
||||
--harddrive ~/test.qcow2:15G \
|
||||
--harddrive ~/test_large.qcow2:25G
|
||||
|
||||
As this will boot EFI mode with just the harddrives to verify the installation.
|
||||
|
|
@ -0,0 +1,483 @@
|
|||
import getpass
|
||||
import grp
|
||||
import hashlib
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from argparse import ArgumentParser
|
||||
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
|
||||
|
||||
|
||||
class RequirementError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_master(interface):
|
||||
master_path = pathlib.Path(f'/sys/class/net/{interface}/master')
|
||||
return master_path.readlink().name if master_path.exists() else None
|
||||
|
||||
|
||||
def gray(text):
|
||||
return f'\033[38;5;246m{text}\033[0m'
|
||||
|
||||
|
||||
def orange(text):
|
||||
return f'\033[38;5;208m{text}\033[0m'
|
||||
|
||||
|
||||
def red(text):
|
||||
return f'\033[31m{text}\033[0m'
|
||||
|
||||
|
||||
sudo_password = None # Gets populated later
|
||||
harddrives = {}
|
||||
username = getpass.getuser()
|
||||
groupname = grp.getgrgid(os.getgid()).gr_name
|
||||
|
||||
# https://stackoverflow.com/a/43627833/929999
|
||||
_VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]'
|
||||
_VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode()
|
||||
|
||||
parser = ArgumentParser(description='A set of common parameters for the tooling', add_help=True)
|
||||
|
||||
# Defaults to the order of which the harddrives are defined.
|
||||
boot_option = parser.add_mutually_exclusive_group()
|
||||
boot_option.add_argument('--uki', help='Boot a UKI (EFI) image')
|
||||
boot_option.add_argument('--kernel', help='Boot a Linux kernel')
|
||||
boot_option.add_argument('--iso', help='Boot a ISO 9660')
|
||||
|
||||
networking = parser.add_argument_group('Networking', "Disables the default '-net nic -net user' network behavior of Qemu.")
|
||||
networking.add_argument('--tap', nargs='?', help='Configures a TAP interface and passes it in as a virtio-net-pci.', default=None, type=str)
|
||||
networking.add_argument('--tap-mac', nargs='?', help='MAC for the --tap interface', default='52:54:00:00:00:02')
|
||||
networking.add_argument('--bridge', nargs='?', help='Configures a bridge, to which the --tap is added.', default=None, type=str)
|
||||
networking.add_argument('--bridge-mac', nargs='?', help='MAC for the interface', default=None)
|
||||
networking.add_argument('--bridge-master', nargs='?', help="Which interface to set as 'master' on the bridge.", default=None, type=str)
|
||||
|
||||
hardware = parser.add_argument_group('Hardware', 'General hardware specs for the virtual machine')
|
||||
# To override the use of EFI boot (will not work with --uki for obvious reasons)
|
||||
hardware.add_argument('--bios', action='store_true', help='Disables EFI (edk2/ovmf) and uses BIOS support instead', default=False)
|
||||
hardware.add_argument('--memory', nargs='?', help='Ammount of memory to supply the machine', default=8192)
|
||||
hardware.add_argument('--harddrive', action='append', help='Sets up one or more virtio-scsi-pci, size is defined by --harddrive test.qcow2:15G', type=str)
|
||||
hardware.add_argument('--cpu', help='Sets the number of cores to allocate (default nproc -1)', type=str, default=os.cpu_count() - 1 if os.cpu_count() else 1)
|
||||
hardware.add_argument('--resolution', help="Sets Qemu's VGA resolution", type=str, default='1920x1107')
|
||||
|
||||
kernel = parser.add_argument_group('Kernel', '--kernel specific arguments')
|
||||
kernel.add_argument('--initrd', nargs='?', help='Defines which ISO to run (skips build all together)', default=None, type=pathlib.Path)
|
||||
|
||||
args, unknowns = parser.parse_known_args() # pylint: disable=redefined-outer-name
|
||||
|
||||
if args.bios and args.uki:
|
||||
raise ArgumentError('Cannot boot a --uki image with --bios mode (at least not that I know of).')
|
||||
|
||||
if args.uki is None and args.kernel is None and args.iso is None and args.harddrive is None:
|
||||
raise ArgumentError('Cannot boot this machine, define at least one of: --uki, --kernel, --iso, --harddrive')
|
||||
|
||||
if args.bridge is None and args.bridge_master:
|
||||
raise ArgumentError('Cannot use --bridge-master without defining --bridge')
|
||||
|
||||
if args.bridge is None and args.bridge_mac:
|
||||
raise ArgumentError('Cannot use --bridge-mac without defining --bridge')
|
||||
elif args.bridge and args.bridge_mac is None:
|
||||
args.bridge_mac = '52:54:00:00:00:1'
|
||||
|
||||
if args.tap and not args.bridge and get_master(args.tap) is None:
|
||||
# We'll allow it, because maybe we're tesing what happens without networking, but the NIC exists.
|
||||
# Or the user has some creative iptables/nftables forwarding.
|
||||
print(orange('--tap does not have a master, consider adding --bridge or manual set a master using ip-link(8).'))
|
||||
|
||||
if args.tap is None and args.bridge:
|
||||
print(orange("--bridge* arguments will be ignored since there's no --tap defined"))
|
||||
elif args.tap and args.tap_mac is None:
|
||||
args.tap_mac = '52:54:00:00:00:2'
|
||||
|
||||
|
||||
class SysCallError(Exception):
|
||||
def __init__(self, message: str, exit_code: int | None = None, worker_log: bytes = b'') -> None:
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.exit_code = exit_code
|
||||
self.worker_log = worker_log
|
||||
|
||||
|
||||
def clear_vt100_escape_codes(data: bytes) -> bytes:
|
||||
return re.sub(_VT100_ESCAPE_REGEX_BYTES, b'', data)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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]: # pylint: disable=redefined-outer-name
|
||||
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:
|
||||
print(gray(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
|
||||
|
||||
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:
|
||||
try:
|
||||
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
|
||||
except FileNotFoundError:
|
||||
print(red(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)
|
||||
|
||||
|
||||
def ensure_sudo():
|
||||
global sudo_password # pylint: disable=global-statement
|
||||
|
||||
if sudo_password is None:
|
||||
if (sudo_password := getpass.getpass(f'[sudo] password for {username}: ')) == '':
|
||||
raise ValueError('Certain commands need sudo to work and no sudo password was given.')
|
||||
|
||||
|
||||
def setup_networking():
|
||||
if args.tap:
|
||||
if pathlib.Path(f'/sys/class/net/{args.tap}').exists() is False:
|
||||
print(gray(f'Creating {args.tap} for user {username} and group {groupname}'))
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip tuntap add dev {args.tap} mode tap user {username} group {groupname}'), False
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
if args.bridge:
|
||||
if pathlib.Path(f'/sys/class/net/{args.bridge}').exists() is False:
|
||||
print(gray(f'Creating {args.bridge}'))
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link add name {args.bridge} type bridge'), False
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
if args.bridge_mac:
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} address {args.bridge_mac}'), False
|
||||
print(gray(f'Setting bridge {args.bridge} MAC address to {args.bridge_mac}'))
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
if args.bridge_master and get_master(args.bridge) != args.bridge_master:
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge_master} master {args.bridge}'), False
|
||||
print(gray(f'Setting interface {args.bridge_master} master to {args.bridge}'))
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
print(gray(f'Setting interface {args.tap} master to {args.bridge}'))
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} master {args.bridge}'), False
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
print(gray(f'Bringing up bridge {args.bridge}'))
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.bridge} up'), False
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
print(gray(f'Bringing interface {args.tap} up'))
|
||||
handle, pw_prompted = SysCommandWorker(f'sudo ip link set dev {args.tap} up'), False
|
||||
while handle.is_alive():
|
||||
if b'password for' in handle and pw_prompted is False:
|
||||
ensure_sudo()
|
||||
handle.write(bytes(sudo_password, 'UTF-8'))
|
||||
pw_prompted = True
|
||||
|
||||
|
||||
def setup_disks():
|
||||
if args.harddrive:
|
||||
for harddrive_arg in args.harddrive:
|
||||
path, size = harddrive_arg.split(':')
|
||||
path = pathlib.Path(path.strip()).expanduser().resolve().absolute()
|
||||
harddrives[path] = size.strip()
|
||||
|
||||
if path.exists() is False:
|
||||
handle = SysCommandWorker(f'qemu-img create -f qcow2 {hdd} {size}')
|
||||
while handle.is_alive():
|
||||
time.sleep(0.01)
|
||||
|
||||
if handle.exit_code != 0:
|
||||
raise ValueError(f'Could not create harddrive {hdd}: {handle}')
|
||||
|
||||
|
||||
setup_networking()
|
||||
setup_disks()
|
||||
|
||||
if args.uki or args.bios is False:
|
||||
disk_paths_hash = hashlib.sha1((''.join(sorted([str(x) for x in harddrives.keys()]))).encode()).hexdigest()
|
||||
|
||||
shutil.copy2('/usr/share/ovmf/x64/OVMF_CODE.secboot.4m.fd', f'./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}')
|
||||
shutil.copy2('/usr/share/ovmf/x64/OVMF_VARS.4m.fd', f'./OVMF_VARS.4m.fd.{disk_paths_hash}')
|
||||
|
||||
boot_index = 0
|
||||
qemu = 'qemu-system-x86_64'
|
||||
qemu += ' -cpu host'
|
||||
qemu += ' -enable-kvm'
|
||||
qemu += ' -machine q35,accel=kvm'
|
||||
qemu += ' -object rng-random,filename=/dev/urandom,id=rng0'
|
||||
qemu += ' -device virtio-rng-pci,rng=rng0'
|
||||
qemu += ' -global driver=cfi.pflash01,property=secure,value=on'
|
||||
qemu += f' -smp {args.cpu},sockets=1,dies=1,cores={args.cpu},threads=1'
|
||||
# qemu += f' -vga vga'
|
||||
qemu += f' -device VGA,edid=on,xres={args.resolution.split("x")[0]},yres={args.resolution.split("x")[1]}'
|
||||
qemu += ' -device intel-iommu,device-iotlb=on,caching-mode=on'
|
||||
qemu += f' -m {args.memory}'
|
||||
if args.bios is False:
|
||||
qemu += f' -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.secboot.4m.fd.{disk_paths_hash}'
|
||||
qemu += f' -drive if=pflash,format=raw,file=./OVMF_VARS.4m.fd.{disk_paths_hash}'
|
||||
if args.uki:
|
||||
qemu += f' -kernel {args.uki}'
|
||||
boot_index += 1
|
||||
scsi_index = 0
|
||||
for scsi_index, hdd in enumerate(harddrives.keys()):
|
||||
# qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{index}'
|
||||
# qemu += f' -device scsi-hd,drive=hdd{index},bus=scsi{index}.0,id=scsi{index}.0,bootindex={hdd_boot_priority+index}'
|
||||
# qemu += f' -drive file={hdd},if=none,format=qcow2,discard=unmap,aio=native,cache=none,id=hdd{index}'
|
||||
qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{scsi_index},addr=0x{scsi_index + 8}'
|
||||
qemu += f' -device scsi-hd,drive=libvirt-{scsi_index}-format,bus=scsi{scsi_index}.0,id=scsi{scsi_index}-0-0-0,channel=0,scsi-id=0,lun=0,device_id=drive-scsi0-0-0-0,bootindex={boot_index},write-cache=on' # noqa: E501
|
||||
qemu += f' -blockdev \'{{"driver":"file","filename":"{hdd}","aio":"threads","node-name":"libvirt-{scsi_index}-storage","cache":{{"direct":false,"no-flush":false}},"auto-read-only":true,"discard":"unmap"}}\'' # noqa: E501
|
||||
qemu += f' -blockdev \'{{"node-name":"libvirt-{scsi_index}-format","read-only":false,"discard":"unmap","cache":{{"direct":true,"no-flush":false}},"driver":"qcow2","file":"libvirt-{scsi_index}-storage","backing":null}}\'' # noqa: E501
|
||||
boot_index += 1
|
||||
if args.iso:
|
||||
qemu += f' -device virtio-scsi-pci,bus=pcie.0,id=scsi{scsi_index + 1}'
|
||||
qemu += f' -device scsi-cd,drive=cdrom0,bus=scsi{scsi_index + 1}.0,bootindex={boot_index}'
|
||||
qemu += f' -drive file={args.iso},media=cdrom,if=none,format=raw,cache=none,id=cdrom0'
|
||||
boot_index += 1
|
||||
|
||||
# if args.vfio:
|
||||
# qemu += f' -drive file={args.vfio},index=2,media=cdrom'
|
||||
|
||||
if args.tap:
|
||||
qemu += f' -device virtio-net-pci,mac={args.tap_mac},id=network0,netdev=network0.0,status=on,bus=pcie.0'
|
||||
qemu += f' -netdev tap,ifname={args.tap},id=network0.0,script=no,downscript=no'
|
||||
|
||||
print(gray(qemu))
|
||||
|
||||
qemu_session = subprocess.run(shlex.split(qemu), check=True, capture_output=True)
|
||||
|
||||
if qemu_session.stdout:
|
||||
print(qemu_session.stdout.decode())
|
||||
if qemu_session.returncode != 0:
|
||||
print(red(qemu_session.stderr.decode()))
|
||||
|
|
@ -1,94 +1,127 @@
|
|||
# pylint: disable=redefined-outer-name
|
||||
import string
|
||||
import urllib.error
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from hypothesis import HealthCheck, given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from archinstall.lib.output import share_install_log
|
||||
from archinstall.lib.log import share_install_log
|
||||
|
||||
urls = st.builds(
|
||||
'{}://{}.{}/{}'.format,
|
||||
st.sampled_from(['http', 'https']),
|
||||
st.text(alphabet=string.ascii_lowercase, min_size=3, max_size=10),
|
||||
st.sampled_from(['com', 'net', 'org', 'rs']),
|
||||
st.text(alphabet=string.ascii_lowercase + string.digits, min_size=0, max_size=8),
|
||||
)
|
||||
|
||||
max_bytes = st.one_of(st.none(), st.integers(min_value=1, max_value=130))
|
||||
|
||||
random_paths = st.lists(
|
||||
st.text(
|
||||
alphabet=string.ascii_lowercase + string.digits,
|
||||
min_size=1,
|
||||
max_size=10,
|
||||
),
|
||||
min_size=1,
|
||||
max_size=5,
|
||||
).map(lambda parts: Path(*parts))
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def log_file(tmp_path: Path) -> Path:
|
||||
log_dir = tmp_path / 'archinstall'
|
||||
log_dir.mkdir()
|
||||
return log_dir / 'install.log'
|
||||
|
||||
|
||||
def _fake_logger(log_file: Path) -> MagicMock:
|
||||
mock = MagicMock()
|
||||
mock.path = log_file
|
||||
return mock
|
||||
# def _fake_logger(log_file: Path) -> MagicMock:
|
||||
# mock = MagicMock()
|
||||
# mock.path = log_file
|
||||
# return mock
|
||||
|
||||
|
||||
def test_file_not_found(tmp_path: Path) -> None:
|
||||
missing = tmp_path / 'no-such' / 'install.log'
|
||||
with patch('archinstall.lib.output.logger', _fake_logger(missing)):
|
||||
assert share_install_log() == 1
|
||||
@given(paste_url=urls, max_byte=max_bytes, sub_path=random_paths)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_file_not_found(
|
||||
tmp_path: Path,
|
||||
sub_path: Path,
|
||||
paste_url: str,
|
||||
max_byte: int | None,
|
||||
) -> None:
|
||||
missing_log = tmp_path / sub_path / 'install.log'
|
||||
|
||||
with patch('archinstall.lib.log.logger._path', new=missing_log):
|
||||
assert share_install_log(paste_url, max_byte) is None
|
||||
|
||||
|
||||
def test_empty_file(log_file: Path) -> None:
|
||||
@given(paste_url=urls, max_byte=max_bytes)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_empty_file(log_file: Path, paste_url: str, max_byte: int | None) -> None:
|
||||
log_file.write_bytes(b'')
|
||||
with patch('archinstall.lib.output.logger', _fake_logger(log_file)):
|
||||
assert share_install_log() == 1
|
||||
|
||||
with patch('archinstall.lib.log.logger._path', new=log_file.parent):
|
||||
# with patch('archinstall.lib.log.logger', _fake_logger(log_file)):
|
||||
assert share_install_log(paste_url, max_byte) is None
|
||||
|
||||
|
||||
def test_user_cancels(log_file: Path) -> None:
|
||||
@given(paste_url=urls, resp_url=urls, max_byte=max_bytes)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_successful_upload(log_file: Path, resp_url: str, paste_url: str, max_byte: int | None) -> None:
|
||||
log_file.write_text('some log content')
|
||||
with patch('archinstall.lib.output.logger', _fake_logger(log_file)):
|
||||
assert share_install_log(confirm=lambda _: False) == 1
|
||||
|
||||
|
||||
def test_successful_upload(log_file: Path) -> None:
|
||||
log_file.write_text('some log content')
|
||||
fake_response = BytesIO(b'https://paste.rs/abc.def')
|
||||
fake_response = BytesIO(resp_url.encode())
|
||||
|
||||
with (
|
||||
patch('archinstall.lib.output.logger', _fake_logger(log_file)),
|
||||
patch('urllib.request.urlopen', return_value=fake_response) as mock_open,
|
||||
patch('archinstall.lib.log.logger._path', new=log_file.parent),
|
||||
patch('urllib.request.urlopen', return_value=fake_response),
|
||||
):
|
||||
result = share_install_log()
|
||||
|
||||
assert result == 0
|
||||
req = mock_open.call_args[0][0]
|
||||
assert req.data == b'some log content'
|
||||
result = share_install_log(paste_url, max_byte)
|
||||
assert result == resp_url
|
||||
|
||||
|
||||
def test_truncation(log_file: Path) -> None:
|
||||
max_size = 100
|
||||
@given(paste_url=urls, resp_url=urls, max_byte=max_bytes)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_truncation(log_file: Path, resp_url: str, paste_url: str, max_byte: int | None) -> None:
|
||||
content = b'A' * 50 + b'B' * 80
|
||||
log_file.write_bytes(content)
|
||||
fake_response = BytesIO(b'https://paste.rs/abc.def')
|
||||
fake_response = BytesIO(resp_url.encode())
|
||||
|
||||
exptected_byte = len(content) if max_byte is None else max_byte
|
||||
|
||||
with (
|
||||
patch('archinstall.lib.output.logger', _fake_logger(log_file)),
|
||||
patch('archinstall.lib.log.logger._path', new=log_file.parent),
|
||||
patch('urllib.request.urlopen', return_value=fake_response) as mock_open,
|
||||
):
|
||||
result = share_install_log(max_size=max_size)
|
||||
|
||||
assert result == 0
|
||||
req = mock_open.call_args[0][0]
|
||||
assert len(req.data) == max_size
|
||||
assert req.data == content[-max_size:]
|
||||
_ = share_install_log(paste_url, max_byte)
|
||||
req = mock_open.call_args[0][0]
|
||||
assert len(req.data) == exptected_byte
|
||||
assert req.data == content[-exptected_byte:]
|
||||
|
||||
|
||||
def test_network_error(log_file: Path) -> None:
|
||||
@given(paste_url=urls, max_byte=max_bytes)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_network_error(log_file: Path, paste_url: str, max_byte: int | None) -> None:
|
||||
log_file.write_text('some log content')
|
||||
|
||||
with (
|
||||
patch('archinstall.lib.output.logger', _fake_logger(log_file)),
|
||||
patch('archinstall.lib.log.logger._path', new=log_file.parent),
|
||||
patch('urllib.request.urlopen', side_effect=urllib.error.URLError('no network')),
|
||||
):
|
||||
assert share_install_log() == 1
|
||||
assert share_install_log(paste_url, max_byte) is None
|
||||
|
||||
|
||||
def test_unexpected_response(log_file: Path) -> None:
|
||||
@given(paste_url=urls, max_byte=max_bytes)
|
||||
@settings(max_examples=3, suppress_health_check=[HealthCheck.function_scoped_fixture])
|
||||
def test_unexpected_response(log_file: Path, paste_url: str, max_byte: int | None) -> None:
|
||||
log_file.write_text('some log content')
|
||||
fake_response = BytesIO(b'ERROR: something went wrong')
|
||||
|
||||
with (
|
||||
patch('archinstall.lib.output.logger', _fake_logger(log_file)),
|
||||
patch('archinstall.lib.log.logger._path', new=log_file.parent),
|
||||
patch('urllib.request.urlopen', return_value=fake_response),
|
||||
):
|
||||
assert share_install_log() == 1
|
||||
assert share_install_log(paste_url, max_byte) is None
|
||||
|
|
|
|||
Loading…
Reference in New Issue