Merge branch 'master' into feat/add_plymouth
This commit is contained in:
commit
e61b81d5cc
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- run: pacman --noconfirm -Syu bandit
|
||||
- name: Security checkup with Bandit
|
||||
run: bandit -r archinstall || exit 0
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
- name: Install pre-dependencies
|
||||
run: |
|
||||
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
run: |
|
||||
sphinx-build docs _build
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
publish_branch: gh-pages
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ jobs:
|
|||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- run: pwd
|
||||
- run: find .
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ jobs:
|
|||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ jobs:
|
|||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ jobs:
|
|||
ruff_format_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
|
||||
- run: ruff format --diff
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ jobs:
|
|||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
name: Validate translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
- name: Install gettext
|
||||
run: sudo apt-get update && sudo apt-get install -y gettext
|
||||
- name: Run translation checks
|
||||
|
|
|
|||
|
|
@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # 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.14
|
||||
rev: v0.15.16
|
||||
hooks:
|
||||
# fix unused imports and sort them
|
||||
- id: ruff
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from archinstall.lib.crypt import encrypt
|
|||
from archinstall.lib.log import debug, logger, warn
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.menu.util import get_password, prompt_dir
|
||||
from archinstall.lib.models.network import NetworkConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.utils.format import as_key_value_pair
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
@ -83,13 +82,10 @@ class ConfigurationOutput:
|
|||
|
||||
return simple_summary
|
||||
|
||||
async def confirm_config(self, show_install_warnings: bool = False) -> bool:
|
||||
async def confirm_config(self) -> bool:
|
||||
header = f'{tr("The specified configuration will be applied")}. '
|
||||
header += tr('Would you like to continue?') + '\n'
|
||||
|
||||
if show_install_warnings:
|
||||
header += self._render_install_warnings()
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_preview_for_all(lambda x: self.user_config_to_json())
|
||||
|
||||
|
|
@ -107,22 +103,6 @@ class ConfigurationOutput:
|
|||
|
||||
return True
|
||||
|
||||
def get_install_warnings(self) -> list[str]:
|
||||
warnings: list[str] = []
|
||||
|
||||
if not isinstance(self._config.network_config, NetworkConfiguration):
|
||||
warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.'))
|
||||
|
||||
return warnings
|
||||
|
||||
def _render_install_warnings(self) -> str:
|
||||
warnings = self.get_install_warnings()
|
||||
|
||||
if not warnings:
|
||||
return ''
|
||||
|
||||
return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n'
|
||||
|
||||
def _is_valid_path(self, dest_path: Path) -> bool:
|
||||
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
||||
if not dest_path_ok:
|
||||
|
|
|
|||
|
|
@ -149,7 +149,11 @@ class Luks2:
|
|||
'luks2',
|
||||
]
|
||||
|
||||
result = run(cmd, input_data=passphrase)
|
||||
try:
|
||||
result = run(cmd, input_data=passphrase)
|
||||
except CalledProcessError as err:
|
||||
output = err.stdout.decode().rstrip()
|
||||
raise DiskError(f'Could not unlock luks2 device "{self.luks_dev_path}": {output}')
|
||||
|
||||
debug(f'cryptsetup open output: {result.stdout.decode().rstrip()}')
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ 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
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult
|
||||
|
||||
|
||||
class GlobalMenu(AbstractMenu[None]):
|
||||
|
|
@ -494,21 +494,48 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
|
||||
return None
|
||||
|
||||
def _prev_install_invalid_config(self, item: MenuItem) -> str | None:
|
||||
if missing := self._missing_configs():
|
||||
text = tr('Missing configurations:\n')
|
||||
for m in missing:
|
||||
text += f'- {m}\n'
|
||||
return text[:-1] # remove last new line
|
||||
def _get_install_warnings(self) -> list[str]:
|
||||
warnings: list[str] = []
|
||||
|
||||
if error := self._validate_bootloader():
|
||||
return tr('Invalid configuration: {}').format(error)
|
||||
if not isinstance(self._arch_config.network_config, NetworkConfiguration):
|
||||
warnings.append(tr('No network configuration selected. Network will need to be set up manually on the installed system.'))
|
||||
|
||||
return warnings
|
||||
|
||||
def _prev_install_invalid_config(self, item: MenuItem) -> PreviewResult | None:
|
||||
self.sync_all_to_config()
|
||||
summary = ConfigurationOutput(self._arch_config).as_summary()
|
||||
if summary:
|
||||
return f'{tr("Ready to install")}\n\n{summary}'
|
||||
return tr('Ready to install')
|
||||
config_output = ConfigurationOutput(self._arch_config)
|
||||
|
||||
warnings = self._get_install_warnings()
|
||||
messages: list[tuple[str, MsgLevelType]] = []
|
||||
|
||||
errors = ''
|
||||
if missing := self._missing_configs():
|
||||
errors += f'{tr("Missing configurations:")}\n'
|
||||
errors += '\n'.join(f'- {m}' for m in missing)
|
||||
|
||||
disk_item = self._item_group.find_by_key('disk_config')
|
||||
if disk_item.has_value():
|
||||
if error := self._validate_bootloader():
|
||||
if errors:
|
||||
errors += '\n\n'
|
||||
errors += f'{tr("Invalid configuration:")}\n- {error}'
|
||||
|
||||
if errors:
|
||||
messages.append((errors, MsgLevelType.MsgError))
|
||||
else:
|
||||
messages.append((tr('Ready to install'), MsgLevelType.MsgInfo))
|
||||
|
||||
if warnings:
|
||||
text = f'{tr("Warnings:")}\n' + '\n'.join(f'- {w}' for w in warnings)
|
||||
messages.append((text, MsgLevelType.MsgWarning))
|
||||
|
||||
if not errors:
|
||||
summary = config_output.as_summary()
|
||||
if summary:
|
||||
messages.append((summary, MsgLevelType.MsgNone))
|
||||
|
||||
return PreviewResult(messages)
|
||||
|
||||
def _prev_profile(self, item: MenuItem) -> str | None:
|
||||
profile_config: ProfileConfiguration | None = item.value
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ from pathlib import Path
|
|||
from archinstall.lib.menu.helpers import Confirmation, Input
|
||||
from archinstall.lib.models.users import Password, PasswordStrength
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.components import InputInfo, InputInfoType, tui
|
||||
from archinstall.tui.components import InputInfo, tui
|
||||
from archinstall.tui.menu_item import MsgLevelType
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
|
|
@ -20,11 +21,11 @@ async def get_password(
|
|||
return None
|
||||
strength = PasswordStrength.strength(value)
|
||||
if strength in (PasswordStrength.VERY_WEAK, PasswordStrength.WEAK):
|
||||
return InputInfo(message=tr('Password strength: Weak'), info_type=InputInfoType.MsgError)
|
||||
return InputInfo(message=tr('Password strength: Weak'), msg_level=MsgLevelType.MsgError)
|
||||
elif strength == PasswordStrength.MODERATE:
|
||||
return InputInfo(message=tr('Password strength: Moderate'), info_type=InputInfoType.MsgWarning)
|
||||
return InputInfo(message=tr('Password strength: Moderate'), msg_level=MsgLevelType.MsgWarning)
|
||||
elif strength == PasswordStrength.STRONG:
|
||||
return InputInfo(message=tr('Password strength: Strong'), info_type=InputInfoType.MsgInfo)
|
||||
return InputInfo(message=tr('Password strength: Strong'), msg_level=MsgLevelType.MsgInfo)
|
||||
return None
|
||||
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ProfileConfiguration(SubConfig):
|
|||
out.append(self.profile.name)
|
||||
|
||||
if self.gfx_driver:
|
||||
out.append(tr('{} grphics driver').format(self.gfx_driver.value))
|
||||
out.append(tr('{} graphics driver').format(self.gfx_driver.value))
|
||||
|
||||
if self.greeter:
|
||||
out.append(tr('{} greeter').format(self.greeter.value))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from asyncio import sleep
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import assert_never, override
|
||||
|
|
@ -109,36 +108,25 @@ class WifiHandler(InstanceRunnable[bool]):
|
|||
|
||||
debug(f'Found wifi interface: {wifi_iface}')
|
||||
|
||||
async def get_wifi_networks() -> MenuItemGroup:
|
||||
debug('Scanning Wifi networks')
|
||||
result = self._wpa_cli('scan', wifi_iface)
|
||||
wifi_networks = await self._scan_wifi(wifi_iface)
|
||||
|
||||
if not result.success:
|
||||
debug(f'Failed to scan wifi networks: {result.error}')
|
||||
return MenuItemGroup([])
|
||||
if not wifi_networks:
|
||||
debug('No networks found')
|
||||
await NotifyScreen(header=tr('No wifi networks found')).run()
|
||||
tui.exit(Result.false())
|
||||
return False
|
||||
|
||||
await sleep(5)
|
||||
wifi_networks = self._get_scan_results(wifi_iface)
|
||||
|
||||
items = [MenuItem(network.ssid, value=network) for network in wifi_networks]
|
||||
return MenuItemGroup(items)
|
||||
items = [MenuItem(network.ssid, value=network) for network in wifi_networks]
|
||||
|
||||
result = await TableSelectionScreen[WifiNetwork](
|
||||
header=tr('Select wifi network to connect to'),
|
||||
loading_header=tr('Scanning wifi networks...'),
|
||||
group_callback=get_wifi_networks,
|
||||
group=MenuItemGroup(items),
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
).run()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
if not result.has_data():
|
||||
debug('No networks found')
|
||||
await NotifyScreen(header=tr('No wifi networks found')).run()
|
||||
tui.exit(Result.false())
|
||||
return False
|
||||
|
||||
network = result.get_value()
|
||||
case ResultType.Skip | ResultType.Reset:
|
||||
tui.exit(Result.false())
|
||||
|
|
@ -184,6 +172,18 @@ class WifiHandler(InstanceRunnable[bool]):
|
|||
|
||||
return True
|
||||
|
||||
async def _scan_wifi(self, wifi_iface: str) -> list[WifiNetwork]:
|
||||
debug('Scanning Wifi networks')
|
||||
scan_result = self._wpa_cli('scan', wifi_iface)
|
||||
|
||||
if not scan_result.success:
|
||||
debug(f'Failed to scan wifi networks: {scan_result.error}')
|
||||
return []
|
||||
|
||||
await LoadingScreen(timer=5, header=tr('Scanning wifi networks...')).run()
|
||||
|
||||
return self._get_scan_results(wifi_iface)
|
||||
|
||||
async def _notify_failure(self) -> None:
|
||||
await NotifyScreen(header=tr('Failed setting up wifi')).run()
|
||||
|
||||
|
|
|
|||
|
|
@ -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}')
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -70,25 +70,10 @@ cmd_check_no_tr_fstring() {
|
|||
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
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -2230,3 +2230,162 @@ msgstr "Налаштування входу з пристроєм двофакт
|
|||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr "Стандартно: {}мс, Рекомендований діапазон - 1000-60000"
|
||||
|
||||
msgid "ArchInstall Language"
|
||||
msgstr "Мова ArchInstall"
|
||||
|
||||
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 "Зашифрований пароль root"
|
||||
|
||||
msgid "Audio server \"{}\""
|
||||
msgstr "Аудіосервер \"{}\""
|
||||
|
||||
msgid "Firewall \"{}\""
|
||||
msgstr "Брандмауер \"{}\""
|
||||
|
||||
msgid "Bootloader \"{}\""
|
||||
msgstr "Завантажувач \"{}\""
|
||||
|
||||
msgid "Power management \"{}\""
|
||||
msgstr "Керування живленням \"{}\""
|
||||
|
||||
msgid "Btrfs snapshot \"{}\""
|
||||
msgstr "Знімок Btrfs \"{}\""
|
||||
|
||||
msgid "Keyboard layout \"{}\""
|
||||
msgstr "Розкладка клавіатури \"{}\""
|
||||
|
||||
msgid "Locale language \"{}\""
|
||||
msgstr "Мова локалізації \"{}\""
|
||||
|
||||
msgid "Locale encoding \"{}\""
|
||||
msgstr "Кодування локалізації \"{}\""
|
||||
|
||||
msgid "Console font \"{}\""
|
||||
msgstr "Шрифт консолі \"{}\""
|
||||
|
||||
msgid "Mirror regions \"{}\""
|
||||
msgstr "Регіони дзеркал \"{}\""
|
||||
|
||||
msgid "Optional repositories \"{}\""
|
||||
msgstr "Необов'язкові репозиторії \"{}\""
|
||||
|
||||
msgid "Extra fonts \"{}\""
|
||||
msgstr "Додаткові шрифти \"{}\""
|
||||
|
||||
msgid "Bluetooth enabled"
|
||||
msgstr "Bluetooth увімкнено"
|
||||
|
||||
msgid "Color enabled"
|
||||
msgstr "Колір увімкнено"
|
||||
|
||||
msgid "Print service enabled"
|
||||
msgstr "Службу друку увімкнено"
|
||||
|
||||
msgid "Zram enabled"
|
||||
msgstr "Zram увімкнено"
|
||||
|
||||
msgid "UKI enabled"
|
||||
msgstr "UKI увімкнено"
|
||||
|
||||
msgid "Root password set"
|
||||
msgstr "Пароль root встановлено"
|
||||
|
||||
msgid "U2F set up"
|
||||
msgstr "U2F налаштовано"
|
||||
|
||||
msgid "LVM set up"
|
||||
msgstr "LVM налаштовано"
|
||||
|
||||
msgid "Custom servers set up"
|
||||
msgstr "Користувацькі сервери налаштовано"
|
||||
|
||||
msgid "Custom repositories set up"
|
||||
msgstr "Користувацькі репозиторії налаштовано"
|
||||
|
||||
msgid "Configured {} user(s)"
|
||||
msgstr "Налаштовано користувачів: {}"
|
||||
|
||||
msgid "Zram algorithm {}"
|
||||
msgstr "Алгоритм Zram {}"
|
||||
|
||||
msgid "Devices {}"
|
||||
msgstr "Пристрої {}"
|
||||
|
||||
msgid "{} grphics driver"
|
||||
msgstr "Графічний драйвер {}"
|
||||
|
||||
msgid "{} encryption"
|
||||
msgstr "Шифрування {}"
|
||||
|
||||
msgid "{} layout"
|
||||
msgstr "Розмітка {}"
|
||||
|
||||
msgid "{} greeter"
|
||||
msgstr "Менеджер входу {}"
|
||||
|
||||
msgid "About to upload \"{}\" to the publicly accessible {}"
|
||||
msgstr "Зараз \"{}\" буде вивантажено на загальнодоступний {}"
|
||||
|
||||
msgid "Log uploaded successfully. URL: {}"
|
||||
msgstr "Журнал успішно вивантажено. URL: {}"
|
||||
|
||||
msgid "Failed to upload log."
|
||||
msgstr "Не вдалося вивантажити журнал."
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Вручну"
|
||||
|
||||
msgid "Pre-mount"
|
||||
msgstr "Передмонтування"
|
||||
|
||||
msgid "Default"
|
||||
msgstr "Типово"
|
||||
|
||||
msgid "Warnings:"
|
||||
msgstr "Попередження:"
|
||||
|
||||
msgid "Missing configurations:"
|
||||
msgstr "Відсутні конфігурації:"
|
||||
|
||||
msgid "Invalid configuration:"
|
||||
msgstr "Неправильна конфігурація:"
|
||||
|
||||
msgid "Do you want to continue?"
|
||||
msgstr "Бажаєте продовжити?"
|
||||
|
||||
msgid "Enter a repository name"
|
||||
msgstr "Введіть назву репозиторію"
|
||||
|
||||
msgid "Use standalone iwd"
|
||||
msgstr "Використовувати автономний iwd"
|
||||
|
||||
msgid "{} needs access to your seat"
|
||||
msgstr "{} потребує доступу до вашого seat"
|
||||
|
||||
msgid "Choose an option how to give {} access to your hardware"
|
||||
msgstr "Виберіть, як надати {} доступ до вашого обладнання"
|
||||
|
||||
msgid "collection of hardware devices i.e. keyboard, mouse"
|
||||
msgstr "набір апаратних пристроїв, напр. клавіатура, миша"
|
||||
|
||||
msgid "No network configuration selected. Network will need to be set up manually on the installed system."
|
||||
msgstr "Конфігурацію мережі не обрано. Мережу доведеться налаштувати вручну на встановленій системі."
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
|
|||
|
||||
if not arch_config_handler.args.silent:
|
||||
aborted = False
|
||||
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
|
||||
res: bool = tui.run(config.confirm_config)
|
||||
|
||||
if not res:
|
||||
debug('Installation aborted')
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
|
|||
|
||||
if not arch_config_handler.args.silent:
|
||||
aborted = False
|
||||
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
|
||||
res: bool = tui.run(config.confirm_config)
|
||||
|
||||
if not res:
|
||||
debug('Installation aborted')
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import sys
|
|||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, replace
|
||||
from enum import Enum, auto
|
||||
from typing import Any, ClassVar, Literal, TypeVar, cast, override
|
||||
|
||||
from rich.text import Text
|
||||
from textual import work
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding, BindingsMap
|
||||
|
|
@ -21,12 +21,28 @@ from textual.worker import WorkerCancelled
|
|||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup, MsgLevelType, PreviewResult
|
||||
from archinstall.tui.result import Result, ResultType
|
||||
|
||||
ValueT = TypeVar('ValueT')
|
||||
|
||||
|
||||
def _update_preview(widget: Label, result: str | PreviewResult | None) -> None:
|
||||
if result is None:
|
||||
widget.update('')
|
||||
return
|
||||
|
||||
if isinstance(result, str):
|
||||
widget.update(result)
|
||||
else:
|
||||
text = Text()
|
||||
for i, (message, level) in enumerate(result.messages):
|
||||
if i > 0:
|
||||
text.append('\n\n')
|
||||
text.append(message, style=level.style())
|
||||
widget.update(text)
|
||||
|
||||
|
||||
def _translate_bindings(source: BindingsMap | None, target: BindingsMap) -> None:
|
||||
"""Translate binding descriptions from source to target.
|
||||
|
||||
|
|
@ -361,13 +377,9 @@ class OptionListScreen(BaseScreen[ValueT]):
|
|||
item = self._group.find_by_id(item_id)
|
||||
|
||||
if item.preview_action is not None:
|
||||
maybe_preview = item.preview_action(item)
|
||||
|
||||
if maybe_preview is not None:
|
||||
preview_widget.update(maybe_preview)
|
||||
return
|
||||
|
||||
preview_widget.update('')
|
||||
_update_preview(preview_widget, item.preview_action(item))
|
||||
else:
|
||||
_update_preview(preview_widget, None)
|
||||
|
||||
|
||||
class _SelectionList(SelectionList[ValueT]):
|
||||
|
|
@ -614,12 +626,9 @@ class SelectListScreen(BaseScreen[ValueT]):
|
|||
preview_widget = self.query_one('#preview_content', Label)
|
||||
|
||||
if item.preview_action is not None:
|
||||
maybe_preview = item.preview_action(item)
|
||||
if maybe_preview is not None:
|
||||
preview_widget.update(maybe_preview)
|
||||
return
|
||||
|
||||
preview_widget.update('')
|
||||
_update_preview(preview_widget, item.preview_action(item))
|
||||
else:
|
||||
_update_preview(preview_widget, None)
|
||||
|
||||
|
||||
# DEPRECATED: Removed when switching to async
|
||||
|
|
@ -735,13 +744,8 @@ class ConfirmationScreen(BaseScreen[ValueT]):
|
|||
|
||||
if self._preview_header is not None:
|
||||
preview = self.query_one('#preview_content', Label)
|
||||
|
||||
if focused.preview_action is None:
|
||||
preview.update('')
|
||||
else:
|
||||
text = focused.preview_action(focused)
|
||||
if text is not None:
|
||||
preview.update(text)
|
||||
result = focused.preview_action(focused) if focused.preview_action else None
|
||||
_update_preview(preview, result)
|
||||
else:
|
||||
button.remove_class('-active')
|
||||
|
||||
|
|
@ -768,16 +772,10 @@ class NotifyScreen(ConfirmationScreen[ValueT]):
|
|||
super().__init__(group, header)
|
||||
|
||||
|
||||
class InputInfoType(Enum):
|
||||
MsgInfo = auto()
|
||||
MsgWarning = auto()
|
||||
MsgError = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class InputInfo:
|
||||
message: str
|
||||
info_type: InputInfoType
|
||||
msg_level: MsgLevelType
|
||||
|
||||
|
||||
class InputScreen(BaseScreen[str]):
|
||||
|
|
@ -889,11 +887,11 @@ class InputScreen(BaseScreen[str]):
|
|||
result = self._info_callback(event.value)
|
||||
if result:
|
||||
css_class = ''
|
||||
if result.info_type == InputInfoType.MsgError:
|
||||
if result.msg_level == MsgLevelType.MsgError:
|
||||
css_class = 'input-hint-msg-error'
|
||||
elif result.info_type == InputInfoType.MsgWarning:
|
||||
elif result.msg_level == MsgLevelType.MsgWarning:
|
||||
css_class = 'input-hint-msg-warning'
|
||||
elif result.info_type == InputInfoType.MsgInfo:
|
||||
elif result.msg_level == MsgLevelType.MsgInfo:
|
||||
css_class = 'input-hint-msg-info'
|
||||
info_label.update(result.message)
|
||||
info_label.set_classes(css_class)
|
||||
|
|
@ -1138,13 +1136,7 @@ class TableSelectionScreen(BaseScreen[ValueT]):
|
|||
return
|
||||
|
||||
preview_widget = self.query_one('#preview_content', Label)
|
||||
|
||||
maybe_preview = item.preview_action(item)
|
||||
if maybe_preview is not None:
|
||||
preview_widget.update(maybe_preview)
|
||||
return
|
||||
|
||||
preview_widget.update('')
|
||||
_update_preview(preview_widget, item.preview_action(item))
|
||||
|
||||
def _set_cursor(self, row_index: int) -> None:
|
||||
data_table = self.query_one(DataTable)
|
||||
|
|
@ -1279,6 +1271,7 @@ class _AppInstance(App[ValueT]):
|
|||
background: black;
|
||||
border-left: vkey white 20%;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, main: InstanceRunnable[ValueT] | Callable[[], Awaitable[ValueT]]) -> None:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from enum import Enum, StrEnum, auto
|
||||
from functools import cached_property
|
||||
from typing import Any, ClassVar, Self, override
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
class MsgLevelStyle(StrEnum):
|
||||
White = 'white'
|
||||
Green = 'green'
|
||||
Yellow = 'bright_yellow'
|
||||
Red = 'red'
|
||||
|
||||
|
||||
class MsgLevelType(Enum):
|
||||
MsgNone = auto()
|
||||
MsgInfo = auto()
|
||||
MsgWarning = auto()
|
||||
MsgError = auto()
|
||||
|
||||
def style(self) -> MsgLevelStyle:
|
||||
match self:
|
||||
case MsgLevelType.MsgNone:
|
||||
return MsgLevelStyle.White
|
||||
case MsgLevelType.MsgInfo:
|
||||
return MsgLevelStyle.Green
|
||||
case MsgLevelType.MsgWarning:
|
||||
return MsgLevelStyle.Yellow
|
||||
case MsgLevelType.MsgError:
|
||||
return MsgLevelStyle.Red
|
||||
|
||||
|
||||
@dataclass
|
||||
class PreviewResult:
|
||||
messages: list[tuple[str, MsgLevelType]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class MenuItem:
|
||||
text: str
|
||||
|
|
@ -18,7 +50,7 @@ class MenuItem:
|
|||
dependencies: list[str | Callable[[], bool]] = field(default_factory=list)
|
||||
dependencies_not: list[str] = field(default_factory=list)
|
||||
display_action: Callable[[Any], str] | None = None
|
||||
preview_action: Callable[[Self], str | None] | None = None
|
||||
preview_action: Callable[[Self], str | PreviewResult | None] | None = None
|
||||
key: str | None = None
|
||||
|
||||
_id: str = ''
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@
|
|||
"ntp": true,
|
||||
"offline": false,
|
||||
"packages": [],
|
||||
"parallel downloads": 0,
|
||||
"pacman_config": {
|
||||
"color": false,
|
||||
"parallel_downloads": 5
|
||||
},
|
||||
"profile_config": {
|
||||
"gfx_driver": "All open-source (default)",
|
||||
"greeter": "sddm",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ dependencies = [
|
|||
"pyparted==3.13.0",
|
||||
"pydantic==2.13.4",
|
||||
"cryptography==48.0.0",
|
||||
"textual==8.2.6",
|
||||
"textual==8.2.7",
|
||||
"markdown-it-py==4.0.0",
|
||||
"linkify-it-py==2.1.0",
|
||||
]
|
||||
|
|
@ -37,7 +37,7 @@ dev = [
|
|||
"mypy==2.1.0",
|
||||
"flake8==7.3.0",
|
||||
"pre-commit==4.6.0",
|
||||
"ruff==0.15.14",
|
||||
"ruff==0.15.16",
|
||||
"pylint==4.0.5",
|
||||
"pytest==9.0.3",
|
||||
"hypothesis>=6.152.4",
|
||||
|
|
@ -61,6 +61,7 @@ include-package-data = true
|
|||
"**/*.po",
|
||||
"**/*.pot",
|
||||
"**/*.json",
|
||||
"**/*.kdl",
|
||||
]
|
||||
|
||||
[tool.setuptools.package-dir]
|
||||
|
|
@ -69,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()))
|
||||
Loading…
Reference in New Issue