Compare commits
No commits in common. "master" and "v0.2.1-beta" have entirely different histories.
master
...
v0.2.1-bet
|
|
@ -1,13 +0,0 @@
|
|||
# http://editorconfig.org
|
||||
# See coding conventions in CONTRIBUTING.md
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
8
.flake8
8
.flake8
|
|
@ -1,8 +0,0 @@
|
|||
[flake8]
|
||||
count = True
|
||||
ignore = W191,W503,E704,E203
|
||||
max-complexity = 40
|
||||
max-line-length = 160
|
||||
show-source = True
|
||||
statistics = True
|
||||
exclude = .git,__pycache__,build,docs,actions-runner
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
# As per https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#example-of-a-codeowners-file
|
||||
|
||||
* @Torxed
|
||||
|
||||
# Any PKGBUILD changes should tag grazzolini
|
||||
/PKGBUILDs/ @grazzolini
|
||||
/PKGBUILD @grazzolini
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [archlinux]
|
||||
custom: ['https://archlinux.org/donate/']
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
name: bug report
|
||||
description: archinstall crashed or could not install properly?
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Please read the ~5 known issues first:
|
||||
https://archinstall.archlinux.page/help/known_issues.html
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
**NOTE: Always try the latest official ISO**
|
||||
|
||||
- type: input
|
||||
id: iso
|
||||
attributes:
|
||||
label: Which ISO version are you using?
|
||||
description: 'Always use the latest ISO version'
|
||||
placeholder: '"2024-12-01" or "Dec 1:st"'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug-report
|
||||
attributes:
|
||||
label: The installation log
|
||||
description: 'note: located at `/var/log/archinstall/install.log`'
|
||||
placeholder: |
|
||||
Hardware model detected: Dell Inc. Precision 7670; UEFI mode: True
|
||||
Processor model detected: 12th Gen Intel(R) Core(TM) i7-12850HX
|
||||
Memory statistics: 31111048 available out of 32545396 total installed
|
||||
Disk states before installing: {'blockdevices': ... }
|
||||
Testing connectivity to the Arch Linux mirrors ...
|
||||
...
|
||||
render: json
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
**Note**: Assuming you have network connectivity,
|
||||
you can easily upload the installation log and get a shareable URL by running:
|
||||
`archinstall share-log`
|
||||
|
||||
- type: textarea
|
||||
id: freeform
|
||||
attributes:
|
||||
label: describe the problem
|
||||
description: >
|
||||
Please describe your issue as best as you can.
|
||||
And please consider personal preferences vs what the recommended
|
||||
steps/values are in https://wiki.archlinux.org/title/Installation_guide
|
||||
as we try to abide by them as best we can.
|
||||
value: |
|
||||
#### Description of the issue
|
||||
|
||||
I was installing on X hardware ...
|
||||
|
||||
Then X Y Z happened and archinstall crashed ...
|
||||
|
||||
#### Virtual machine config:
|
||||
|
||||
```xml
|
||||
<domain type="kvm">
|
||||
<name>my-arch-machine</name>
|
||||
...
|
||||
</devices>
|
||||
</domain>
|
||||
```
|
||||
|
||||
```console
|
||||
/usr/bin/qemu-system-x86_64 -name guest=my-arch-machine,debug-threads=on -object ...
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
**Note**: Feel free to modify the textarea above as you wish.
|
||||
But it will grately help us in testing if we can generate the specific qemu command line,
|
||||
for instance via:
|
||||
`sudo virsh domxml-to-native qemu-argv --domain my-arch-machine`
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
name: feature request
|
||||
description: a new feature!
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Please read our short mission statement before requesting more features:
|
||||
https://github.com/archlinux/archinstall?tab=readme-ov-file#mission-statement
|
||||
|
||||
- type: textarea
|
||||
id: freeform
|
||||
attributes:
|
||||
label: describe the request
|
||||
description: >
|
||||
Feel free to write any feature you think others might benefit from:
|
||||
validations:
|
||||
required: true
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: Bandit security checkup
|
||||
jobs:
|
||||
bandit:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- run: pacman --noconfirm -Syu bandit
|
||||
- name: Security checkup with Bandit
|
||||
run: bandit -r archinstall || exit 0
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: flake8 linting
|
||||
jobs:
|
||||
flake8:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
|
||||
- run: pip install --break-system-packages --upgrade pip
|
||||
# this will install the exact version of flake8 that is in the pyproject.toml file
|
||||
- name: Install archinstall dependencies
|
||||
run: pip install --break-system-packages .[dev]
|
||||
- run: python --version
|
||||
- run: flake8 --version
|
||||
- name: Lint with flake8
|
||||
run: flake8
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
name: documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
- name: Install pre-dependencies
|
||||
run: |
|
||||
pacman -Sy --noconfirm tree git python-pyparted python-setuptools python-sphinx python-sphinx_rtd_theme python-build python-installer python-wheel
|
||||
- name: Sphinx build
|
||||
run: |
|
||||
sphinx-build docs _build
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
publish_branch: gh-pages
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: _build/
|
||||
force_orphan: true
|
||||
enable_jekyll: false # This is required to preserve _static (and thus the theme)
|
||||
cname: archinstall.archlinux.page
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# This workflow will build an Arch Linux ISO file with the commit on it
|
||||
|
||||
name: Build Arch ISO 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: ./test_tooling/mkarchiso/build_iso.sh
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: Arch Live ISO
|
||||
path: /tmp/archlive/out/*.iso
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: mypy type checking
|
||||
jobs:
|
||||
mypy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
|
||||
- run: pip install --break-system-packages --upgrade pip
|
||||
# this will install the exact version of mypy that is in the pyproject.toml file
|
||||
- name: Install archinstall dependencies
|
||||
run: pip install --break-system-packages .[dev]
|
||||
- run: python --version
|
||||
- run: mypy --version
|
||||
- name: run mypy
|
||||
run: mypy --config-file pyproject.toml
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: Pylint linting
|
||||
jobs:
|
||||
pylint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
|
||||
- run: pip install --break-system-packages --upgrade pip
|
||||
- name: Install Pylint
|
||||
run: pip install --break-system-packages .[dev]
|
||||
- run: python --version
|
||||
- run: pylint --version
|
||||
- name: Lint with Pylint
|
||||
run: pylint .
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: pytest test validation
|
||||
jobs:
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python-pip python-pyparted pkgconfig gcc
|
||||
- run: pip install --break-system-packages --upgrade pip
|
||||
- name: Install archinstall dependencies
|
||||
run: pip install --break-system-packages .[dev]
|
||||
- name: Test with pytest
|
||||
run: pytest
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# This workflow will build Python packages on every commit.
|
||||
|
||||
name: Build archinstall
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python-uv python-setuptools python-pip
|
||||
pacman --noconfirm -Sy python-pyparted python-pydantic python-textual
|
||||
- name: Remove existing archinstall (if any)
|
||||
run:
|
||||
uv pip uninstall archinstall --break-system-packages --system
|
||||
- name: Build archinstall
|
||||
run: uv build --no-build-isolation --wheel
|
||||
- name: Install archinstall
|
||||
run: |
|
||||
uv pip install dist/*.whl --break-system-packages --system --no-build --no-deps
|
||||
- name: Run archinstall
|
||||
run: |
|
||||
python -V
|
||||
archinstall --script guided -v
|
||||
archinstall --script only_hd -v
|
||||
archinstall --script minimal -v
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: archinstall
|
||||
path: dist/*
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# This workflow will upload a Python Package when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: Upload archinstall to PyPi
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# IMPORTANT: this permission is mandatory for Trusted Publishing
|
||||
id-token: write
|
||||
container:
|
||||
image: archlinux/archlinux:latest
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Prepare arch
|
||||
run: |
|
||||
pacman-key --init
|
||||
pacman --noconfirm -Sy archlinux-keyring
|
||||
pacman --noconfirm -Syyu
|
||||
pacman --noconfirm -Sy python python-uv python-setuptools python-pip python-pyparted python-pydantic python-textual
|
||||
- name: Build archinstall
|
||||
run: |
|
||||
uv build --no-build-isolation --wheel
|
||||
- name: Publish archinstall to PyPi
|
||||
run: |
|
||||
uv publish --trusted-publishing always
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: ruff check formatting
|
||||
jobs:
|
||||
ruff_format_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
|
||||
- run: ruff format --diff
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
on: [ push, pull_request ]
|
||||
name: ruff check linting
|
||||
jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# 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
|
||||
|
|
@ -1,47 +1,2 @@
|
|||
**/**__pycache__
|
||||
SAFETY_LOCK
|
||||
**/**old.*
|
||||
**/**.img
|
||||
**/**pwfile
|
||||
**/**build
|
||||
**/**dist
|
||||
**/**.egg*
|
||||
**/**.sh
|
||||
!archinstall/locales/locales_generator.sh
|
||||
**/**.egg-info/
|
||||
**/**build/
|
||||
**/**src/
|
||||
**/**pkg/
|
||||
**/**dist/
|
||||
**/**archinstall.build/
|
||||
**/**archinstall-v*/
|
||||
**/**.pkg.*.xz
|
||||
**/**archinstall-*.tar.gz
|
||||
**/**.zst
|
||||
**/**.network
|
||||
**/**.target
|
||||
**/**.qcow2
|
||||
**/**.log
|
||||
**/**.fd
|
||||
/test*.py
|
||||
**/archiso
|
||||
/guided.py
|
||||
venv
|
||||
.venv
|
||||
.idea/**
|
||||
**/install.log
|
||||
.DS_Store
|
||||
**/cmd_history.txt
|
||||
**/*.*~
|
||||
/*.sig
|
||||
/*.json
|
||||
requirements.txt
|
||||
/.gitconfig
|
||||
/actions-runner
|
||||
/cmd_output.txt
|
||||
node_modules/
|
||||
uv.lock
|
||||
test_tooling/mkosi/mkosi.output/*image*
|
||||
test_tooling/mkosi/mkosi.cache/**
|
||||
test_tooling/mkosi/mkosi.tools/**
|
||||
test_tooling/mkosi/mkosi.tools.manifest
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
# This file contains GitLab CI/CD configuration for the ArchInstall project.
|
||||
# It defines several jobs that get run when a new commit is made, and is comparable to the GitHub workflows.
|
||||
# There is an expectation that a runner exists that has the --privileged flag enabled for the build ISO job to run correctly.
|
||||
# These jobs should leverage the same tag as that runner. If necessary, change the tag from 'docker' to the one it uses.
|
||||
# All jobs will be run in the official archlinux container image, so we will declare that here.
|
||||
|
||||
image: archlinux/archlinux:latest
|
||||
|
||||
# This can be used to handle common actions. In this case, we do a pacman -Sy to make sure repos are ready to use.
|
||||
before_script:
|
||||
- pacman -Sy
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
- publish
|
||||
|
||||
mypy:
|
||||
stage: lint
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- pacman --noconfirm -Syu python mypy
|
||||
- mypy . --ignore-missing-imports || exit 0
|
||||
|
||||
flake8:
|
||||
stage: lint
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- pacman --noconfirm -Syu python python-pip
|
||||
- python -m pip install --upgrade pip
|
||||
- pip install flake8
|
||||
- flake8 . --count --select=E9,F63,F7 --show-source --statistics
|
||||
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
# We currently do not have unit tests implemented but this stage is written in anticipation of their future usage.
|
||||
# When a stage name is preceded with a '.' it's treated as "disabled" by GitLab and is not executed, so it's fine for it to be declared.
|
||||
.pytest:
|
||||
stage: test
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- pacman --noconfirm -Syu python python-pip
|
||||
- python -m pip install --upgrade pip
|
||||
- pip install pytest
|
||||
- pytest
|
||||
|
||||
# This stage might fail with exit code 137 on a shared runner. This is probably due to the CPU/memory consumption needed to run the build.
|
||||
build_iso:
|
||||
stage: build
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- pwd
|
||||
- find .
|
||||
- cat /etc/os-release
|
||||
- mkdir -p /tmp/archlive/airootfs/root/archinstall-git; cp -r . /tmp/archlive/airootfs/root/archinstall-git
|
||||
- echo "pip uninstall archinstall -y; cd archinstall-git; python setup.py install" > /tmp/archlive/airootfs/root/.zprofile
|
||||
- echo "echo \"This is an unofficial ISO for development and testing of archinstall. No support will be provided.\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- echo "echo \"This ISO was built from Git SHA $CI_COMMIT_SHA\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- echo "echo \"Type archinstall to launch the installer.\"" >> /tmp/archlive/airootfs/root/.zprofile
|
||||
- cat /tmp/archlive/airootfs/root/.zprofile
|
||||
- pacman --noconfirm -S git archiso
|
||||
- cp -r /usr/share/archiso/configs/releng/* /tmp/archlive
|
||||
- echo -e "git\npython\npython-pip\npython-setuptools" >> /tmp/archlive/packages.x86_64
|
||||
- find /tmp/archlive
|
||||
- cd /tmp/archlive; mkarchiso -v -w work/ -o out/ ./
|
||||
artifacts:
|
||||
name: "Arch Live ISO"
|
||||
paths:
|
||||
- /tmp/archlive/out/*.iso
|
||||
expire_in: 1 week
|
||||
|
||||
## This job only runs when a tag is created on the master branch. This is because we do not want to try to publish to PyPi every time we commit.
|
||||
## The following CI/CD variables need to be set to the PyPi username and password in the GitLab project's settings for this stage to work.
|
||||
# * FLIT_USERNAME
|
||||
# * FLIT_PASSWORD
|
||||
publish_pypi:
|
||||
stage: publish
|
||||
tags:
|
||||
- docker
|
||||
script:
|
||||
- pacman --noconfirm -S python python-pip
|
||||
- python -m pip install --upgrade pip
|
||||
- pip install setuptools wheel flit
|
||||
- flit
|
||||
only:
|
||||
- tags
|
||||
except:
|
||||
- branches
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
default_stages: ['pre-commit']
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.14
|
||||
hooks:
|
||||
# fix unused imports and sort them
|
||||
- id: ruff
|
||||
args: ["--extend-select", "I", "--fix"]
|
||||
# format the code
|
||||
- id: ruff-format
|
||||
# run the linter
|
||||
- id: ruff
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
# general hooks:
|
||||
- id: check-added-large-files # Prevent giant files from being committed
|
||||
args: ['--maxkb=5000']
|
||||
- id: check-merge-conflict # Check for files that contain merge conflict strings
|
||||
- id: check-symlinks # Checks for symlinks which do not point to anything
|
||||
- id: check-yaml # Attempts to load all yaml files to verify syntax
|
||||
- id: destroyed-symlinks # Detects symlinks which are changed to regular files
|
||||
- id: detect-private-key # Checks for the existence of private keys
|
||||
# Python specific hooks:
|
||||
- id: check-ast # Simply check whether files parse as valid python
|
||||
- id: check-docstring-first # Checks for a common error of placing code before the docstring
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: [--config=.flake8]
|
||||
fail_fast: true
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [
|
||||
'--config-file=pyproject.toml'
|
||||
]
|
||||
fail_fast: true
|
||||
additional_dependencies:
|
||||
- pydantic
|
||||
- pytest
|
||||
- hypothesis
|
||||
- cryptography
|
||||
- textual
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: pylint
|
||||
language: system
|
||||
types: [python]
|
||||
fail_fast: true
|
||||
require_serial: true
|
||||
6
.pypirc
6
.pypirc
|
|
@ -1,6 +0,0 @@
|
|||
[distutils]
|
||||
index-servers =
|
||||
pypi
|
||||
|
||||
[pypi]
|
||||
repository = https://upload.pypi.org/legacy/
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
builder: html
|
||||
configuration: docs/conf.py
|
||||
fail_on_warning: true
|
||||
|
||||
build:
|
||||
os: "ubuntu-22.04"
|
||||
tools:
|
||||
python: "3.12"
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
# Contributing to archinstall
|
||||
|
||||
Any contributions through pull requests are welcome as this project aims to be a community based project to ease some Arch Linux installation steps.
|
||||
Bear in mind that in the future this repo might be transferred to the official [GitLab repo under Arch Linux](http://gitlab.archlinux.org/archlinux/) *(if GitLab becomes open to the general public)*.
|
||||
|
||||
Therefore, guidelines and style changes to the code might come into effect as well as guidelines surrounding bug reporting and discussions.
|
||||
|
||||
## Branches
|
||||
|
||||
`master` is currently the default branch, and that's where all future feature work is being done, this means that `master` is a living entity and will most likely never be in a fully stable state.
|
||||
For stable releases, please see the tagged commits.
|
||||
|
||||
Patch releases will be done against their own branches, branched from stable tagged releases and will be named according to the version it will become on release.
|
||||
*(Patches to `v2.1.4` will be done on branch `v2.1.5` for instance)*.
|
||||
|
||||
## Discussions
|
||||
|
||||
Currently, questions, bugs and suggestions should be reported through [GitHub issue tracker](https://github.com/archlinux/archinstall/issues).<br>
|
||||
For less formal discussions there is also an [archinstall Discord server](https://discord.gg/aDeMffrxNg).
|
||||
|
||||
## Coding convention
|
||||
|
||||
ArchInstall's goal is to follow [PEP8](https://www.python.org/dev/peps/pep-0008/) as best as it can with some minor exceptions.<br>
|
||||
|
||||
The exceptions to PEP8 are:
|
||||
|
||||
* Archinstall uses [tabs instead of spaces](https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces) simply to make it
|
||||
easier for non-IDE developers to navigate the code *(Tab display-width should be equal to 4 spaces)*. Exception to the
|
||||
rule are comments that need fine-tuned indentation for documentation purposes.
|
||||
* [Line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) a maximum line length is enforced via flake8 with 160 characters
|
||||
* Archinstall should always be saved with **Unix-formatted line endings** and no other platform-specific formats.
|
||||
* [String quotes](https://www.python.org/dev/peps/pep-0008/#string-quotes) follow PEP8, the exception being when
|
||||
creating formatted strings, double-quoted strings are *preferred* but not required on the outer edges *(
|
||||
Example: `f"Welcome {name}"` rather than `f'Welcome {name}'`)*.
|
||||
|
||||
Most of these style guidelines have been put into place after the fact *(in an attempt to clean up the code)*.<br>
|
||||
There might therefore be older code which does not follow the coding convention and the code is subject to change.
|
||||
|
||||
## Git hooks
|
||||
|
||||
`archinstall` ships pre-commit hooks that make it easier to run checks such as `mypy`, `ruff check`, and `flake8` locally.
|
||||
The checks are listed in `.pre-commit-config.yaml` and can be installed via
|
||||
```
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
This will install the pre-commit hook and run it every time a `git commit` is executed.
|
||||
|
||||
## Documentation
|
||||
|
||||
If you'd like to contribute to the documentation, refer to [this guide](docs/README.md) on how to build the documentation locally.
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
Archinstall uses GitHub's pull-request workflow and all contributions in terms of code should be done through pull requests.<br>
|
||||
|
||||
Anyone interested in archinstall may review your code. One of the core developers will merge your pull request when they
|
||||
think it is ready. For every pull request, we aim to promptly either merge it or say why it is not yet ready; if you go
|
||||
a few days without a reply, please feel free to ping the thread by adding a new comment.
|
||||
|
||||
To get your pull request merged sooner, you should explain why you are making the change. For example, you can point to
|
||||
a code sample that is outdated in terms of Arch Linux command lines. It is also helpful to add links to online
|
||||
documentation or to the implementation of the code you are changing.
|
||||
|
||||
Also, do not squash your commits after you have submitted a pull request, as this erases context during review. We will
|
||||
squash commits when the pull request is merged.
|
||||
|
||||
Maintainer:
|
||||
* Anton Hvornum ([@Torxed](https://github.com/Torxed))
|
||||
|
||||
[Contributors](https://github.com/archlinux/archinstall/graphs/contributors)
|
||||
674
LICENSE
674
LICENSE
|
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Copyright (c) 2018-present Anton Hvornum (https://github.com/Torxed).
|
||||
|
||||
Permission is hereby granted for non-commercial use, free of charge, to whomever (including
|
||||
non-profit organizations not owned by commercial entities) obtainins a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute copies of the Software for non-commercial usecases, and to permit persons
|
||||
to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software and are subject to:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
For commercial purposes, contact the creator via any of the contact options listed under the following:
|
||||
* https://github.com/Torxed
|
||||
* https://hvornum.se/
|
||||
89
PKGBUILD
89
PKGBUILD
|
|
@ -1,89 +0,0 @@
|
|||
# Maintainer: David Runge <dvzrv@archlinux.org>
|
||||
# Maintainer: Giancarlo Razzolini <grazzolini@archlinux.org>
|
||||
# Maintainer: Anton Hvornum <torxed@archlinux.org>
|
||||
# Contributor: Anton Hvornum <anton@hvornum.se>
|
||||
# Contributor: demostanis worlds <demostanis@protonmail.com>
|
||||
|
||||
pkgname=archinstall
|
||||
pkgver=4.3
|
||||
pkgrel=1
|
||||
pkgdesc="Just another guided/automated Arch Linux installer with a twist"
|
||||
arch=(any)
|
||||
url="https://github.com/archlinux/archinstall"
|
||||
license=(GPL-3.0-only)
|
||||
depends=(
|
||||
'arch-install-scripts'
|
||||
'btrfs-progs'
|
||||
'coreutils'
|
||||
'cryptsetup'
|
||||
'dosfstools'
|
||||
'e2fsprogs'
|
||||
'glibc'
|
||||
'kbd'
|
||||
'libcrypt.so'
|
||||
'libxcrypt'
|
||||
'pciutils'
|
||||
'procps-ng'
|
||||
'python'
|
||||
'python-cryptography'
|
||||
'python-pydantic'
|
||||
'python-pyparted'
|
||||
'python-textual'
|
||||
'python-markdown-it-py'
|
||||
'python-linkify-it-py'
|
||||
'systemd'
|
||||
'util-linux'
|
||||
'xfsprogs'
|
||||
'lvm2'
|
||||
'f2fs-tools'
|
||||
'libfido2'
|
||||
)
|
||||
makedepends=(
|
||||
'python-build'
|
||||
'python-installer'
|
||||
'python-setuptools'
|
||||
'python-sphinx'
|
||||
'python-wheel'
|
||||
'python-sphinx_rtd_theme'
|
||||
'python-pylint'
|
||||
'python-pylint-pydantic'
|
||||
'ruff'
|
||||
)
|
||||
optdepends=(
|
||||
'python-systemd: Adds journald logging'
|
||||
)
|
||||
provides=(python-archinstall archinstall)
|
||||
conflicts=(python-archinstall archinstall-git)
|
||||
replaces=(python-archinstall archinstall-git)
|
||||
source=(
|
||||
$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/$pkgver.tar.gz
|
||||
$pkgname-$pkgver.tar.gz.sig::$url/releases/download/$pkgver/$pkgname-$pkgver.tar.gz.sig
|
||||
)
|
||||
sha512sums=()
|
||||
b2sums=()
|
||||
validpgpkeys=('8AA2213C8464C82D879C8127D4B58E897A929F2E') # torxed@archlinux.org
|
||||
|
||||
check() {
|
||||
cd $pkgname-$pkgver
|
||||
ruff check
|
||||
}
|
||||
|
||||
pkgver() {
|
||||
cd $pkgname-$pkgver
|
||||
|
||||
awk '$1 ~ /^__version__/ {gsub("\"", ""); print $3}' archinstall/__init__.py
|
||||
}
|
||||
|
||||
build() {
|
||||
cd $pkgname-$pkgver
|
||||
|
||||
python -m build --wheel --no-isolation
|
||||
PYTHONDONTWRITEBYTECODE=1 make man -C docs
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$pkgname-$pkgver"
|
||||
|
||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||
install -vDm 644 docs/_build/man/archinstall.1 -t "$pkgdir/usr/share/man/man1/"
|
||||
}
|
||||
321
README.md
321
README.md
|
|
@ -1,269 +1,118 @@
|
|||
<!-- <div align="center"> -->
|
||||
<img src="https://github.com/archlinux/archinstall/raw/master/docs/logo.png" alt="drawing" width="200"/>
|
||||
# archinstall
|
||||
Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer.
|
||||
|
||||
<!-- </div> -->
|
||||
# Arch Installer
|
||||
[](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml)
|
||||
Pre-built ISO's can be found here which autostarts this script *(in guided mode)*: https://hvornum.se/archiso/
|
||||
|
||||
Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist.
|
||||
The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium or from an existing installation)*.
|
||||
# How-to / Usecases
|
||||
|
||||
* archinstall [discord](https://discord.gg/aDeMffrxNg) server
|
||||
* archinstall [#archinstall:matrix.org](https://matrix.to/#/#archinstall:matrix.org) Matrix channel
|
||||
* archinstall [#archinstall@irc.libera.chat:6697](https://web.libera.chat/?channel=#archinstall)
|
||||
* archinstall [documentation](https://archinstall.archlinux.page/)
|
||||
## Run on Live-CD (Binary)
|
||||
|
||||
# Installation & Usage
|
||||
> [!TIP]
|
||||
> In the ISO you are root by default. Use sudo if running from an existing system.
|
||||
# wget https://gzip.app/archinstall
|
||||
# chmod +x archinstall; ./archinstall
|
||||
|
||||
```shell
|
||||
pacman-key --init
|
||||
pacman -Sy archinstall
|
||||
archinstall
|
||||
```
|
||||
## Run on Live-CD (Python):
|
||||
|
||||
Alternative ways to install are `git clone` the repository (and is better since you get the latest code regardless of [build date](https://archlinux.org/packages/?sort=&q=archinstall)) or `pip install --upgrade archinstall`.
|
||||
# wget https://raw.githubusercontent.com/Torxed/archinstall/master/archinstall.py
|
||||
# pacman -S --noconfirm python; python archinstall.py
|
||||
|
||||
## Upgrade `archinstall` on live Arch ISO image
|
||||
This will start a guided install.<br>
|
||||
Add `--default` for a unattended minimalistic installation of Arch Linux.
|
||||
|
||||
Upgrading archinstall on the ISO needs to be done via a full system upgrade using
|
||||
> **Creating your own ISO:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on how to create your own ISO or use a pre-built [guided ISO](https://hvornum.se/archiso/) to skip the python installation step, or to create auto-installing ISO templates. Further down are examples and cheat sheets on how to create different live ISO's.
|
||||
|
||||
```shell
|
||||
pacman -Syu
|
||||
```
|
||||
# Features
|
||||
|
||||
When booting from a live USB, the space on the ramdisk is limited and may not be sufficient to allow running a re-installation or upgrade of the installer.
|
||||
In case one runs into this issue, any of the following can be used
|
||||
* User guided install of Arch Linux *(Like most other distros have)*
|
||||
* `AUR` package support.
|
||||
* Unattended install of Arch Linux
|
||||
* Profile / Template based installs
|
||||
* Full disk encryption, locale/region settings and customizable application selection
|
||||
* YubiKey support for disk and root password *(TBD / next release)*
|
||||
* <strike>Supports offline-installation of Arch Linux</strike>
|
||||
* Never creates or leave post-install/service scripts *(usually used to finalize databases etc)*
|
||||
|
||||
* Resize the root partition https://wiki.archlinux.org/title/Archiso#Adjusting_the_size_of_the_root_file_system
|
||||
* Specify the boot parameter copytoram=y (https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio-archiso/-/blob/master/docs/README.bootparams#L26) which will copy the root filesystem to tmpfs
|
||||
**Default Installation Contains:** Encrypts drive, btrfs filesystem, `linux` kernel, nano, wpa_supplicant *(and dialog)*
|
||||
|
||||
## Running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer
|
||||
# Examples:
|
||||
|
||||
Assuming you are on an Arch Linux live-ISO or installed via `pip`, `archinstall` will use the `guided` script by default
|
||||
```shell
|
||||
archinstall
|
||||
```
|
||||
similar goes for running the [guided](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) installer using `git
|
||||
* `./archinstall --profile=workstation --drive=/dev/sda` - Installs the [workstation](https://github.com/Torxed/archinstall/blob/master/deployments/workstation.json) template on the drive `/dev/sda`
|
||||
|
||||
```shell
|
||||
git clone https://github.com/archlinux/archinstall
|
||||
cd archinstall
|
||||
python -m archinstall $@
|
||||
```
|
||||
# [Build a Arch Linux ISO to autorun archinstall](https://github.com/Torxed/archinstall/wiki/Autorun-on-Arch-Live-CD)
|
||||
|
||||
To run alternative scripts using the `--script` parameter
|
||||
More options for the built ISO:
|
||||
|
||||
```
|
||||
archinstall --script <name>
|
||||
```
|
||||
### [Unattended install of a profile](https://github.com/Torxed/archinstall/wiki/Unattended-install-of-a-profile)
|
||||
|
||||
#### Advanced
|
||||
Some additional options that most users do not need are hidden behind the `--advanced` flag and all options/args can be consulted through `-h` or `--help`.
|
||||
### [User guided install (DEFAULT)](https://github.com/Torxed/archinstall/wiki/User-guided-installation-(DEFAULT))
|
||||
|
||||
## Running from a declarative configuration file or URL
|
||||
### [Custom web-server for deployment profiles](https://github.com/Torxed/archinstall/wiki/Custom-web-server-for-deployment-profiles)
|
||||
|
||||
`archinstall` can be run with a JSON configuration file. There are 2 different configuration files to consider,
|
||||
the `user_configuration.json` contains all general installation configuration, whereas the `user_credentials.json`
|
||||
contains the sensitive user configuration such as user password, root password, and encryption password.
|
||||
### [Rerunning the installation](https://github.com/Torxed/archinstall/wiki/Rerunning-the-installation)
|
||||
|
||||
An example of the user configuration file can be found here
|
||||
[configuration file](https://github.com/archlinux/archinstall/blob/master/examples/config-sample.json)
|
||||
and an example of the credentials configuration here
|
||||
[credentials file](https://github.com/archlinux/archinstall/blob/master/examples/creds-sample.json).
|
||||
# Some parameters you can give it
|
||||
|
||||
**HINT:** The configuration files can be auto-generated by starting `archinstall`, configuring all desired menu
|
||||
points and then going to `Save configuration`.
|
||||
--drive=</dev/sdX>
|
||||
Which drive to install arch on, if absent, the first disk under /dev/ is used
|
||||
|
||||
--size=100% (Default)
|
||||
Sets the size of the root filesystem (btrfs)
|
||||
|
||||
--start=513MiB (Default)
|
||||
Sets the starting location of the root partition
|
||||
(TODO: /boot will take up space from 1MiB - <start>, make sure boot is no larger than 513MiB)
|
||||
|
||||
--password=0000 (Default)
|
||||
Which disk password to use,
|
||||
--password="<STDIN>" for prompt of password
|
||||
--password="<YUBIKEY>" for setting a unique password on the YubiKey and use that as a password
|
||||
(NOTE: This will wipe/replace slot 1 on the YubiKey)
|
||||
|
||||
To load the configuration file into `archinstall` run the following command
|
||||
```shell
|
||||
archinstall --config <path to user config file or URL> --creds <path to user credentials config file or URL>
|
||||
```
|
||||
--aur-support (default)
|
||||
|
||||
### Credentials configuration file encryption
|
||||
By default, all user account credentials are hashed with `yescrypt` and only the hash is stored in the saved `user_credentials.json` file.
|
||||
This is not possible for disk encryption password which needs to be stored in plaintext to be able to apply it.
|
||||
--pwfile=/tmp/diskpw (Default)
|
||||
Which file to store the disk encryption password while sending it to cryptsetup
|
||||
|
||||
--hostname=Arcinstall (Default)
|
||||
Sets the hostname of the box
|
||||
|
||||
--country=SE (Default)
|
||||
Default mirror allocation for fetching packages.
|
||||
|
||||
--packages='' (Default)
|
||||
Which additional packages to install, defaults to none.
|
||||
(Space separated as it's passed unchanged to `pacstrap`
|
||||
|
||||
--user=<name>
|
||||
Adds an additional username to the system (default group Wheel)
|
||||
|
||||
--post=reboot (Default)
|
||||
After a successful install, reboots into the system. Use --post=stay to not reboot.
|
||||
|
||||
However, when selecting to save configuration files, `archinstall` will prompt for the option to encrypt the `user_credentials.json` file content.
|
||||
A prompt will require to enter a encryption password to encrypt the file. When providing an encrypted `user_configuration.json` as a argument with `--creds <user_credentials.json>`
|
||||
there are multiple ways to provide the decryption key:
|
||||
* Provide the decryption key via the command line argument `--creds-decryption-key <password>`
|
||||
* Store the encryption key in the environment variable `ARCHINSTALL_CREDS_DECRYPTION_KEY` which will be read automatically
|
||||
* If none of the above is provided a prompt will be shown to enter the decryption key manually
|
||||
--default
|
||||
This parameter causes the installation script to install arch unattended on the first disk
|
||||
|
||||
--profile=<name>
|
||||
For instance, --profile=workstation will install the workstation profile.
|
||||
|
||||
# Help or Issues
|
||||
--profiles-path=https://example.com/profiles
|
||||
Changes the default path the script looks for deployment profiles.
|
||||
The default path is 'https://raw.githubusercontent.com/Torxed/archinstall/master/deployments'
|
||||
|
||||
If you come across any issues, kindly submit your issue here on GitHub or post your query in the
|
||||
[discord](https://discord.gg/aDeMffrxNg) help channel.
|
||||
--rerun="Name of step in profile"
|
||||
Enables you to skip the format, encryption and base install steps.
|
||||
And head straight for a step in the profile specified.
|
||||
(Useful for debugging a step in your profile)
|
||||
|
||||
When submitting an issue, please:
|
||||
* Provide the stacktrace of the output if applicable
|
||||
* Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you!
|
||||
* To upload the log from the ISO image and get a shareable URL, run<br>
|
||||
```shell
|
||||
archinstall share-log
|
||||
```
|
||||
--localtime="Europe/Stockholm" (Default if --country=SE, otherwise GMT+0)
|
||||
Specify a localtime you're used to.
|
||||
|
||||
Deployment profile structs support all the above parameters and more, for instance, custom arguments with string formatting.
|
||||
See [deployments/workstation.json](https://github.com/Torxed/archinstall/blob/net-deploy/deployments/workstation.json) for examples.
|
||||
|
||||
# Available Languages
|
||||
# Contact
|
||||
|
||||
Archinstall is available in different languages which have been contributed and are maintained by the community.
|
||||
The language can be switched inside the installer (first menu entry). Bear in mind that not all languages provide
|
||||
full translations as we rely on contributors to do the translations. Each language has an indicator that shows
|
||||
how much has been translated.
|
||||
IRC: `#archinstall@FreeNode`
|
||||
|
||||
Any contributions to the translations are more than welcome,
|
||||
to get started please follow [the guide](https://github.com/archlinux/archinstall/blob/master/archinstall/locales/README.md)
|
||||
## End note
|
||||
|
||||
## Fonts
|
||||
The ISO does not ship with all fonts needed for different languages.
|
||||
Fonts that use a different character set than Latin will not be displayed correctly. If those languages
|
||||
want to be selected then a proper font has to be set manually in the console.
|
||||
|
||||
All available console fonts can be found in `/usr/share/kbd/consolefonts` and set with `setfont LatGrkCyr-8x16`.
|
||||
|
||||
|
||||
# Scripting your own installation
|
||||
|
||||
## Scripting interactive installation
|
||||
|
||||
For an example of a fully scripted, interactive installation please refer to the example
|
||||
[interactive_installation.py](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py)
|
||||
|
||||
|
||||
> **To create your own ISO with this script in it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on creating your own ISO.
|
||||
|
||||
## Script non-interactive automated installation
|
||||
|
||||
For an example of a fully scripted, automated installation please refer to the example
|
||||
[full_automated_installation.py](https://github.com/archlinux/archinstall/blob/master/examples/full_automated_installation.py)
|
||||
|
||||
# Profiles
|
||||
|
||||
`archinstall` comes with a set of pre-configured profiles available for selection during the installation process.
|
||||
|
||||
- [Desktop](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/desktops)
|
||||
- [Server](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles/servers)
|
||||
|
||||
The profiles' definitions and the packages they will install can be directly viewed in the menu, or
|
||||
[default profiles](https://github.com/archlinux/archinstall/tree/master/archinstall/default_profiles)
|
||||
|
||||
|
||||
# Testing
|
||||
|
||||
## Using a Live ISO Image
|
||||
|
||||
If you want to test a commit, branch, or bleeding edge release from the repository using the standard Arch Linux Live ISO image,
|
||||
replace the archinstall version with a newer one and execute the subsequent steps defined below.
|
||||
|
||||
1. You need a working network connection
|
||||
2. Install the build requirements with `pacman -Sy; pacman -S git python-pip gcc pkgconf`
|
||||
*(note that this may or may not work depending on your RAM and current state of the squashfs maximum filesystem free space)*
|
||||
3. Uninstall the previous version of archinstall with `pip uninstall --break-system-packages archinstall`
|
||||
4. Now clone the latest repository with `git clone https://github.com/archlinux/archinstall`
|
||||
5. Enter the repository with `cd archinstall`
|
||||
*At this stage, you can choose to check out a feature branch for instance with `git checkout v2.3.1-rc1`*
|
||||
6. To run the source code, there are 2 different options:
|
||||
- Run a specific branch version from source directly using `python -m archinstall`, in most cases this will work just fine, the
|
||||
rare case it will not work is if the source has introduced any new dependencies that are not installed yet
|
||||
- Installing the branch version with `pip install --break-system-packages .` and `archinstall`
|
||||
|
||||
## Without a Live ISO Image
|
||||
|
||||
To test this without a live ISO, the simplest approach is to use a local image and create a loop device.<br>
|
||||
This can be done by installing `pacman -S arch-install-scripts util-linux` locally and doing the following:
|
||||
|
||||
# truncate -s 20G testimage.img
|
||||
# losetup --partscan --show ./testimage.img
|
||||
# pip install --upgrade archinstall
|
||||
# python -m archinstall --script guided
|
||||
# qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd
|
||||
|
||||
This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.<br>
|
||||
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~<br>
|
||||
*(You'd actually need to do some EFI magic in order to point the EFI vars to the partition 0 in the test medium, so this won't work entirely out of the box, but that gives you a general idea of what we're going for here)*
|
||||
|
||||
There's also a [Building and Testing](https://github.com/archlinux/archinstall/wiki/Building-and-Testing) guide.<br>
|
||||
It will go through everything from packaging, building and running *(with qemu)* the installer against a dev branch.
|
||||
|
||||
## Boot an Arch ISO image in a VM
|
||||
|
||||
You may want to boot an ISO image in a VM to test `archinstall` in there.
|
||||
|
||||
* Download the latest [Arch ISO](https://archlinux.org/download/)
|
||||
* Use the the below command to boot the ISO in a VM
|
||||
|
||||
```
|
||||
qemu-system-x86_64 -enable-kvm \
|
||||
-machine q35,accel=kvm -device intel-iommu \
|
||||
-cpu host -m 4096 -boot order=d \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
|
||||
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw
|
||||
```
|
||||
|
||||
HINT: For espeakup support
|
||||
```
|
||||
qemu-system-x86_64 -enable-kvm \
|
||||
-machine q35,accel=kvm -device intel-iommu \
|
||||
-cpu host -m 4096 -boot order=d \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
|
||||
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw \
|
||||
-device intel-hda -device hda-duplex,audiodev=snd0 \
|
||||
-audiodev pa,id=snd0,server=/run/user/1000/pulse/native
|
||||
```
|
||||
|
||||
|
||||
# FAQ
|
||||
|
||||
## AUR
|
||||
|
||||
`archinstall` will not offer or bundle AUR helpers or AUR packages due to a current consensus. This is not any individual developers decision. The reasons and discussions for this stance on the topic can be found on our mailing list thread: [(optional) AUR helper in archinstall](https://lists.archlinux.org/archives/list/arch-dev-public@lists.archlinux.org/thread/VYOULH2GOJLFM2BXOFLWH3D754YXFPSL/).
|
||||
|
||||
## Keyring out-of-date
|
||||
For a description of the problem see https://archinstall.archlinux.page/help/known_issues.html#keyring-is-out-of-date-2213 and discussion in issue https://github.com/archlinux/archinstall/issues/2213.
|
||||
|
||||
For a quick fix the below command will install the latest keyrings
|
||||
|
||||
```pacman -Sy archlinux-keyring```
|
||||
|
||||
## How to dual boot with Windows
|
||||
|
||||
To install Arch Linux alongside an existing Windows installation using `archinstall`, follow these steps:
|
||||
|
||||
1. Ensure some unallocated space is available for the Linux installation after the Windows installation.
|
||||
2. Boot into the ISO and run `archinstall`.
|
||||
3. Choose `Disk configuration` -> `Manual partitioning`.
|
||||
4. Select the disk on which Windows resides.
|
||||
5. Select `Create a new partition`.
|
||||
6. Choose a filesystem type.
|
||||
7. Determine the start and end sectors for the new partition location (values can be suffixed with various units).
|
||||
8. Assign the mountpoint `/` to the new partition.
|
||||
9. Assign the `Boot/ESP` partition the mountpoint `/boot` from the partitioning menu.
|
||||
10. Confirm your settings and exit to the main menu by choosing `Confirm and exit`.
|
||||
11. Modify any additional settings for your installation as necessary.
|
||||
12. Start the installation upon completion of setup.
|
||||
|
||||
|
||||
# Mission Statement
|
||||
|
||||
Archinstall promises to ship a [guided installer](https://github.com/archlinux/archinstall/blob/master/archinstall/scripts/guided.py) that follows
|
||||
the [Arch Linux Principles](https://wiki.archlinux.org/index.php/Arch_Linux#Principles) as well as a library to manage services, packages, and other Arch Linux aspects.
|
||||
|
||||
The guided installer ensures a user-friendly experience, offering optional selections throughout the process. Emphasizing its flexible nature, these options are never obligatory.
|
||||
In addition, the decision to use the guided installer remains entirely with the user, reflecting the Linux philosophy of providing full freedom and flexibility.
|
||||
|
||||
---
|
||||
|
||||
Archinstall primarily functions as a flexible library for managing services, packages, and other elements within an Arch Linux system.
|
||||
This core library is the backbone for the guided installer that Archinstall provides. It is also designed to be used by those who wish to script their own custom installations.
|
||||
|
||||
Therefore, Archinstall will try its best to not introduce any breaking changes except for major releases which may break backward compatibility after notifying about such changes.
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md](https://github.com/archlinux/archinstall/blob/master/CONTRIBUTING.md)
|
||||

|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,3 +0,0 @@
|
|||
from archinstall.lib.plugins import plugin
|
||||
|
||||
__all__ = ['plugin']
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import sys
|
||||
|
||||
from archinstall.main import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import Audio, AudioConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class AudioApp:
|
||||
@property
|
||||
def pulseaudio_packages(self) -> list[str]:
|
||||
return [
|
||||
'pulseaudio',
|
||||
]
|
||||
|
||||
@property
|
||||
def pipewire_packages(self) -> list[str]:
|
||||
return [
|
||||
'pipewire',
|
||||
'pipewire-alsa',
|
||||
'pipewire-jack',
|
||||
'pipewire-pulse',
|
||||
'gst-plugin-pipewire',
|
||||
'libpulse',
|
||||
'wireplumber',
|
||||
]
|
||||
|
||||
def _enable_pipewire(
|
||||
self,
|
||||
install_session: Installer,
|
||||
users: list[User] | None = None,
|
||||
) -> None:
|
||||
if users is None:
|
||||
return
|
||||
|
||||
for user in users:
|
||||
# Create the full path for enabling the pipewire systemd items
|
||||
service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants'
|
||||
service_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Set ownership of the entire user catalogue
|
||||
install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}')
|
||||
|
||||
# symlink in the correct pipewire systemd items
|
||||
install_session.arch_chroot(
|
||||
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service',
|
||||
run_as=user.username,
|
||||
)
|
||||
install_session.arch_chroot(
|
||||
f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket',
|
||||
run_as=user.username,
|
||||
)
|
||||
|
||||
def install(
|
||||
self,
|
||||
install_session: Installer,
|
||||
audio_config: AudioConfiguration,
|
||||
users: list[User] | None = None,
|
||||
) -> None:
|
||||
debug(f'Installing audio server: {audio_config.audio.value}')
|
||||
|
||||
if audio_config.audio == Audio.NO_AUDIO:
|
||||
debug('No audio server selected, skipping installation.')
|
||||
return
|
||||
|
||||
if SysInfo.requires_sof_fw():
|
||||
install_session.add_additional_packages('sof-firmware')
|
||||
|
||||
if SysInfo.requires_alsa_fw():
|
||||
install_session.add_additional_packages('alsa-firmware')
|
||||
|
||||
match audio_config.audio:
|
||||
case Audio.PIPEWIRE:
|
||||
install_session.add_additional_packages(self.pipewire_packages)
|
||||
self._enable_pipewire(install_session, users)
|
||||
case Audio.PULSEAUDIO:
|
||||
install_session.add_additional_packages(self.pulseaudio_packages)
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class BluetoothApp:
|
||||
@property
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'bluez',
|
||||
'bluez-utils',
|
||||
]
|
||||
|
||||
@property
|
||||
def services(self) -> list[str]:
|
||||
return [
|
||||
'bluetooth.service',
|
||||
]
|
||||
|
||||
def install(self, install_session: Installer) -> None:
|
||||
debug('Installing Bluetooth')
|
||||
install_session.add_additional_packages(self.packages)
|
||||
install_session.enable_service(self.services)
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import Firewall, FirewallConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class FirewallApp:
|
||||
@property
|
||||
def ufw_packages(self) -> list[str]:
|
||||
return [
|
||||
'ufw',
|
||||
]
|
||||
|
||||
@property
|
||||
def fwd_packages(self) -> list[str]:
|
||||
return [
|
||||
'firewalld',
|
||||
]
|
||||
|
||||
@property
|
||||
def ufw_services(self) -> list[str]:
|
||||
return [
|
||||
'ufw.service',
|
||||
]
|
||||
|
||||
@property
|
||||
def fwd_services(self) -> list[str]:
|
||||
return [
|
||||
'firewalld.service',
|
||||
]
|
||||
|
||||
def install(
|
||||
self,
|
||||
install_session: Installer,
|
||||
firewall_config: FirewallConfiguration,
|
||||
) -> None:
|
||||
debug(f'Installing firewall: {firewall_config.firewall.value}')
|
||||
|
||||
match firewall_config.firewall:
|
||||
case Firewall.UFW:
|
||||
install_session.add_additional_packages(self.ufw_packages)
|
||||
install_session.enable_service(self.ufw_services)
|
||||
# write default conf file to enabled
|
||||
ufw_conf = install_session.target / 'etc/ufw/ufw.conf'
|
||||
ufw_conf.write_text(ufw_conf.read_text().replace('ENABLED=no', 'ENABLED=yes'))
|
||||
|
||||
case Firewall.FWD:
|
||||
install_session.add_additional_packages(self.fwd_packages)
|
||||
install_session.enable_service(self.fwd_services)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import FontsConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class FontsApp:
|
||||
def install(self, install_session: Installer, fonts_config: FontsConfiguration) -> None:
|
||||
packages = [f.value for f in fonts_config.fonts]
|
||||
debug(f'Installing fonts: {packages}')
|
||||
install_session.add_additional_packages(packages)
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
from archinstall.lib.models.application import PowerManagement, PowerManagementConfiguration
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class PowerManagementApp:
|
||||
@property
|
||||
def ppd_packages(self) -> list[str]:
|
||||
return [
|
||||
'power-profiles-daemon',
|
||||
]
|
||||
|
||||
@property
|
||||
def tuned_packages(self) -> list[str]:
|
||||
return [
|
||||
'tuned',
|
||||
'tuned-ppd',
|
||||
]
|
||||
|
||||
@property
|
||||
def ppd_services(self) -> list[str]:
|
||||
return [
|
||||
'power-profiles-daemon.service',
|
||||
]
|
||||
|
||||
@property
|
||||
def tuned_services(self) -> list[str]:
|
||||
return [
|
||||
'tuned.service',
|
||||
]
|
||||
|
||||
def install(
|
||||
self,
|
||||
install_session: Installer,
|
||||
power_management_config: PowerManagementConfiguration,
|
||||
) -> None:
|
||||
debug(f'Installing power management daemon: {power_management_config.power_management.value}')
|
||||
|
||||
match power_management_config.power_management:
|
||||
case PowerManagement.POWER_PROFILES_DAEMON:
|
||||
install_session.add_additional_packages(self.ppd_packages)
|
||||
install_session.enable_service(self.ppd_services)
|
||||
case PowerManagement.TUNED:
|
||||
install_session.add_additional_packages(self.tuned_packages)
|
||||
install_session.enable_service(self.tuned_services)
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class PrintServiceApp:
|
||||
@property
|
||||
def packages(self) -> list[str]:
|
||||
return ['cups', 'system-config-printer', 'cups-pk-helper']
|
||||
|
||||
@property
|
||||
def services(self) -> list[str]:
|
||||
return [
|
||||
'cups.service',
|
||||
]
|
||||
|
||||
def install(self, install_session: Installer) -> None:
|
||||
debug('Installing print service')
|
||||
install_session.add_additional_packages(self.packages)
|
||||
install_session.enable_service(self.services)
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
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.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
|
||||
class DesktopProfile(Profile):
|
||||
def __init__(self, current_selection: list[Self] = []) -> None:
|
||||
super().__init__(
|
||||
'Desktop',
|
||||
ProfileType.Desktop,
|
||||
current_selection=current_selection,
|
||||
support_greeter=True,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'nano',
|
||||
'vim',
|
||||
'openssh',
|
||||
'htop',
|
||||
'wget',
|
||||
'smartmontools',
|
||||
'xdg-utils',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType | None:
|
||||
combined_greeters: dict[GreeterType, int] = {}
|
||||
for profile in self.current_selection:
|
||||
if profile.default_greeter_type:
|
||||
combined_greeters.setdefault(profile.default_greeter_type, 0)
|
||||
combined_greeters[profile.default_greeter_type] += 1
|
||||
|
||||
if len(combined_greeters) >= 1:
|
||||
return list(combined_greeters)[0]
|
||||
|
||||
return None
|
||||
|
||||
async def _do_on_select_profiles(self) -> None:
|
||||
for profile in self.current_selection:
|
||||
await profile.do_on_select()
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> SelectResult:
|
||||
items = [
|
||||
MenuItem(
|
||||
p.name,
|
||||
value=p,
|
||||
preview_action=lambda x: x.value.preview_text() if x.value else None,
|
||||
)
|
||||
for p in profile_handler.get_desktop_profiles()
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items, sort_items=True, sort_case_sensitive=False)
|
||||
group.set_selected_by_value(self.current_selection)
|
||||
|
||||
result = await Selection[Self](
|
||||
group,
|
||||
multi=True,
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
preview_location='right',
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
self.current_selection = result.get_values()
|
||||
await self._do_on_select_profiles()
|
||||
return SelectResult.NewSelection
|
||||
case ResultType.Skip:
|
||||
return SelectResult.SameSelection
|
||||
case ResultType.Reset:
|
||||
return SelectResult.ResetCurrent
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: Installer) -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.post_install(install_session)
|
||||
|
||||
@override
|
||||
def provision(self, install_session: Installer, users: list[User]) -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.provision(install_session, users)
|
||||
|
||||
@override
|
||||
def install(self, install_session: Installer) -> None:
|
||||
# Install common packages for all desktop environments
|
||||
install_session.add_additional_packages(self.packages)
|
||||
|
||||
xorg_installed = False
|
||||
|
||||
for profile in self.current_selection:
|
||||
info(f'Installing profile {profile.name}...')
|
||||
|
||||
install_session.add_additional_packages(profile.packages)
|
||||
install_session.enable_service(profile.services)
|
||||
|
||||
if not xorg_installed and profile.display_server == DisplayServerType.Xorg:
|
||||
install_session.add_additional_packages(['xorg-server', 'xorg-xinit'])
|
||||
xorg_installed = True
|
||||
|
||||
profile.install(install_session)
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class AwesomeProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Awesome',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'awesome',
|
||||
'alacritty',
|
||||
'xorg-xrandr',
|
||||
'xterm',
|
||||
'feh',
|
||||
'slock',
|
||||
'terminus-font',
|
||||
'gnu-free-fonts',
|
||||
'ttf-liberation',
|
||||
'xsel',
|
||||
]
|
||||
|
||||
@override
|
||||
def install(self, install_session: Installer) -> None:
|
||||
super().install(install_session)
|
||||
|
||||
# TODO: Copy a full configuration to ~/.config/awesome/rc.lua instead.
|
||||
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua') as fh:
|
||||
awesome_lua = fh.read()
|
||||
|
||||
# Replace xterm with alacritty for a smoother experience.
|
||||
awesome_lua = awesome_lua.replace('"xterm"', '"alacritty"')
|
||||
|
||||
with open(f'{install_session.target}/etc/xdg/awesome/rc.lua', 'w') as fh:
|
||||
fh.write(awesome_lua)
|
||||
|
||||
# TODO: Configure the right-click-menu to contain the above packages that were installed. (as a user config)
|
||||
|
||||
# TODO: check if we selected a greeter,
|
||||
# but for now, awesome is intended to run without one.
|
||||
with open(f'{install_session.target}/etc/X11/xinit/xinitrc') as xinitrc:
|
||||
xinitrc_data = xinitrc.read()
|
||||
|
||||
for line in xinitrc_data.split('\n'):
|
||||
if 'twm &' in line:
|
||||
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||
if 'xclock' in line:
|
||||
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||
if 'xterm' in line:
|
||||
xinitrc_data = xinitrc_data.replace(line, f'# {line}')
|
||||
|
||||
xinitrc_data += '\n'
|
||||
xinitrc_data += 'exec awesome\n'
|
||||
|
||||
with open(f'{install_session.target}/etc/X11/xinit/xinitrc', 'w') as xinitrc:
|
||||
xinitrc.write(xinitrc_data)
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
|
||||
class BspwmProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Bspwm',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'bspwm',
|
||||
'sxhkd',
|
||||
'dmenu',
|
||||
'xdo',
|
||||
'rxvt-unicode',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
||||
@override
|
||||
def provision(self, install_session: Installer, users: list[User]) -> None:
|
||||
for user in users:
|
||||
install_session.arch_chroot('mkdir -p ~/.config/bspwm ~/.config/sxhkd', run_as=user.username)
|
||||
install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/bspwmrc ~/.config/bspwm/', run_as=user.username)
|
||||
install_session.arch_chroot('cp /usr/share/doc/bspwm/examples/sxhkdrc ~/.config/sxhkd/', run_as=user.username)
|
||||
install_session.arch_chroot('chmod +x ~/.config/bspwm/bspwmrc', run_as=user.username)
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class BudgieProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Budgie',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'materia-gtk-theme',
|
||||
'budgie',
|
||||
'mate-terminal',
|
||||
'nemo',
|
||||
'nemo-fileroller',
|
||||
'papirus-icon-theme',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.LightdmSlick
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class CinnamonProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Cinnamon',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'cinnamon',
|
||||
'system-config-printer',
|
||||
'gnome-keyring',
|
||||
'gnome-terminal',
|
||||
'engrampa',
|
||||
'gnome-screenshot',
|
||||
'gvfs-smb',
|
||||
'xed',
|
||||
'xdg-user-dirs-gtk',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class CosmicProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Cosmic',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'cosmic',
|
||||
'xdg-user-dirs',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.CosmicSession
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class DeepinProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Deepin',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'deepin',
|
||||
'deepin-terminal',
|
||||
'deepin-editor',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class EnlightenmentProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Enlightenment',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'enlightenment',
|
||||
'terminology',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class GnomeProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'GNOME',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'gnome',
|
||||
'gnome-tweaks',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Gdm
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops.utils import select_seat_access
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class HyprlandProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Hyprland',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'hyprland',
|
||||
'dunst',
|
||||
'kitty',
|
||||
'uwsm',
|
||||
'dolphin',
|
||||
'wofi',
|
||||
'xdg-desktop-portal-hyprland',
|
||||
'qt5-wayland',
|
||||
'qt6-wayland',
|
||||
'polkit-kde-agent',
|
||||
'grim',
|
||||
'slurp',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Sddm
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
seat_access = await select_seat_access(self.name, default)
|
||||
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class I3wmProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'i3-wm',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'i3-wm',
|
||||
'i3lock',
|
||||
'i3status',
|
||||
'i3blocks',
|
||||
'xss-lock',
|
||||
'xterm',
|
||||
'lightdm-gtk-greeter',
|
||||
'lightdm',
|
||||
'dmenu',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops.utils import select_seat_access
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class LabwcProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Labwc',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
'alacritty',
|
||||
'labwc',
|
||||
] + additional
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
seat_access = await select_seat_access(self.name, default)
|
||||
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class LxqtProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Lxqt',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
# NOTE: SDDM is the only officially supported greeter for LXQt, so unlike other DEs, lightdm is not used here.
|
||||
# LXQt works with lightdm, but since this is not supported, we will not default to this.
|
||||
# https://github.com/lxqt/lxqt/issues/795
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'lxqt',
|
||||
'breeze-icons',
|
||||
'oxygen-icons',
|
||||
'xdg-utils',
|
||||
'ttf-freefont',
|
||||
'l3afpad',
|
||||
'slock',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Sddm
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class MateProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Mate',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'mate',
|
||||
'mate-extra',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops.utils import select_seat_access
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class NiriProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'niri',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
'niri',
|
||||
'alacritty',
|
||||
'fuzzel',
|
||||
'mako',
|
||||
'xorg-xwayland',
|
||||
'waybar',
|
||||
'swaybg',
|
||||
'swayidle',
|
||||
'swaylock',
|
||||
'xdg-desktop-portal-gnome',
|
||||
] + additional
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
seat_access = await select_seat_access(self.name, default)
|
||||
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
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')
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// ! DO NOT EDIT !
|
||||
// ! AUTO-GENERATED BY DMS !
|
||||
// ! CHANGES WILL BE OVERWRITTEN !
|
||||
// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
|
||||
|
||||
recent-windows {
|
||||
highlight {
|
||||
corner-radius 12
|
||||
}
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// ! 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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
// Place cursor configuration here.
|
||||
// Example:
|
||||
// cursor {
|
||||
// xcursor-theme "Adwaita"
|
||||
// xcursor-size 24
|
||||
// }
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// ! 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
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Place per-output configuration here.
|
||||
// Example:
|
||||
// output "DP-1" {
|
||||
// mode "2560x1440@165"
|
||||
// position x=0 y=0
|
||||
// scale 1
|
||||
// }
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
// 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"
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
from enum import StrEnum
|
||||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.packages.packages import available_package, package_group_info
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class PlasmaFlavor(StrEnum):
|
||||
Meta = 'plasma-meta'
|
||||
Plasma = 'plasma'
|
||||
Desktop = 'plasma-desktop'
|
||||
|
||||
def show(self) -> str:
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
return f'{self.value} ({tr("Recommended")})'
|
||||
case PlasmaFlavor.Plasma | PlasmaFlavor.Desktop:
|
||||
return self.value
|
||||
|
||||
def package_details(self) -> str:
|
||||
ty = ''
|
||||
details = ''
|
||||
desc = ''
|
||||
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
ty = tr('Package')
|
||||
desc = tr('Curated selection of KDE Plasma packages')
|
||||
info = available_package(self.value)
|
||||
|
||||
if info is not None:
|
||||
details = tr('Dependencies') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
|
||||
case PlasmaFlavor.Plasma:
|
||||
ty = tr('Package group')
|
||||
desc = tr('Extensive KDE Plasma installation')
|
||||
group = package_group_info(self.value)
|
||||
|
||||
if group is not None:
|
||||
details = tr('Packages in group') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in group.packages)
|
||||
case PlasmaFlavor.Desktop:
|
||||
ty = tr('Package group')
|
||||
desc = tr('Minimal KDE Plasma installation')
|
||||
info = available_package(self.value)
|
||||
|
||||
if info is not None:
|
||||
details = tr('Dependencies') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
|
||||
|
||||
return f'{tr("Type")}: {ty}\n{tr("Description")}: {desc}\n\n{details}'
|
||||
|
||||
def packages(self) -> list[str]:
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
return ['plasma-meta']
|
||||
case PlasmaFlavor.Plasma:
|
||||
return ['plasma']
|
||||
case PlasmaFlavor.Desktop:
|
||||
return ['plasma-desktop']
|
||||
|
||||
|
||||
class PlasmaProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'KDE Plasma',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
flavor_str = self.custom_settings.get(CustomSetting.PlasmaFlavor)
|
||||
|
||||
if flavor_str is not None:
|
||||
flavor = PlasmaFlavor(flavor_str)
|
||||
return flavor.packages()
|
||||
else:
|
||||
return PlasmaFlavor.Meta.packages() # use plasma-meta as the recommended default
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.PlasmaLoginManager
|
||||
|
||||
async def _select_flavor(self) -> None:
|
||||
header = tr('Select a flavor of KDE Plasma to install') + '\n'
|
||||
|
||||
items = [
|
||||
MenuItem(
|
||||
s.show(),
|
||||
value=s,
|
||||
preview_action=lambda x: x.value.package_details() if x.value else None,
|
||||
)
|
||||
for s in PlasmaFlavor
|
||||
]
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
|
||||
default = self.custom_settings.get(CustomSetting.PlasmaFlavor, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[PlasmaFlavor](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preview_location='right',
|
||||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings[CustomSetting.PlasmaFlavor] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
await self._select_flavor()
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class QtileProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Qtile',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'qtile',
|
||||
'alacritty',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class RiverProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'River',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'foot',
|
||||
'xdg-desktop-portal-wlr',
|
||||
'river',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops.utils import select_seat_access
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class SwayProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Sway',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
'sway',
|
||||
'swaybg',
|
||||
'swaylock',
|
||||
'swayidle',
|
||||
'waybar',
|
||||
'wmenu',
|
||||
'brightnessctl',
|
||||
'grim',
|
||||
'slurp',
|
||||
'pavucontrol',
|
||||
'foot',
|
||||
'xorg-xwayland',
|
||||
] + additional
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
seat_access = await select_seat_access(self.name, default)
|
||||
self.custom_settings[CustomSetting.SeatAccess] = seat_access.value
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class SeatAccess(Enum):
|
||||
seatd = 'seatd'
|
||||
polkit = 'polkit'
|
||||
|
||||
|
||||
async def select_seat_access(profile_name: str, default: str | None) -> SeatAccess:
|
||||
header = tr('{} needs access to your seat').format(profile_name)
|
||||
header += f' ({tr("collection of hardware devices i.e. keyboard, mouse")})' + '\n'
|
||||
header += tr('Choose an option how to give {} access to your hardware').format(profile_name)
|
||||
|
||||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[SeatAccess](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
return result.get_value()
|
||||
else:
|
||||
raise ValueError('Unexpected result type from seat access selection')
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class Xfce4Profile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Xfce4',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'xfce4',
|
||||
'xfce4-goodies',
|
||||
'pavucontrol',
|
||||
'gvfs',
|
||||
'xarchiver',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class XmonadProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Xmonad',
|
||||
ProfileType.WindowMgr,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'xmonad',
|
||||
'xmonad-contrib',
|
||||
'xmonad-extras',
|
||||
'xterm',
|
||||
'dmenu',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Lightdm
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class MinimalProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Minimal',
|
||||
ProfileType.Minimal,
|
||||
)
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
from enum import Enum, StrEnum, auto
|
||||
from typing import TYPE_CHECKING, Self
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
|
||||
class DisplayServerType(Enum):
|
||||
Xorg = 'Xorg'
|
||||
Wayland = 'Wayland'
|
||||
|
||||
|
||||
class ProfileType(Enum):
|
||||
# top level default_profiles
|
||||
Server = 'Server'
|
||||
Desktop = 'Desktop'
|
||||
Xorg = 'Xorg'
|
||||
Minimal = 'Minimal'
|
||||
Custom = 'Custom'
|
||||
# detailed selection default_profiles
|
||||
ServerType = 'ServerType'
|
||||
WindowMgr = 'Window Manager'
|
||||
DesktopEnv = 'Desktop Environment'
|
||||
CustomType = 'CustomType'
|
||||
# special things
|
||||
Application = 'Application'
|
||||
|
||||
|
||||
class GreeterType(Enum):
|
||||
Lightdm = 'lightdm-gtk-greeter'
|
||||
LightdmSlick = 'lightdm-slick-greeter'
|
||||
Sddm = 'sddm'
|
||||
Gdm = 'gdm'
|
||||
Ly = 'ly'
|
||||
CosmicSession = 'cosmic-greeter'
|
||||
PlasmaLoginManager = 'plasma-login-manager'
|
||||
GreetdDms = 'dms-greeter'
|
||||
|
||||
|
||||
class SelectResult(Enum):
|
||||
NewSelection = auto()
|
||||
SameSelection = auto()
|
||||
ResetCurrent = auto()
|
||||
|
||||
|
||||
class CustomSetting(StrEnum):
|
||||
SeatAccess = 'seat_access'
|
||||
PlasmaFlavor = 'plasma_flavor'
|
||||
|
||||
|
||||
class Profile:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
profile_type: ProfileType,
|
||||
current_selection: list[Self] = [],
|
||||
packages: list[str] = [],
|
||||
services: list[str] = [],
|
||||
support_gfx_driver: bool = False,
|
||||
support_greeter: bool = False,
|
||||
display_server: DisplayServerType | None = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.profile_type = profile_type
|
||||
self.custom_settings: dict[CustomSetting, str | None] = {}
|
||||
|
||||
self._support_gfx_driver = support_gfx_driver
|
||||
self._support_greeter = support_greeter
|
||||
self._display_server = display_server
|
||||
|
||||
# self.gfx_driver: str | None = None
|
||||
|
||||
self.current_selection = current_selection
|
||||
self._packages = packages
|
||||
self._services = services
|
||||
|
||||
# Only used for custom default_profiles
|
||||
self.custom_enabled = False
|
||||
|
||||
@property
|
||||
def packages(self) -> list[str]:
|
||||
"""
|
||||
Returns a list of packages that should be installed when
|
||||
this profile is among the chosen ones
|
||||
"""
|
||||
return self._packages
|
||||
|
||||
@property
|
||||
def services(self) -> list[str]:
|
||||
"""
|
||||
Returns a list of services that should be enabled when
|
||||
this profile is among the chosen ones
|
||||
"""
|
||||
return self._services
|
||||
|
||||
@property
|
||||
def default_greeter_type(self) -> GreeterType | None:
|
||||
"""
|
||||
Setting a default greeter type for a desktop profile
|
||||
"""
|
||||
return None
|
||||
|
||||
def install(self, install_session: Installer) -> None:
|
||||
"""
|
||||
Performs installation steps when this profile was selected
|
||||
"""
|
||||
|
||||
def post_install(self, install_session: Installer) -> None:
|
||||
"""
|
||||
Hook that will be called when the installation process is
|
||||
finished and custom installation steps for specific default_profiles
|
||||
are needed
|
||||
"""
|
||||
|
||||
def provision(self, install_session: Installer, users: list[User]) -> None:
|
||||
"""
|
||||
Hook that will be called when the installation process is
|
||||
finished and user configuration for specific default_profiles
|
||||
is needed
|
||||
"""
|
||||
|
||||
def json(self) -> dict[str, str]:
|
||||
"""
|
||||
Returns a json representation of the profile
|
||||
"""
|
||||
return {}
|
||||
|
||||
async def do_on_select(self) -> SelectResult | None:
|
||||
"""
|
||||
Hook that will be called when a profile is selected
|
||||
"""
|
||||
return SelectResult.NewSelection
|
||||
|
||||
def set_custom_settings(self, settings: dict[CustomSetting, str | None]) -> None:
|
||||
"""
|
||||
Set the custom settings for the profile.
|
||||
This is also called when the settings are parsed from the config
|
||||
and can be overridden to perform further actions based on the profile
|
||||
"""
|
||||
self.custom_settings = settings
|
||||
|
||||
def current_selection_names(self) -> list[str]:
|
||||
if self.current_selection:
|
||||
return [s.name for s in self.current_selection]
|
||||
return []
|
||||
|
||||
def reset(self) -> None:
|
||||
self.current_selection = []
|
||||
|
||||
def is_top_level_profile(self) -> bool:
|
||||
top_levels = [ProfileType.Desktop, ProfileType.Server, ProfileType.Xorg, ProfileType.Minimal, ProfileType.Custom]
|
||||
return self.profile_type in top_levels
|
||||
|
||||
def is_desktop_profile(self) -> bool:
|
||||
return self.profile_type == ProfileType.Desktop
|
||||
|
||||
def is_server_type_profile(self) -> bool:
|
||||
return self.profile_type == ProfileType.ServerType
|
||||
|
||||
def is_desktop_type_profile(self) -> bool:
|
||||
return self.profile_type == ProfileType.DesktopEnv or self.profile_type == ProfileType.WindowMgr
|
||||
|
||||
def is_xorg_type_profile(self) -> bool:
|
||||
return self.profile_type == ProfileType.Xorg
|
||||
|
||||
def is_custom_type_profile(self) -> bool:
|
||||
return self.profile_type == ProfileType.CustomType
|
||||
|
||||
def is_graphic_driver_supported(self) -> bool:
|
||||
if not self.current_selection:
|
||||
return self._support_gfx_driver
|
||||
else:
|
||||
if any([p._support_gfx_driver for p in self.current_selection]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_greeter_supported(self) -> bool:
|
||||
return self._support_greeter
|
||||
|
||||
@property
|
||||
def display_server(self) -> DisplayServerType | None:
|
||||
return self._display_server
|
||||
|
||||
def preview_text(self) -> str:
|
||||
"""
|
||||
Override this method to provide a preview text for the profile
|
||||
"""
|
||||
if self.is_desktop_type_profile():
|
||||
if self._display_server:
|
||||
text = tr('Environment type: {} {}').format(self._display_server.value, self.profile_type.value)
|
||||
else:
|
||||
text = tr('Environment type: {}').format(self.profile_type.value)
|
||||
if packages := self.packages_text():
|
||||
text += f'\n{packages}'
|
||||
return text
|
||||
|
||||
return self.packages_text()
|
||||
|
||||
def packages_text(self, include_sub_packages: bool = False) -> str:
|
||||
packages = set()
|
||||
|
||||
if self.packages:
|
||||
packages = set(self.packages)
|
||||
|
||||
if include_sub_packages:
|
||||
for sub_profile in self.current_selection:
|
||||
if sub_profile.packages:
|
||||
packages.update(sub_profile.packages)
|
||||
|
||||
text = tr('Installed packages') + ':\n'
|
||||
|
||||
for pkg in sorted(packages):
|
||||
text += f' - {pkg}\n'
|
||||
|
||||
return text
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
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.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
|
||||
class ServerProfile(Profile):
|
||||
def __init__(self, current_value: list[Self] = []):
|
||||
super().__init__(
|
||||
'Server',
|
||||
ProfileType.Server,
|
||||
current_selection=current_value,
|
||||
)
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> SelectResult:
|
||||
items = [
|
||||
MenuItem(
|
||||
p.name,
|
||||
value=p,
|
||||
preview_action=lambda x: x.value.preview_text() if x.value else None,
|
||||
)
|
||||
for p in profile_handler.get_server_profiles()
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
group.set_selected_by_value(self.current_selection)
|
||||
|
||||
result = await Selection[Self](
|
||||
group,
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
multi=True,
|
||||
preview_location='right',
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
selections = result.get_values()
|
||||
self.current_selection = selections
|
||||
return SelectResult.NewSelection
|
||||
case ResultType.Skip:
|
||||
return SelectResult.SameSelection
|
||||
case ResultType.Reset:
|
||||
return SelectResult.ResetCurrent
|
||||
|
||||
@override
|
||||
def provision(self, install_session: Installer, users: list[User]) -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.provision(install_session, users)
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: Installer) -> None:
|
||||
for profile in self.current_selection:
|
||||
profile.post_install(install_session)
|
||||
|
||||
@override
|
||||
def install(self, install_session: Installer) -> None:
|
||||
server_info = self.current_selection_names()
|
||||
details = ', '.join(server_info)
|
||||
info(f'Now installing the selected servers: {details}')
|
||||
|
||||
for server in self.current_selection:
|
||||
info(f'Installing {server.name}...')
|
||||
install_session.add_additional_packages(server.packages)
|
||||
install_session.enable_service(server.services)
|
||||
server.install(install_session)
|
||||
|
||||
info('If your selections included multiple servers with the same port, you may have to reconfigure them.')
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class CockpitProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Cockpit',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['cockpit', 'udisks2', 'packagekit']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['cockpit.socket']
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
|
||||
class DockerProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Docker',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['docker']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['docker']
|
||||
|
||||
@override
|
||||
def provision(self, install_session: Installer, users: list[User]) -> None:
|
||||
for user in users:
|
||||
install_session.arch_chroot(f'usermod -a -G docker {user.username}')
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class HttpdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'httpd',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['apache']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['httpd']
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class LighttpdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Lighttpd',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['lighttpd']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['lighttpd']
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class MariadbProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Mariadb',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['mariadb']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['mariadb']
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: Installer) -> None:
|
||||
install_session.arch_chroot('mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql')
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class NginxProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Nginx',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['nginx']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['nginx']
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class PostgresqlProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Postgresql',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['postgresql']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['postgresql']
|
||||
|
||||
@override
|
||||
def post_install(self, install_session: Installer) -> None:
|
||||
install_session.arch_chroot('initdb -D /var/lib/postgres/data', run_as='postgres')
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class SshdProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'sshd',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['openssh']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['sshd']
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import Profile, ProfileType
|
||||
|
||||
|
||||
class TomcatProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Tomcat',
|
||||
ProfileType.ServerType,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return ['tomcat10']
|
||||
|
||||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
return ['tomcat10']
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, Profile, ProfileType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class XorgProfile(Profile):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = 'Xorg',
|
||||
profile_type: ProfileType = ProfileType.Xorg,
|
||||
):
|
||||
super().__init__(
|
||||
name,
|
||||
profile_type,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'xorg-server',
|
||||
'xorg-xinit',
|
||||
]
|
||||
|
||||
@override
|
||||
def install(self, install_session: Installer) -> None:
|
||||
install_session.add_additional_packages(self.packages)
|
||||
|
|
@ -1 +0,0 @@
|
|||
../examples/
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.applications.audio import AudioApp
|
||||
from archinstall.applications.bluetooth import BluetoothApp
|
||||
from archinstall.applications.firewall import FirewallApp
|
||||
from archinstall.applications.fonts import FontsApp
|
||||
from archinstall.applications.power_management import PowerManagementApp
|
||||
from archinstall.applications.print_service import PrintServiceApp
|
||||
from archinstall.lib.models import Audio
|
||||
from archinstall.lib.models.application import ApplicationConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class ApplicationHandler:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def install_applications(self, install_session: Installer, app_config: ApplicationConfiguration, users: list[User] | None = None) -> None:
|
||||
if app_config.bluetooth_config and app_config.bluetooth_config.enabled:
|
||||
BluetoothApp().install(install_session)
|
||||
|
||||
if app_config.audio_config and app_config.audio_config.audio != Audio.NO_AUDIO:
|
||||
AudioApp().install(
|
||||
install_session,
|
||||
app_config.audio_config,
|
||||
users,
|
||||
)
|
||||
|
||||
if app_config.power_management_config:
|
||||
PowerManagementApp().install(
|
||||
install_session,
|
||||
app_config.power_management_config,
|
||||
)
|
||||
|
||||
if app_config.print_service_config and app_config.print_service_config.enabled:
|
||||
PrintServiceApp().install(install_session)
|
||||
|
||||
if app_config.firewall_config:
|
||||
FirewallApp().install(
|
||||
install_session,
|
||||
app_config.firewall_config,
|
||||
)
|
||||
|
||||
if app_config.fonts_config:
|
||||
FontsApp().install(
|
||||
install_session,
|
||||
app_config.fonts_config,
|
||||
)
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.models.application import (
|
||||
ApplicationConfiguration,
|
||||
Audio,
|
||||
AudioConfiguration,
|
||||
BluetoothConfiguration,
|
||||
Firewall,
|
||||
FirewallConfiguration,
|
||||
FontPackage,
|
||||
FontsConfiguration,
|
||||
PowerManagement,
|
||||
PowerManagementConfiguration,
|
||||
PrintServiceConfiguration,
|
||||
)
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
preset: ApplicationConfiguration | None = None,
|
||||
):
|
||||
if preset:
|
||||
self._app_config = preset
|
||||
else:
|
||||
self._app_config = ApplicationConfiguration()
|
||||
|
||||
menu_options = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_options, checkmarks=True)
|
||||
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._app_config,
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
@override
|
||||
async def show(self) -> ApplicationConfiguration | None:
|
||||
_ = await super().show()
|
||||
return self._app_config
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=tr('Bluetooth'),
|
||||
action=select_bluetooth,
|
||||
value=self._app_config.bluetooth_config,
|
||||
preview_action=self._prev_bluetooth,
|
||||
key='bluetooth_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Audio'),
|
||||
action=select_audio,
|
||||
preview_action=self._prev_audio,
|
||||
key='audio_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Print service'),
|
||||
action=select_print_service,
|
||||
preview_action=self._prev_print_service,
|
||||
key='print_service_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Power management'),
|
||||
action=select_power_management,
|
||||
preview_action=self._prev_power_management,
|
||||
enabled=SysInfo.has_battery(),
|
||||
key='power_management_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Firewall'),
|
||||
action=select_firewall,
|
||||
preview_action=self._prev_firewall,
|
||||
key='firewall_config',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Additional fonts'),
|
||||
action=select_fonts,
|
||||
value=self._app_config.fonts_config,
|
||||
preview_action=self._prev_fonts,
|
||||
key='fonts_config',
|
||||
),
|
||||
]
|
||||
|
||||
def _prev_power_management(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
config: PowerManagementConfiguration = item.value
|
||||
return f'{tr("Power management")}: {config.power_management.value}'
|
||||
return None
|
||||
|
||||
def _prev_bluetooth(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
bluetooth_config: BluetoothConfiguration = item.value
|
||||
|
||||
output = f'{tr("Bluetooth")}: '
|
||||
output += tr('Enabled') if bluetooth_config.enabled else tr('Disabled')
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_audio(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
config: AudioConfiguration = item.value
|
||||
return f'{tr("Audio")}: {config.audio.value}'
|
||||
return None
|
||||
|
||||
def _prev_print_service(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
print_service_config: PrintServiceConfiguration = item.value
|
||||
|
||||
output = f'{tr("Print service")}: '
|
||||
output += tr('Enabled') if print_service_config.enabled else tr('Disabled')
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_firewall(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
config: FirewallConfiguration = item.value
|
||||
return f'{tr("Firewall")}: {config.firewall.value}'
|
||||
return None
|
||||
|
||||
def _prev_fonts(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
config: FontsConfiguration = item.value
|
||||
packages = ', '.join(f.value for f in config.fonts)
|
||||
return f'{tr("Additional fonts")}: {packages}'
|
||||
return None
|
||||
|
||||
|
||||
async def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None:
|
||||
group = MenuItemGroup.from_enum(PowerManagement)
|
||||
|
||||
if preset:
|
||||
group.set_focus_by_value(preset.power_management)
|
||||
|
||||
result = await Selection[PowerManagement](
|
||||
group,
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return PowerManagementConfiguration(power_management=result.get_value())
|
||||
case ResultType.Reset:
|
||||
return None
|
||||
|
||||
|
||||
async def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None:
|
||||
header = tr('Would you like to configure Bluetooth?') + '\n'
|
||||
preset_val = preset.enabled if preset else False
|
||||
|
||||
result = await Confirmation(
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
preset=preset_val,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
return BluetoothConfiguration(result.get_value())
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
async def select_print_service(preset: PrintServiceConfiguration | None) -> PrintServiceConfiguration | None:
|
||||
header = tr('Would you like to configure the print service?') + '\n'
|
||||
preset_val = preset.enabled if preset else False
|
||||
|
||||
result = await Confirmation(
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
preset=preset_val,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
result.get_value()
|
||||
return PrintServiceConfiguration(result.get_value())
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
async def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None:
|
||||
items = [MenuItem(a.value, value=a) for a in Audio]
|
||||
group = MenuItemGroup(items)
|
||||
|
||||
if preset:
|
||||
group.set_focus_by_value(preset.audio)
|
||||
|
||||
result = await Selection[Audio](
|
||||
group,
|
||||
header=tr('Select audio configuration'),
|
||||
allow_skip=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return AudioConfiguration(audio=result.get_value())
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
async def select_firewall(preset: FirewallConfiguration | None = None) -> FirewallConfiguration | None:
|
||||
group = MenuItemGroup.from_enum(Firewall)
|
||||
|
||||
if preset:
|
||||
group.set_focus_by_value(preset.firewall)
|
||||
|
||||
result = await Selection[Firewall](
|
||||
group,
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return FirewallConfiguration(firewall=result.get_value())
|
||||
case ResultType.Reset:
|
||||
return None
|
||||
|
||||
|
||||
async def select_fonts(preset: FontsConfiguration | None = None) -> FontsConfiguration | None:
|
||||
items = [MenuItem(f'{f.value} ({f.description()})', value=f) for f in FontPackage]
|
||||
group = MenuItemGroup(items)
|
||||
|
||||
if preset:
|
||||
for f in preset.fonts:
|
||||
group.set_selected_by_value(f)
|
||||
|
||||
result = await Selection[FontPackage](
|
||||
group,
|
||||
header=tr('Select font packages to install'),
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
multi=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
selected = result.get_values()
|
||||
if selected:
|
||||
return FontsConfiguration(fonts=selected)
|
||||
return None
|
||||
case ResultType.Reset:
|
||||
return None
|
||||
|
|
@ -1,671 +0,0 @@
|
|||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Any, Self
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from pydantic.dataclasses import dataclass as p_dataclass
|
||||
|
||||
from archinstall.lib.crypt import decrypt
|
||||
from archinstall.lib.log import debug, error, logger, warn
|
||||
from archinstall.lib.menu.util import get_password
|
||||
from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.config import SubConfig
|
||||
from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguration
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models.network import NetworkConfiguration
|
||||
from archinstall.lib.models.package_types import DEFAULT_KERNEL
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.models.users import Password, User, UserSerialization
|
||||
from archinstall.lib.plugins import load_plugin
|
||||
from archinstall.lib.translationhandler import Language, tr, translation_handler
|
||||
from archinstall.lib.version import get_version
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
class SubCommand(Enum):
|
||||
SHARE_LOG = 'share-log'
|
||||
|
||||
|
||||
@p_dataclass
|
||||
class Arguments:
|
||||
config: Path | None = None
|
||||
config_url: str | None = None
|
||||
creds: Path | None = None
|
||||
creds_url: str | None = None
|
||||
creds_decryption_key: str | None = None
|
||||
silent: bool = False
|
||||
dry_run: bool = False
|
||||
script: str | None = None
|
||||
mountpoint: Path = Path('/mnt')
|
||||
skip_ntp: bool = False
|
||||
skip_wkd: bool = False
|
||||
skip_boot: bool = False
|
||||
debug: bool = False
|
||||
offline: bool = False
|
||||
no_pkg_lookups: bool = False
|
||||
plugin: str | None = None
|
||||
skip_version_check: bool = False
|
||||
skip_wifi_check: bool = False
|
||||
advanced: bool = False
|
||||
verbose: bool = False
|
||||
|
||||
command: SubCommand | None = None
|
||||
|
||||
|
||||
class ArchConfigType(StrEnum):
|
||||
VERSION = 'version'
|
||||
SCRIPT = 'script'
|
||||
LOCALE_CONFIG = 'locale_config'
|
||||
ARCHINSTALL_LANGUAGE = 'archinstall_language'
|
||||
DISK_CONFIG = 'disk_config'
|
||||
PROFILE_CONFIG = 'profile_config'
|
||||
MIRROR_CONFIG = 'mirror_config'
|
||||
NETWORK_CONFIG = 'network_config'
|
||||
BOOTLOADER_CONFIG = 'bootloader_config'
|
||||
APP_CONFIG = 'app_config'
|
||||
AUTH_CONFIG = 'auth_config'
|
||||
SWAP = 'swap'
|
||||
USERS = 'users'
|
||||
ROOT_ENC_PASSWORD = 'root_enc_password'
|
||||
ENCRYPTION_PASSWORD = 'encryption_password'
|
||||
HOSTNAME = 'hostname'
|
||||
KERNELS = 'kernels'
|
||||
NTP = 'ntp'
|
||||
TIMEZONE = 'timezone'
|
||||
SERVICES = 'services'
|
||||
PACKAGES = 'packages'
|
||||
PACMAN_CONFIG = 'pacman_config'
|
||||
CUSTOM_COMMANDS = 'custom_commands'
|
||||
|
||||
def text(self) -> str:
|
||||
match self:
|
||||
case ArchConfigType.ARCHINSTALL_LANGUAGE:
|
||||
return tr('ArchInstall Language')
|
||||
case ArchConfigType.VERSION:
|
||||
return tr('Version')
|
||||
case ArchConfigType.SCRIPT:
|
||||
return tr('Installation Script')
|
||||
case ArchConfigType.LOCALE_CONFIG:
|
||||
return tr('Locales')
|
||||
case ArchConfigType.DISK_CONFIG:
|
||||
return tr('Disk configuration')
|
||||
case ArchConfigType.PROFILE_CONFIG:
|
||||
return tr('Profile')
|
||||
case ArchConfigType.MIRROR_CONFIG:
|
||||
return tr('Mirrors and repositories')
|
||||
case ArchConfigType.NETWORK_CONFIG:
|
||||
return tr('Network')
|
||||
case ArchConfigType.BOOTLOADER_CONFIG:
|
||||
return tr('Bootloader')
|
||||
case ArchConfigType.APP_CONFIG:
|
||||
return tr('Application')
|
||||
case ArchConfigType.AUTH_CONFIG:
|
||||
return tr('Authentication')
|
||||
case ArchConfigType.SWAP:
|
||||
return tr('Swap')
|
||||
case ArchConfigType.HOSTNAME:
|
||||
return tr('Hostname')
|
||||
case ArchConfigType.KERNELS:
|
||||
return tr('Kernels')
|
||||
case ArchConfigType.NTP:
|
||||
return tr('Automatic time sync (NTP)')
|
||||
case ArchConfigType.TIMEZONE:
|
||||
return tr('Timezone')
|
||||
case ArchConfigType.SERVICES:
|
||||
return tr('Services')
|
||||
case ArchConfigType.PACKAGES:
|
||||
return tr('Additional packages')
|
||||
case ArchConfigType.PACMAN_CONFIG:
|
||||
return tr('Pacman')
|
||||
case ArchConfigType.CUSTOM_COMMANDS:
|
||||
return tr('Custom commands')
|
||||
case ArchConfigType.USERS:
|
||||
return tr('Users')
|
||||
case ArchConfigType.ROOT_ENC_PASSWORD:
|
||||
return tr('Root encrypted password')
|
||||
case ArchConfigType.ENCRYPTION_PASSWORD:
|
||||
return tr('Disk encryption password')
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArchConfig:
|
||||
version: str | None = None
|
||||
script: str | None = None
|
||||
locale_config: LocaleConfiguration | None = None
|
||||
archinstall_language: Language = field(default_factory=lambda: translation_handler.get_language_by_abbr('en'))
|
||||
disk_config: DiskLayoutConfiguration | None = None
|
||||
profile_config: ProfileConfiguration | None = None
|
||||
mirror_config: MirrorConfiguration | None = None
|
||||
network_config: NetworkConfiguration | None = None
|
||||
bootloader_config: BootloaderConfiguration | None = None
|
||||
app_config: ApplicationConfiguration | None = None
|
||||
auth_config: AuthenticationConfiguration | None = None
|
||||
swap: ZramConfiguration | None = None
|
||||
hostname: str = 'archlinux'
|
||||
kernels: list[str] = field(default_factory=lambda: [DEFAULT_KERNEL.value])
|
||||
ntp: bool = True
|
||||
packages: list[str] = field(default_factory=list)
|
||||
pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration.default)
|
||||
timezone: str = 'UTC'
|
||||
services: list[str] = field(default_factory=list)
|
||||
custom_commands: list[str] = field(default_factory=list)
|
||||
|
||||
def unsafe_config(self) -> dict[ArchConfigType, Any]:
|
||||
config: dict[ArchConfigType, list[UserSerialization] | str | None] = {}
|
||||
|
||||
if self.auth_config:
|
||||
if self.auth_config.users:
|
||||
config[ArchConfigType.USERS] = [user.json() for user in self.auth_config.users]
|
||||
|
||||
if self.auth_config.root_enc_password:
|
||||
config[ArchConfigType.ROOT_ENC_PASSWORD] = self.auth_config.root_enc_password.enc_password
|
||||
|
||||
if self.disk_config:
|
||||
disk_encryption = self.disk_config.disk_encryption
|
||||
if disk_encryption and disk_encryption.encryption_password:
|
||||
config[ArchConfigType.ENCRYPTION_PASSWORD] = disk_encryption.encryption_password.plaintext
|
||||
|
||||
return config
|
||||
|
||||
def safe_config(self) -> dict[ArchConfigType, Any]:
|
||||
base_config: dict[ArchConfigType, Any] = {
|
||||
ArchConfigType.VERSION: self.version,
|
||||
ArchConfigType.SCRIPT: self.script,
|
||||
ArchConfigType.ARCHINSTALL_LANGUAGE: self.archinstall_language.json(),
|
||||
}
|
||||
|
||||
base_config.update(self.plain_cfg())
|
||||
sub_config = self.sub_cfg()
|
||||
|
||||
for config_type, value in sub_config.items():
|
||||
if not hasattr(value, 'json'):
|
||||
raise ValueError(f'Config value for {config_type} must implement json() method')
|
||||
base_config[config_type] = value.json()
|
||||
|
||||
return base_config
|
||||
|
||||
def plain_cfg(self) -> dict[ArchConfigType, str | list[str] | bool]:
|
||||
return {
|
||||
ArchConfigType.HOSTNAME: self.hostname,
|
||||
ArchConfigType.KERNELS: self.kernels,
|
||||
ArchConfigType.NTP: self.ntp,
|
||||
ArchConfigType.TIMEZONE: self.timezone,
|
||||
ArchConfigType.SERVICES: self.services,
|
||||
ArchConfigType.PACKAGES: self.packages,
|
||||
ArchConfigType.CUSTOM_COMMANDS: self.custom_commands,
|
||||
}
|
||||
|
||||
def sub_cfg(self) -> dict[ArchConfigType, SubConfig]:
|
||||
cfg: dict[ArchConfigType, SubConfig] = {
|
||||
ArchConfigType.PACMAN_CONFIG: self.pacman_config,
|
||||
}
|
||||
|
||||
if self.mirror_config:
|
||||
cfg[ArchConfigType.MIRROR_CONFIG] = self.mirror_config
|
||||
|
||||
if self.bootloader_config:
|
||||
cfg[ArchConfigType.BOOTLOADER_CONFIG] = self.bootloader_config
|
||||
|
||||
if self.disk_config:
|
||||
cfg[ArchConfigType.DISK_CONFIG] = self.disk_config
|
||||
|
||||
if self.swap:
|
||||
cfg[ArchConfigType.SWAP] = self.swap
|
||||
|
||||
if self.auth_config:
|
||||
cfg[ArchConfigType.AUTH_CONFIG] = self.auth_config
|
||||
|
||||
if self.locale_config:
|
||||
cfg[ArchConfigType.LOCALE_CONFIG] = self.locale_config
|
||||
|
||||
if self.profile_config:
|
||||
cfg[ArchConfigType.PROFILE_CONFIG] = self.profile_config
|
||||
|
||||
if self.network_config:
|
||||
cfg[ArchConfigType.NETWORK_CONFIG] = self.network_config
|
||||
|
||||
if self.app_config:
|
||||
cfg[ArchConfigType.APP_CONFIG] = self.app_config
|
||||
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self:
|
||||
arch_config = cls()
|
||||
|
||||
arch_config.locale_config = LocaleConfiguration.parse_arg(args_config)
|
||||
|
||||
if script := args_config.get('script', None):
|
||||
arch_config.script = script
|
||||
|
||||
if archinstall_lang := args_config.get('archinstall-language', None):
|
||||
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
|
||||
translation_handler.activate(arch_config.archinstall_language, set_font=False)
|
||||
|
||||
if disk_config := args_config.get('disk_config', {}):
|
||||
enc_password = args_config.get('encryption_password', '')
|
||||
password = Password(plaintext=enc_password) if enc_password else None
|
||||
arch_config.disk_config = DiskLayoutConfiguration.parse_arg(disk_config, password)
|
||||
|
||||
# DEPRECATED
|
||||
# backwards compatibility for main level disk_encryption entry
|
||||
disk_encryption: DiskEncryption | None = None
|
||||
|
||||
if args_config.get('disk_encryption', None) is not None and arch_config.disk_config is not None:
|
||||
disk_encryption = DiskEncryption.parse_arg(
|
||||
arch_config.disk_config,
|
||||
args_config['disk_encryption'],
|
||||
Password(plaintext=args_config.get('encryption_password', '')),
|
||||
)
|
||||
|
||||
if disk_encryption:
|
||||
arch_config.disk_config.disk_encryption = disk_encryption
|
||||
|
||||
if profile_config := args_config.get('profile_config', None):
|
||||
arch_config.profile_config = ProfileConfiguration.parse_arg(profile_config)
|
||||
|
||||
if mirror_config := args_config.get('mirror_config', None):
|
||||
backwards_compatible_repo = []
|
||||
if additional_repositories := args_config.get('additional-repositories', []):
|
||||
backwards_compatible_repo = [Repository(r) for r in additional_repositories]
|
||||
|
||||
arch_config.mirror_config = MirrorConfiguration.parse_args(
|
||||
mirror_config,
|
||||
backwards_compatible_repo,
|
||||
)
|
||||
|
||||
if net_config := args_config.get('network_config', None):
|
||||
arch_config.network_config = NetworkConfiguration.parse_arg(net_config)
|
||||
|
||||
if bootloader_config_dict := args_config.get('bootloader_config', None):
|
||||
arch_config.bootloader_config = BootloaderConfiguration.parse_arg(bootloader_config_dict, args.skip_boot)
|
||||
# DEPRECATED: separate bootloader and uki fields (backward compatibility)
|
||||
elif bootloader_str := args_config.get('bootloader', None):
|
||||
bootloader = Bootloader.from_arg(bootloader_str, args.skip_boot)
|
||||
uki = args_config.get('uki', False)
|
||||
if uki and not bootloader.has_uki_support():
|
||||
uki = False
|
||||
arch_config.bootloader_config = BootloaderConfiguration(bootloader=bootloader, uki=uki, removable=True)
|
||||
|
||||
# deprecated: backwards compatibility
|
||||
audio_config_args = args_config.get('audio_config', None)
|
||||
app_config_args = args_config.get('app_config', None)
|
||||
|
||||
if audio_config_args is not None or app_config_args is not None:
|
||||
arch_config.app_config = ApplicationConfiguration.parse_arg(app_config_args, audio_config_args)
|
||||
|
||||
if auth_config_args := args_config.get('auth_config', None):
|
||||
arch_config.auth_config = AuthenticationConfiguration.parse_arg(auth_config_args)
|
||||
|
||||
if hostname := args_config.get('hostname', ''):
|
||||
arch_config.hostname = hostname
|
||||
|
||||
if kernels := args_config.get('kernels', []):
|
||||
arch_config.kernels = kernels
|
||||
|
||||
arch_config.ntp = args_config.get('ntp', True)
|
||||
|
||||
if packages := args_config.get('packages', []):
|
||||
arch_config.packages = packages
|
||||
|
||||
if pacman_config := args_config.get('pacman_config', None):
|
||||
arch_config.pacman_config = PacmanConfiguration.parse_arg(pacman_config)
|
||||
elif parallel_downloads := args_config.get('parallel_downloads', 0):
|
||||
arch_config.pacman_config = PacmanConfiguration(parallel_downloads=int(parallel_downloads))
|
||||
|
||||
swap_arg = args_config.get('swap')
|
||||
if swap_arg is not None:
|
||||
arch_config.swap = ZramConfiguration.parse_arg(swap_arg)
|
||||
|
||||
if timezone := args_config.get('timezone', 'UTC'):
|
||||
arch_config.timezone = timezone
|
||||
|
||||
if services := args_config.get('services', []):
|
||||
arch_config.services = services
|
||||
|
||||
# DEPRECATED: backwards compatibility
|
||||
root_password = None
|
||||
if root_password := args_config.get('!root-password', None):
|
||||
root_password = Password(plaintext=root_password)
|
||||
|
||||
if enc_password := args_config.get('root_enc_password', None):
|
||||
root_password = Password(enc_password=enc_password)
|
||||
|
||||
if root_password is not None:
|
||||
if arch_config.auth_config is None:
|
||||
arch_config.auth_config = AuthenticationConfiguration()
|
||||
arch_config.auth_config.root_enc_password = root_password
|
||||
|
||||
# DEPRECATED: backwards compatibility
|
||||
users: list[User] = []
|
||||
if args_users := args_config.get('!users', None):
|
||||
users = User.parse_arguments(args_users)
|
||||
|
||||
if args_users := args_config.get('users', None):
|
||||
users = User.parse_arguments(args_users)
|
||||
|
||||
if users:
|
||||
if arch_config.auth_config is None:
|
||||
arch_config.auth_config = AuthenticationConfiguration()
|
||||
arch_config.auth_config.users = users
|
||||
|
||||
if custom_commands := args_config.get('custom_commands', []):
|
||||
arch_config.custom_commands = custom_commands
|
||||
|
||||
return arch_config
|
||||
|
||||
|
||||
class ArchConfigHandler:
|
||||
def __init__(self) -> None:
|
||||
self._parser: ArgumentParser = self._define_arguments()
|
||||
self._add_sub_parsers()
|
||||
|
||||
self._args: Arguments = self._parse_args()
|
||||
config = self._parse_config()
|
||||
|
||||
try:
|
||||
self._config = ArchConfig.from_config(config, self._args)
|
||||
self._config.version = get_version()
|
||||
except ValueError as err:
|
||||
warn(str(err))
|
||||
sys.exit(1)
|
||||
|
||||
@property
|
||||
def config(self) -> ArchConfig:
|
||||
return self._config
|
||||
|
||||
@property
|
||||
def args(self) -> Arguments:
|
||||
return self._args
|
||||
|
||||
def get_script(self) -> str:
|
||||
if script := self.args.script:
|
||||
return script
|
||||
|
||||
if script := self.config.script:
|
||||
return script
|
||||
|
||||
return 'guided'
|
||||
|
||||
def print_help(self) -> None:
|
||||
self._parser.print_help()
|
||||
|
||||
def _add_sub_parsers(self) -> None:
|
||||
subparsers = self._parser.add_subparsers(dest='command', help='Available subcommands')
|
||||
_ = subparsers.add_parser(SubCommand.SHARE_LOG.value, help='Upload log file to public server')
|
||||
|
||||
def _define_arguments(self) -> ArgumentParser:
|
||||
parser = ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument(
|
||||
'-v',
|
||||
'--version',
|
||||
action='version',
|
||||
default=False,
|
||||
version='%(prog)s ' + get_version(),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
type=Path,
|
||||
nargs='?',
|
||||
default=None,
|
||||
help='JSON configuration file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--config-url',
|
||||
type=str,
|
||||
nargs='?',
|
||||
default=None,
|
||||
help='Url to a JSON configuration file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--creds',
|
||||
type=Path,
|
||||
nargs='?',
|
||||
default=None,
|
||||
help='JSON credentials configuration file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--creds-url',
|
||||
type=str,
|
||||
nargs='?',
|
||||
default=None,
|
||||
help='Url to a JSON credentials configuration file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--creds-decryption-key',
|
||||
type=str,
|
||||
nargs='?',
|
||||
default=None,
|
||||
help='Decryption key for credentials file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--silent',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
'--dry_run',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Generates a configuration file and then exits instead of performing an installation',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--script',
|
||||
nargs='?',
|
||||
help='Script to run for installation',
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--mountpoint',
|
||||
type=Path,
|
||||
nargs='?',
|
||||
default=Path('/mnt'),
|
||||
help='Define an alternate mount point for installation',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-ntp',
|
||||
action='store_true',
|
||||
help='Disables NTP checks during installation',
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-wkd',
|
||||
action='store_true',
|
||||
help='Disables checking if archlinux keyring wkd sync is complete.',
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-boot',
|
||||
action='store_true',
|
||||
help='Disables installation of a boot loader (note: only use this when problems arise with the boot loader step).',
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--debug',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Adds debug info into the log',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--offline',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Disabled online upstream services such as package search and key-ring auto update.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no-pkg-lookups',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Disabled package validation specifically prior to starting installation.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--plugin',
|
||||
nargs='?',
|
||||
type=str,
|
||||
default=None,
|
||||
help='File path to a plugin to load',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-version-check',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Skip the version check when running archinstall',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skip-wifi-check',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Skip wifi check when running archinstall',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--advanced',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Enabled advanced options',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Enabled verbose options',
|
||||
)
|
||||
return parser
|
||||
|
||||
def _parse_args(self) -> Arguments:
|
||||
argparse_args = vars(self._parser.parse_args())
|
||||
args: Arguments = Arguments(**argparse_args)
|
||||
|
||||
# amend the parameters (check internal consistency)
|
||||
# Installation can't be silent if config is not passed
|
||||
if args.config is None and args.config_url is None:
|
||||
args.silent = False
|
||||
|
||||
if args.debug:
|
||||
warn(f'Warning: --debug mode will write certain credentials to {logger.path}!')
|
||||
|
||||
if args.plugin:
|
||||
plugin_path = Path(args.plugin)
|
||||
load_plugin(plugin_path)
|
||||
|
||||
if args.creds_decryption_key is None:
|
||||
if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'):
|
||||
args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY')
|
||||
|
||||
return args
|
||||
|
||||
def _parse_config(self) -> dict[str, Any]:
|
||||
config: dict[str, Any] = {}
|
||||
config_data: str | None = None
|
||||
creds_data: str | None = None
|
||||
|
||||
if self._args.config is not None:
|
||||
config_data = self._read_file(self._args.config)
|
||||
elif self._args.config_url is not None:
|
||||
config_data = self._fetch_from_url(self._args.config_url)
|
||||
|
||||
if config_data is not None:
|
||||
config.update(json.loads(config_data))
|
||||
|
||||
if self._args.creds is not None:
|
||||
creds_data = self._read_file(self._args.creds)
|
||||
elif self._args.creds_url is not None:
|
||||
creds_data = self._fetch_from_url(self._args.creds_url)
|
||||
|
||||
if creds_data is not None:
|
||||
json_data = self._process_creds_data(creds_data)
|
||||
if json_data is not None:
|
||||
config.update(json_data)
|
||||
|
||||
config = self._cleanup_config(config)
|
||||
|
||||
return config
|
||||
|
||||
def _process_creds_data(self, creds_data: str) -> dict[str, Any] | None:
|
||||
if creds_data.startswith('$'): # encrypted data
|
||||
if self._args.creds_decryption_key is not None:
|
||||
try:
|
||||
creds_data = decrypt(creds_data, self._args.creds_decryption_key)
|
||||
return json.loads(creds_data)
|
||||
except ValueError as err:
|
||||
if 'Invalid password' in str(err):
|
||||
error(tr('Incorrect credentials file decryption password'))
|
||||
sys.exit(1)
|
||||
else:
|
||||
debug(f'Error decrypting credentials file: {err}')
|
||||
raise err from err
|
||||
else:
|
||||
header = tr('Enter credentials file decryption password')
|
||||
wrong_pwd_text = tr('Incorrect password')
|
||||
prompt = header
|
||||
|
||||
while True:
|
||||
decryption_pwd: Password | None = tui.run(
|
||||
lambda p=prompt: get_password( # type: ignore[misc]
|
||||
header=p,
|
||||
allow_skip=False,
|
||||
no_confirmation=True,
|
||||
)
|
||||
)
|
||||
|
||||
if not decryption_pwd:
|
||||
return None
|
||||
|
||||
try:
|
||||
creds_data = decrypt(creds_data, decryption_pwd.plaintext)
|
||||
break
|
||||
except ValueError as err:
|
||||
if 'Invalid password' in str(err):
|
||||
debug('Incorrect credentials file decryption password')
|
||||
prompt = f'{header}' + f'\n\n{wrong_pwd_text}'
|
||||
else:
|
||||
debug(f'Error decrypting credentials file: {err}')
|
||||
raise err from err
|
||||
|
||||
return json.loads(creds_data)
|
||||
|
||||
def _fetch_from_url(self, url: str) -> str:
|
||||
if urllib.parse.urlparse(url).scheme:
|
||||
try:
|
||||
req = Request(url, headers={'User-Agent': 'ArchInstall'})
|
||||
with urlopen(req) as resp:
|
||||
return resp.read().decode('utf-8')
|
||||
except urllib.error.HTTPError as err:
|
||||
error(f'Could not fetch JSON from {url}: {err}')
|
||||
else:
|
||||
error('Not a valid url')
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
def _read_file(self, path: Path) -> str:
|
||||
if not path.exists():
|
||||
error(f'Could not find file {path}')
|
||||
sys.exit(1)
|
||||
|
||||
return path.read_text()
|
||||
|
||||
def _cleanup_config(self, config: Namespace | dict[str, Any]) -> dict[str, Any]:
|
||||
clean_args = {}
|
||||
for key, val in config.items():
|
||||
if isinstance(val, dict):
|
||||
val = self._cleanup_config(val)
|
||||
|
||||
if val is not None:
|
||||
clean_args[key] = val
|
||||
|
||||
return clean_args
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import getpass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from archinstall.lib.command import SysCommandWorker
|
||||
from archinstall.lib.log import debug, info
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
||||
|
||||
class AuthenticationHandler:
|
||||
def setup_auth(
|
||||
self,
|
||||
install_session: Installer,
|
||||
auth_config: AuthenticationConfiguration,
|
||||
hostname: str,
|
||||
) -> None:
|
||||
if auth_config.u2f_config and auth_config.users is not None:
|
||||
self._setup_u2f_login(install_session, auth_config.u2f_config, auth_config.users, hostname)
|
||||
|
||||
def _setup_u2f_login(self, install_session: Installer, u2f_config: U2FLoginConfiguration, users: list[User], hostname: str) -> None:
|
||||
self._configure_u2f_mapping(install_session, u2f_config, users, hostname)
|
||||
self._update_pam_config(install_session, u2f_config)
|
||||
|
||||
def _update_pam_config(
|
||||
self,
|
||||
install_session: Installer,
|
||||
u2f_config: U2FLoginConfiguration,
|
||||
) -> None:
|
||||
match u2f_config.u2f_login_method:
|
||||
case U2FLoginMethod.Passwordless:
|
||||
config_entry = 'auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue'
|
||||
case U2FLoginMethod.SecondFactor:
|
||||
config_entry = 'auth required pam_u2f.so authfile=/etc/u2f_mappings cue'
|
||||
case _:
|
||||
raise ValueError(f'Unknown U2F login method: {u2f_config.u2f_login_method}')
|
||||
|
||||
debug(f'U2F PAM configuration: {config_entry}')
|
||||
debug(f'Passwordless sudo enabled: {u2f_config.passwordless_sudo}')
|
||||
|
||||
sudo_config = install_session.target / 'etc/pam.d/sudo'
|
||||
sys_login = install_session.target / 'etc/pam.d/system-login'
|
||||
|
||||
if u2f_config.passwordless_sudo:
|
||||
self._add_u2f_entry(sudo_config, config_entry)
|
||||
|
||||
self._add_u2f_entry(sys_login, config_entry)
|
||||
|
||||
def _add_u2f_entry(self, file: Path, entry: str) -> None:
|
||||
if not file.exists():
|
||||
debug(f'File does not exist: {file}')
|
||||
return
|
||||
|
||||
content = file.read_text().splitlines()
|
||||
|
||||
# remove any existing u2f auth entry
|
||||
content = [line for line in content if 'pam_u2f.so' not in line]
|
||||
|
||||
# add the u2f auth entry as the first one after comments
|
||||
for i, line in enumerate(content):
|
||||
if not line.startswith('#'):
|
||||
content.insert(i, entry)
|
||||
break
|
||||
else:
|
||||
content.append(entry)
|
||||
|
||||
file.write_text('\n'.join(content) + '\n')
|
||||
|
||||
def _configure_u2f_mapping(
|
||||
self,
|
||||
install_session: Installer,
|
||||
u2f_config: U2FLoginConfiguration,
|
||||
users: list[User],
|
||||
hostname: str,
|
||||
) -> None:
|
||||
debug(f'Setting up U2F login: {u2f_config.u2f_login_method.value}')
|
||||
|
||||
install_session.pacman.strap('pam-u2f')
|
||||
|
||||
print(tr('Setting up U2F login: {}').format(u2f_config.u2f_login_method.value))
|
||||
|
||||
# https://developers.yubico.com/pam-u2f/
|
||||
u2f_auth_file = install_session.target / 'etc/u2f_mappings'
|
||||
u2f_auth_file.touch()
|
||||
existing_keys = u2f_auth_file.read_text()
|
||||
|
||||
registered_keys: list[str] = []
|
||||
|
||||
for user in users:
|
||||
print('')
|
||||
info(tr('Setting up U2F device for user: {}').format(user.username))
|
||||
info(tr('You may need to enter the PIN and then touch your U2F device to register it'))
|
||||
|
||||
cmd = ' '.join(
|
||||
['arch-chroot', '-S', str(install_session.target), 'pamu2fcfg', '-u', user.username, '-o', f'pam://{hostname}', '-i', f'pam://{hostname}']
|
||||
)
|
||||
|
||||
debug(f'Enrolling U2F device: {cmd}')
|
||||
|
||||
worker = SysCommandWorker(cmd, peek_output=True)
|
||||
pin_inputted = False
|
||||
|
||||
while worker.is_alive():
|
||||
if pin_inputted is False:
|
||||
if bytes('enter pin for', 'UTF-8') in worker._trace_log.lower():
|
||||
worker.write(bytes(getpass.getpass(''), 'UTF-8'))
|
||||
pin_inputted = True
|
||||
|
||||
output = worker.decode().strip().splitlines()
|
||||
debug(f'Output from pamu2fcfg: {output}')
|
||||
|
||||
key = output[-1].strip()
|
||||
registered_keys.append(key)
|
||||
|
||||
all_keys = '\n'.join(registered_keys)
|
||||
|
||||
if existing_keys:
|
||||
existing_keys += f'\n{all_keys}'
|
||||
else:
|
||||
existing_keys = all_keys
|
||||
|
||||
u2f_auth_file.write_text(existing_keys)
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.lib.disk.fido import Fido2
|
||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.menu.util import get_password
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
|
||||
from archinstall.lib.models.users import Password, User
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.user.user_menu import select_users
|
||||
from archinstall.lib.utils.format import as_table
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
|
||||
def __init__(self, preset: AuthenticationConfiguration | None = None):
|
||||
if preset:
|
||||
self._auth_config = preset
|
||||
else:
|
||||
self._auth_config = AuthenticationConfiguration()
|
||||
|
||||
menu_options = self._define_menu_options()
|
||||
self._item_group = MenuItemGroup(menu_options, checkmarks=True)
|
||||
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._auth_config,
|
||||
allow_reset=True,
|
||||
)
|
||||
|
||||
@override
|
||||
async def show(self) -> AuthenticationConfiguration | None:
|
||||
return await super().show()
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
return [
|
||||
MenuItem(
|
||||
text=tr('Root password'),
|
||||
action=lambda x: select_root_password(),
|
||||
preview_action=self._prev_root_pwd,
|
||||
key='root_enc_password',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('User account'),
|
||||
action=self._create_user_account,
|
||||
preview_action=self._prev_users,
|
||||
key='users',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('U2F login setup'),
|
||||
action=select_u2f_login,
|
||||
value=self._auth_config.u2f_config,
|
||||
preview_action=self._prev_u2f_login,
|
||||
key='u2f_config',
|
||||
),
|
||||
]
|
||||
|
||||
async def _create_user_account(self, preset: list[User] | None = None) -> list[User]:
|
||||
preset = [] if preset is None else preset
|
||||
users = await select_users(preset=preset)
|
||||
return users
|
||||
|
||||
def _prev_users(self, item: MenuItem) -> str | None:
|
||||
users: list[User] | None = item.value
|
||||
|
||||
if users:
|
||||
return as_table(users)
|
||||
return None
|
||||
|
||||
def _prev_root_pwd(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
password: Password = item.value
|
||||
return f'{tr("Root password")}: {password.hidden()}'
|
||||
return None
|
||||
|
||||
def _depends_on_u2f(self) -> bool:
|
||||
devices = Fido2.get_fido2_devices()
|
||||
if not devices:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _prev_u2f_login(self, item: MenuItem) -> str | None:
|
||||
if item.value is not None:
|
||||
u2f_config: U2FLoginConfiguration = item.value
|
||||
|
||||
login_method = u2f_config.u2f_login_method.display_value()
|
||||
output = tr('U2F login method: ') + login_method
|
||||
|
||||
output += '\n'
|
||||
output += tr('Passwordless sudo: ') + (tr('Enabled') if u2f_config.passwordless_sudo else tr('Disabled'))
|
||||
|
||||
return output
|
||||
|
||||
devices = Fido2.get_fido2_devices()
|
||||
if not devices:
|
||||
return tr('No U2F devices found')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def select_root_password() -> Password | None:
|
||||
password = await get_password(header=tr('Enter root password'), allow_skip=True)
|
||||
return password
|
||||
|
||||
|
||||
async def select_u2f_login(preset: U2FLoginConfiguration | None) -> U2FLoginConfiguration | None:
|
||||
devices = Fido2.get_fido2_devices()
|
||||
if not devices:
|
||||
return None
|
||||
|
||||
items = []
|
||||
for method in U2FLoginMethod:
|
||||
items.append(MenuItem(method.display_value(), value=method))
|
||||
|
||||
group = MenuItemGroup(items)
|
||||
|
||||
if preset is not None:
|
||||
group.set_selected_by_value(preset.u2f_login_method)
|
||||
|
||||
result = await Selection[U2FLoginMethod](
|
||||
group,
|
||||
allow_skip=True,
|
||||
allow_reset=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
u2f_method = result.get_value()
|
||||
header = tr('Enable passwordless sudo?')
|
||||
|
||||
result_sudo = await Confirmation(
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
preset=False,
|
||||
).show()
|
||||
|
||||
passwordless_sudo = result_sudo.item() == MenuItem.yes()
|
||||
|
||||
return U2FLoginConfiguration(
|
||||
u2f_login_method=u2f_method,
|
||||
passwordless_sudo=passwordless_sudo,
|
||||
)
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Reset:
|
||||
return None
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import time
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import ClassVar, Self
|
||||
|
||||
from archinstall.lib.command import SysCommand, SysCommandWorker
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.log import error
|
||||
|
||||
|
||||
class Boot:
|
||||
_active_boot: ClassVar[Self | None] = None
|
||||
|
||||
def __init__(self, path: Path | str):
|
||||
if isinstance(path, Path):
|
||||
path = str(path)
|
||||
|
||||
self.path = path
|
||||
self.container_name = 'archinstall'
|
||||
self.session: SysCommandWorker | None = None
|
||||
self.ready = False
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
if Boot._active_boot and Boot._active_boot.path != self.path:
|
||||
raise KeyError('Archinstall only supports booting up one instance and another session is already active.')
|
||||
|
||||
if Boot._active_boot:
|
||||
self.session = Boot._active_boot.session
|
||||
self.ready = Boot._active_boot.ready
|
||||
else:
|
||||
# '-P' or --console=pipe could help us not having to do a bunch
|
||||
# of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
|
||||
self.session = SysCommandWorker(
|
||||
[
|
||||
'systemd-nspawn',
|
||||
'-D',
|
||||
self.path,
|
||||
'--timezone=off',
|
||||
'-b',
|
||||
'--no-pager',
|
||||
'--machine',
|
||||
self.container_name,
|
||||
]
|
||||
)
|
||||
|
||||
if not self.ready and self.session:
|
||||
while self.session.is_alive():
|
||||
if b' login:' in self.session:
|
||||
self.ready = True
|
||||
break
|
||||
|
||||
Boot._active_boot = 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 exc_type is not None:
|
||||
error(
|
||||
str(exc_value),
|
||||
f'The error above occurred in a temporary boot-up of the installation {self.path!r}',
|
||||
)
|
||||
|
||||
shutdown = None
|
||||
shutdown_exit_code: int | None = -1
|
||||
|
||||
try:
|
||||
shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty shutdown now')
|
||||
except SysCallError as err:
|
||||
shutdown_exit_code = err.exit_code
|
||||
|
||||
if self.session:
|
||||
while self.session.is_alive():
|
||||
time.sleep(0.25)
|
||||
|
||||
if shutdown and shutdown.exit_code:
|
||||
shutdown_exit_code = shutdown.exit_code
|
||||
|
||||
if self.session and (self.session.exit_code == 0 or shutdown_exit_code == 0):
|
||||
Boot._active_boot = None
|
||||
else:
|
||||
session_exit_code = self.session.exit_code if self.session else -1
|
||||
|
||||
raise SysCallError(
|
||||
f'Could not shut down temporary boot of {self.path!r}: {session_exit_code}/{shutdown_exit_code}',
|
||||
exit_code=next(filter(bool, [session_exit_code, shutdown_exit_code])),
|
||||
)
|
||||
|
||||
def __iter__(self) -> Iterator[bytes]:
|
||||
if self.session:
|
||||
yield from self.session
|
||||
|
||||
def __contains__(self, key: bytes) -> bool:
|
||||
if self.session is None:
|
||||
return False
|
||||
|
||||
return key in self.session
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
if self.session is None:
|
||||
return False
|
||||
|
||||
return self.session.is_alive()
|
||||
|
||||
def SysCommand(self, cmd: list[str], *args, **kwargs) -> SysCommand: # type: ignore[no-untyped-def]
|
||||
return SysCommand(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
|
||||
|
||||
def SysCommandWorker(self, cmd: list[str], *args, **kwargs) -> SysCommandWorker: # type: ignore[no-untyped-def]
|
||||
return SysCommandWorker(['systemd-run', f'--machine={self.container_name}', '--pty', *cmd], *args, **kwargs)
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
import textwrap
|
||||
from typing import override
|
||||
|
||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
|
||||
def __init__(
|
||||
self,
|
||||
bootloader_conf: BootloaderConfiguration,
|
||||
uefi: bool,
|
||||
skip_boot: bool = False,
|
||||
):
|
||||
self._bootloader_conf = bootloader_conf
|
||||
self._skip_boot = skip_boot
|
||||
self._uefi = uefi
|
||||
menu_options = self._define_menu_options()
|
||||
|
||||
self._item_group = MenuItemGroup(menu_options, sort_items=False, checkmarks=True)
|
||||
super().__init__(
|
||||
self._item_group,
|
||||
config=self._bootloader_conf,
|
||||
allow_reset=False,
|
||||
)
|
||||
|
||||
def _define_menu_options(self) -> list[MenuItem]:
|
||||
bootloader = self._bootloader_conf.bootloader
|
||||
|
||||
# UKI availability
|
||||
uki_enabled = self._uefi and bootloader.has_uki_support()
|
||||
if not uki_enabled:
|
||||
self._bootloader_conf.uki = False
|
||||
|
||||
# Removable availability
|
||||
removable_enabled = self._uefi and bootloader.has_removable_support()
|
||||
if not removable_enabled:
|
||||
self._bootloader_conf.removable = False
|
||||
|
||||
return [
|
||||
MenuItem(
|
||||
text=tr('Bootloader'),
|
||||
action=self._select_bootloader,
|
||||
value=self._bootloader_conf.bootloader,
|
||||
preview_action=self._prev_bootloader,
|
||||
mandatory=True,
|
||||
key='bootloader',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Unified kernel images'),
|
||||
action=self._select_uki,
|
||||
value=self._bootloader_conf.uki,
|
||||
preview_action=self._prev_uki,
|
||||
key='uki',
|
||||
enabled=uki_enabled,
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Install to removable location'),
|
||||
action=self._select_removable,
|
||||
value=self._bootloader_conf.removable,
|
||||
preview_action=self._prev_removable,
|
||||
key='removable',
|
||||
enabled=removable_enabled,
|
||||
),
|
||||
]
|
||||
|
||||
def _prev_bootloader(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return f'{tr("Bootloader")}: {item.value.value}'
|
||||
return None
|
||||
|
||||
def _prev_uki(self, item: MenuItem) -> str | None:
|
||||
uki_text = f'{tr("Unified kernel images")}'
|
||||
if item.value:
|
||||
return f'{uki_text}: {tr("Enabled")}'
|
||||
else:
|
||||
return f'{uki_text}: {tr("Disabled")}'
|
||||
|
||||
def _prev_removable(self, item: MenuItem) -> str | None:
|
||||
if item.value:
|
||||
return tr('Will install to /EFI/BOOT/ (removable location, safe default)')
|
||||
return tr('Will install to custom location with NVRAM entry')
|
||||
|
||||
@override
|
||||
async def show(self) -> BootloaderConfiguration:
|
||||
_ = await super().show()
|
||||
return self._bootloader_conf
|
||||
|
||||
async def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | None:
|
||||
bootloader = await select_bootloader(preset, self._uefi, self._skip_boot)
|
||||
|
||||
if bootloader:
|
||||
# Update UKI option based on bootloader
|
||||
uki_item = self._menu_item_group.find_by_key('uki')
|
||||
if not self._uefi or not bootloader.has_uki_support():
|
||||
uki_item.enabled = False
|
||||
uki_item.value = False
|
||||
self._bootloader_conf.uki = False
|
||||
else:
|
||||
uki_item.enabled = True
|
||||
|
||||
# Update removable option based on bootloader
|
||||
removable_item = self._menu_item_group.find_by_key('removable')
|
||||
if not self._uefi or not bootloader.has_removable_support():
|
||||
removable_item.enabled = False
|
||||
removable_item.value = False
|
||||
self._bootloader_conf.removable = False
|
||||
else:
|
||||
if not removable_item.enabled:
|
||||
removable_item.value = True
|
||||
self._bootloader_conf.removable = True
|
||||
removable_item.enabled = True
|
||||
|
||||
return bootloader
|
||||
|
||||
async def _select_uki(self, preset: bool) -> bool:
|
||||
prompt = tr('Would you like to use unified kernel images?') + '\n'
|
||||
|
||||
result = await Confirmation(header=prompt, allow_skip=True, preset=preset).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.item() == MenuItem.yes()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
async def _select_removable(self, preset: bool) -> bool:
|
||||
prompt = (
|
||||
tr('Would you like to install the bootloader to the default removable media search location?')
|
||||
+ '\n\n'
|
||||
+ tr('This installs the bootloader to /EFI/BOOT/BOOTX64.EFI (or similar) which is useful for:')
|
||||
+ '\n\n • '
|
||||
+ tr('Firmware that does not properly support NVRAM boot entries like most MSI motherboards,')
|
||||
+ '\n '
|
||||
+ tr('most Apple Macs, many laptops...')
|
||||
+ '\n • '
|
||||
+ tr('USB drives or other portable external media.')
|
||||
+ '\n • '
|
||||
+ tr('Systems where you want the disk to be bootable on any computer.')
|
||||
+ '\n\n'
|
||||
+ tr(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
If you do not know what this means, LEAVE THIS OPTION ENABLED, as it is the safe default.
|
||||
|
||||
It is suggested to disable this if none of the above apply, as it makes installing multiple
|
||||
EFI bootloaders on the same disk easier, and it will not overwrite whatever bootloader
|
||||
was previously installed at the default removable media search location, if any.
|
||||
|
||||
It may also make the installation more resilient in case of dual-booting with Windows,
|
||||
as Windows is known to sometimes erase or replace the bootloader installed at the removable
|
||||
location.
|
||||
"""
|
||||
)
|
||||
)
|
||||
+ '\n'
|
||||
)
|
||||
|
||||
result = await Confirmation(
|
||||
header=prompt,
|
||||
allow_skip=True,
|
||||
preset=preset,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
||||
|
||||
async def select_bootloader(
|
||||
preset: Bootloader | None,
|
||||
uefi: bool,
|
||||
skip_boot: bool = False,
|
||||
) -> Bootloader | None:
|
||||
options = []
|
||||
hidden_options = []
|
||||
header = tr('Select bootloader to install')
|
||||
|
||||
default = Bootloader.get_default(uefi, skip_boot)
|
||||
|
||||
if not skip_boot:
|
||||
hidden_options += [Bootloader.NO_BOOTLOADER]
|
||||
|
||||
if not uefi:
|
||||
options += [Bootloader.Grub, Bootloader.Limine]
|
||||
header += '\n' + tr('UEFI is not detected and some options are disabled')
|
||||
else:
|
||||
options += [b for b in Bootloader if b not in hidden_options]
|
||||
|
||||
items = [MenuItem(o.value, value=o) for o in options]
|
||||
group = MenuItemGroup(items)
|
||||
group.set_default_by_value(default)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = await Selection[Bootloader](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Reset:
|
||||
raise ValueError('Unhandled result type')
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import DiskLayoutConfiguration
|
||||
|
||||
|
||||
class BootloaderValidationFailureKind(Enum):
|
||||
LimineNonFatBoot = auto()
|
||||
LimineLayout = auto()
|
||||
BootloaderRequiresUefi = auto()
|
||||
EfistubNonFatBoot = auto()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BootloaderValidationFailure:
|
||||
kind: BootloaderValidationFailureKind
|
||||
description: str
|
||||
|
||||
|
||||
def validate_bootloader_layout(
|
||||
bootloader_config: BootloaderConfiguration | None,
|
||||
disk_config: DiskLayoutConfiguration | None,
|
||||
) -> BootloaderValidationFailure | None:
|
||||
"""Validate bootloader configuration against disk layout.
|
||||
|
||||
Returns a failure with a human-readable description if the configuration
|
||||
would produce an unbootable system, or None if it is valid.
|
||||
"""
|
||||
if not (bootloader_config and disk_config):
|
||||
return None
|
||||
|
||||
bootloader = bootloader_config.bootloader
|
||||
|
||||
if bootloader == Bootloader.NO_BOOTLOADER:
|
||||
return None
|
||||
|
||||
if bootloader.is_uefi_only() and not SysInfo.has_uefi():
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.BootloaderRequiresUefi,
|
||||
description=f'{bootloader.value} requires a UEFI system.',
|
||||
)
|
||||
|
||||
boot_part = next(
|
||||
(p for m in disk_config.device_modifications if (p := m.get_boot_partition())),
|
||||
None,
|
||||
)
|
||||
|
||||
if bootloader == Bootloader.Efistub:
|
||||
# The UEFI firmware reads the kernel directly from the boot partition,
|
||||
# which must be FAT.
|
||||
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.EfistubNonFatBoot,
|
||||
description='Efistub does not support booting with a non-FAT boot partition.',
|
||||
)
|
||||
|
||||
if bootloader == Bootloader.Limine:
|
||||
# Limine reads its config and kernels from the boot partition, which
|
||||
# must be FAT.
|
||||
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.LimineNonFatBoot,
|
||||
description='Limine does not support booting with a non-FAT boot partition.',
|
||||
)
|
||||
|
||||
# When the ESP is the boot partition but mounted outside /boot and
|
||||
# UKI is disabled, kernels end up on the root filesystem which
|
||||
# Limine cannot access.
|
||||
if not bootloader_config.uki:
|
||||
efi_part = next(
|
||||
(p for m in disk_config.device_modifications if (p := m.get_efi_partition())),
|
||||
None,
|
||||
)
|
||||
if efi_part and efi_part == boot_part and efi_part.mountpoint != Path('/boot'):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.LimineLayout,
|
||||
description=(
|
||||
f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_part.mountpoint}, '
|
||||
'enable UKI or add a separate /boot partition to install Limine.'
|
||||
),
|
||||
)
|
||||
|
||||
return None
|
||||
|
|
@ -1,384 +0,0 @@
|
|||
import os
|
||||
import shlex
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Iterator
|
||||
from select import EPOLLHUP, EPOLLIN, epoll
|
||||
from shutil import which
|
||||
from types import TracebackType
|
||||
from typing import Any, Self, override
|
||||
|
||||
from archinstall.lib.exceptions import RequirementError, SysCallError
|
||||
from archinstall.lib.log import debug, error, logger
|
||||
from archinstall.lib.utils.encoding import clear_vt100_escape_codes
|
||||
|
||||
|
||||
class SysCommandWorker:
|
||||
def __init__(
|
||||
self,
|
||||
cmd: str | list[str],
|
||||
peek_output: bool | None = False,
|
||||
environment_vars: dict[str, str] | None = None,
|
||||
working_directory: str = './',
|
||||
remove_vt100_escape_codes_from_lines: bool = True,
|
||||
):
|
||||
if isinstance(cmd, str):
|
||||
cmd = shlex.split(cmd)
|
||||
|
||||
if cmd and not cmd[0].startswith(('/', './')): # Path() does not work well
|
||||
cmd[0] = locate_binary(cmd[0])
|
||||
|
||||
self.cmd = cmd
|
||||
self.peek_output = peek_output
|
||||
# define the standard locale for command outputs. For now the C ascii one. Can be overridden
|
||||
self.environment_vars = {'LC_ALL': 'C'}
|
||||
if environment_vars:
|
||||
self.environment_vars.update(environment_vars)
|
||||
|
||||
self.working_directory = working_directory
|
||||
|
||||
self.exit_code: int | None = None
|
||||
self._trace_log = b''
|
||||
self._trace_log_pos = 0
|
||||
self.poll_object = epoll()
|
||||
self.child_fd: int | None = None
|
||||
self.started = False
|
||||
self.ended = False
|
||||
self.remove_vt100_escape_codes_from_lines: bool = remove_vt100_escape_codes_from_lines
|
||||
|
||||
def __contains__(self, key: bytes) -> bool:
|
||||
"""
|
||||
Contains will also move the current buffert position forward.
|
||||
This is to avoid re-checking the same data when looking for output.
|
||||
"""
|
||||
assert isinstance(key, bytes)
|
||||
|
||||
index = self._trace_log.find(key, self._trace_log_pos)
|
||||
if index >= 0:
|
||||
self._trace_log_pos += index + len(key)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __iter__(self, *args: str, **kwargs: dict[str, Any]) -> Iterator[bytes]:
|
||||
last_line = self._trace_log.rfind(b'\n')
|
||||
lines = filter(None, self._trace_log[self._trace_log_pos : last_line].splitlines())
|
||||
for line in lines:
|
||||
if self.remove_vt100_escape_codes_from_lines:
|
||||
line = clear_vt100_escape_codes(line)
|
||||
|
||||
yield line + b'\n'
|
||||
|
||||
self._trace_log_pos = last_line
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
self.make_sure_we_are_executing()
|
||||
return str(self._trace_log)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
try:
|
||||
return self._trace_log.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return str(self._trace_log)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
|
||||
# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
|
||||
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
|
||||
|
||||
if self.child_fd:
|
||||
try:
|
||||
os.close(self.child_fd)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.peek_output:
|
||||
# To make sure any peaked output didn't leave us hanging
|
||||
# on the same line we were on.
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
if exc_type is not None:
|
||||
debug(str(exc_value))
|
||||
|
||||
if self.exit_code != 0:
|
||||
raise SysCallError(
|
||||
f'{self.cmd} exited with abnormal exit code [{self.exit_code}]: {str(self)[-500:]}',
|
||||
self.exit_code,
|
||||
worker_log=self._trace_log,
|
||||
)
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
self.poll()
|
||||
|
||||
if self.started and not self.ended:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def write(self, data: bytes, line_ending: bool = True) -> int:
|
||||
assert isinstance(data, bytes) # TODO: Maybe we can support str as well and encode it
|
||||
|
||||
self.make_sure_we_are_executing()
|
||||
|
||||
if self.child_fd:
|
||||
return os.write(self.child_fd, data + (b'\n' if line_ending else b''))
|
||||
|
||||
return 0
|
||||
|
||||
def make_sure_we_are_executing(self) -> bool:
|
||||
if not self.started:
|
||||
return self.execute()
|
||||
return True
|
||||
|
||||
def tell(self) -> int:
|
||||
self.make_sure_we_are_executing()
|
||||
return self._trace_log_pos
|
||||
|
||||
def seek(self, pos: int) -> None:
|
||||
self.make_sure_we_are_executing()
|
||||
# Safety check to ensure 0 < pos < len(tracelog)
|
||||
self._trace_log_pos = min(max(0, pos), len(self._trace_log))
|
||||
|
||||
def peak(self, output: str | bytes) -> bool:
|
||||
if self.peek_output:
|
||||
if isinstance(output, bytes):
|
||||
try:
|
||||
output = output.decode('UTF-8')
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
|
||||
_cmd_output(output)
|
||||
|
||||
sys.stdout.write(output)
|
||||
sys.stdout.flush()
|
||||
|
||||
return True
|
||||
|
||||
def poll(self) -> None:
|
||||
self.make_sure_we_are_executing()
|
||||
|
||||
if self.child_fd:
|
||||
got_output = False
|
||||
for _fileno, _event in self.poll_object.poll(0.1):
|
||||
try:
|
||||
output = os.read(self.child_fd, 8192)
|
||||
got_output = True
|
||||
self.peak(output)
|
||||
self._trace_log += output
|
||||
except OSError:
|
||||
self.ended = True
|
||||
break
|
||||
|
||||
if self.ended or (not got_output and not _pid_exists(self.pid)):
|
||||
self.ended = True
|
||||
try:
|
||||
wait_status = os.waitpid(self.pid, 0)[1]
|
||||
self.exit_code = os.waitstatus_to_exitcode(wait_status)
|
||||
except ChildProcessError:
|
||||
try:
|
||||
wait_status = os.waitpid(self.child_fd, 0)[1]
|
||||
self.exit_code = os.waitstatus_to_exitcode(wait_status)
|
||||
except ChildProcessError:
|
||||
self.exit_code = 1
|
||||
|
||||
def execute(self) -> bool:
|
||||
import pty
|
||||
|
||||
if (old_dir := os.getcwd()) != self.working_directory:
|
||||
os.chdir(str(self.working_directory))
|
||||
|
||||
# Note: If for any reason, we get a Python exception between here
|
||||
# and until os.close(), the traceback will get locked inside
|
||||
# stdout of the child_fd object. `os.read(self.child_fd, 8192)` is the
|
||||
# only way to get the traceback without losing it.
|
||||
|
||||
self.pid, self.child_fd = pty.fork()
|
||||
|
||||
# https://stackoverflow.com/questions/4022600/python-pty-fork-how-does-it-work
|
||||
if not self.pid:
|
||||
_cmd_history(self.cmd)
|
||||
|
||||
try:
|
||||
os.execve(self.cmd[0], list(self.cmd), {**os.environ, **self.environment_vars})
|
||||
except FileNotFoundError:
|
||||
error(f'{self.cmd[0]} does not exist.')
|
||||
self.exit_code = 1
|
||||
return False
|
||||
else:
|
||||
# Only parent process moves back to the original working directory
|
||||
os.chdir(old_dir)
|
||||
|
||||
self.started = True
|
||||
self.poll_object.register(self.child_fd, EPOLLIN | EPOLLHUP)
|
||||
|
||||
return True
|
||||
|
||||
def decode(self, encoding: str = 'UTF-8') -> str:
|
||||
return self._trace_log.decode(encoding)
|
||||
|
||||
|
||||
class SysCommand:
|
||||
def __init__(
|
||||
self,
|
||||
cmd: str | list[str],
|
||||
peek_output: bool | None = False,
|
||||
environment_vars: dict[str, str] | None = None,
|
||||
working_directory: str = './',
|
||||
remove_vt100_escape_codes_from_lines: bool = True,
|
||||
):
|
||||
self.cmd = cmd
|
||||
self.peek_output = peek_output
|
||||
self.environment_vars = environment_vars
|
||||
self.working_directory = working_directory
|
||||
self.remove_vt100_escape_codes_from_lines = remove_vt100_escape_codes_from_lines
|
||||
|
||||
self.session: SysCommandWorker | None = None
|
||||
self.create_session()
|
||||
|
||||
def __enter__(self) -> SysCommandWorker | None:
|
||||
return self.session
|
||||
|
||||
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
|
||||
# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
|
||||
# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
|
||||
|
||||
if exc_type is not None:
|
||||
error(str(exc_value))
|
||||
|
||||
def __iter__(self, *args: list[Any], **kwargs: dict[str, Any]) -> Iterator[bytes]:
|
||||
if self.session:
|
||||
yield from self.session
|
||||
|
||||
def __getitem__(self, key: slice) -> bytes:
|
||||
if not self.session:
|
||||
raise KeyError('SysCommand() does not have an active session.')
|
||||
elif type(key) is slice:
|
||||
start = key.start or 0
|
||||
end = key.stop or len(self.session._trace_log)
|
||||
|
||||
return self.session._trace_log[start:end]
|
||||
else:
|
||||
raise ValueError("SysCommand() doesn't have key & value pairs, only slices, SysCommand('ls')[:10] as an example.")
|
||||
|
||||
@override
|
||||
def __repr__(self, *args: list[Any], **kwargs: dict[str, Any]) -> str:
|
||||
return self.decode('UTF-8', errors='backslashreplace') or ''
|
||||
|
||||
def create_session(self) -> bool:
|
||||
"""
|
||||
Initiates a :ref:`SysCommandWorker` session in this class ``.session``.
|
||||
It then proceeds to poll the process until it ends, after which it also
|
||||
clears any printed output if ``.peek_output=True``.
|
||||
"""
|
||||
if self.session:
|
||||
return True
|
||||
|
||||
with SysCommandWorker(
|
||||
self.cmd,
|
||||
peek_output=self.peek_output,
|
||||
environment_vars=self.environment_vars,
|
||||
remove_vt100_escape_codes_from_lines=self.remove_vt100_escape_codes_from_lines,
|
||||
working_directory=self.working_directory,
|
||||
) as session:
|
||||
self.session = session
|
||||
|
||||
while not self.session.ended:
|
||||
self.session.poll()
|
||||
|
||||
if self.peek_output:
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
return True
|
||||
|
||||
def decode(self, encoding: str = 'utf-8', errors: str = 'backslashreplace', strip: bool = True) -> str:
|
||||
if not self.session:
|
||||
raise ValueError('No session available to decode')
|
||||
|
||||
val = self.session._trace_log.decode(encoding, errors=errors)
|
||||
|
||||
if strip:
|
||||
return val.strip()
|
||||
return val
|
||||
|
||||
def output(self, remove_cr: bool = True) -> bytes:
|
||||
if not self.session:
|
||||
raise ValueError('No session available')
|
||||
|
||||
if remove_cr:
|
||||
return self.session._trace_log.replace(b'\r\n', b'\n')
|
||||
|
||||
return self.session._trace_log
|
||||
|
||||
@property
|
||||
def exit_code(self) -> int | None:
|
||||
if self.session:
|
||||
return self.session.exit_code
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def trace_log(self) -> bytes | None:
|
||||
if self.session:
|
||||
return self.session._trace_log
|
||||
return None
|
||||
|
||||
|
||||
def run(
|
||||
cmd: list[str],
|
||||
input_data: bytes | None = None,
|
||||
) -> subprocess.CompletedProcess[bytes]:
|
||||
_cmd_history(cmd)
|
||||
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
input=input_data,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def locate_binary(name: str) -> str:
|
||||
if path := which(name):
|
||||
return path
|
||||
raise RequirementError(f'Binary {name} does not exist.')
|
||||
|
||||
|
||||
def _pid_exists(pid: int) -> bool:
|
||||
try:
|
||||
return any(subprocess.check_output(['ps', '--no-headers', '-o', 'pid', '-p', str(pid)]).strip())
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def _cmd_history(cmd: list[str]) -> None:
|
||||
content = f'{time.time()} {cmd}\n'
|
||||
_append_log('cmd_history.txt', content)
|
||||
|
||||
|
||||
def _cmd_output(output: str) -> None:
|
||||
_append_log('cmd_output.txt', output)
|
||||
|
||||
|
||||
def _append_log(file: str, content: str) -> None:
|
||||
path = logger.directory / file
|
||||
|
||||
change_perm = not path.exists()
|
||||
|
||||
try:
|
||||
with path.open('a') as f:
|
||||
f.write(content)
|
||||
|
||||
if change_perm:
|
||||
path.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
except PermissionError, FileNotFoundError:
|
||||
# If the file does not exist, ignore the error
|
||||
pass
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
import json
|
||||
import readline
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from archinstall.lib.args import ArchConfig, ArchConfigType
|
||||
from archinstall.lib.crypt import encrypt
|
||||
from archinstall.lib.log import debug, logger, warn
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.menu.util import get_password, prompt_dir
|
||||
from archinstall.lib.models.network import NetworkConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.utils.format import as_key_value_pair
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ConfigurationOutput:
|
||||
def __init__(self, config: ArchConfig):
|
||||
"""
|
||||
Configuration output handler to parse the existing
|
||||
configuration data structure and prepare for output on the
|
||||
console and for saving it to configuration files
|
||||
|
||||
:param config: Archinstall configuration object
|
||||
:type config: ArchConfig
|
||||
"""
|
||||
|
||||
self._config = config
|
||||
self._default_save_path = logger.directory
|
||||
self._user_config_file = Path('user_configuration.json')
|
||||
self._user_creds_file = Path('user_credentials.json')
|
||||
|
||||
@property
|
||||
def user_configuration_file(self) -> Path:
|
||||
return self._user_config_file
|
||||
|
||||
@property
|
||||
def user_credentials_file(self) -> Path:
|
||||
return self._user_creds_file
|
||||
|
||||
def user_config_to_json(self) -> str:
|
||||
config = self._config.safe_config()
|
||||
|
||||
adapter = TypeAdapter(dict[ArchConfigType, Any])
|
||||
python_dict = adapter.dump_python(config)
|
||||
return json.dumps(python_dict, indent=4, sort_keys=True)
|
||||
|
||||
def user_credentials_to_json(self) -> str:
|
||||
cfg = self._config.unsafe_config()
|
||||
|
||||
adapter = TypeAdapter(dict[ArchConfigType, Any])
|
||||
python_dict = adapter.dump_python(cfg)
|
||||
return json.dumps(python_dict, indent=4, sort_keys=True)
|
||||
|
||||
def write_debug(self) -> None:
|
||||
debug(' -- Chosen configuration --')
|
||||
debug(self.user_config_to_json())
|
||||
|
||||
def as_summary(self) -> str:
|
||||
"""
|
||||
Render a concise two-column summary of the current configuration.
|
||||
|
||||
Returns an empty string if nothing meaningful to show.
|
||||
"""
|
||||
cfg: dict[str, str | list[str] | bool] = {}
|
||||
|
||||
for key, value in self._config.plain_cfg().items():
|
||||
cfg[key.text()] = value
|
||||
|
||||
for config_type, obj in self._config.sub_cfg().items():
|
||||
if not hasattr(obj, 'summary'):
|
||||
continue
|
||||
|
||||
summary = obj.summary()
|
||||
if summary:
|
||||
cfg[config_type.text()] = summary
|
||||
|
||||
simple_summary = as_key_value_pair(cfg, ignore_empty=True)
|
||||
|
||||
return simple_summary
|
||||
|
||||
async def confirm_config(self, show_install_warnings: bool = False) -> bool:
|
||||
header = f'{tr("The specified configuration will be applied")}. '
|
||||
header += tr('Would you like to continue?') + '\n'
|
||||
|
||||
if show_install_warnings:
|
||||
header += self._render_install_warnings()
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_preview_for_all(lambda x: self.user_config_to_json())
|
||||
|
||||
result = await Confirmation(
|
||||
group=group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preset=True,
|
||||
preview_location='bottom',
|
||||
preview_header=tr('Configuration preview'),
|
||||
).show()
|
||||
|
||||
if not result.get_value():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_install_warnings(self) -> list[str]:
|
||||
warnings: list[str] = []
|
||||
|
||||
if not isinstance(self._config.network_config, NetworkConfiguration):
|
||||
warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.'))
|
||||
|
||||
return warnings
|
||||
|
||||
def _render_install_warnings(self) -> str:
|
||||
warnings = self.get_install_warnings()
|
||||
|
||||
if not warnings:
|
||||
return ''
|
||||
|
||||
return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n'
|
||||
|
||||
def _is_valid_path(self, dest_path: Path) -> bool:
|
||||
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
||||
if not dest_path_ok:
|
||||
warn(
|
||||
f'Destination directory {dest_path.resolve()} does not exist or is not a directory\n.',
|
||||
'Configuration files can not be saved',
|
||||
)
|
||||
return dest_path_ok
|
||||
|
||||
def save_user_config(self, dest_path: Path) -> None:
|
||||
if self._is_valid_path(dest_path):
|
||||
target = dest_path / self._user_config_file
|
||||
target.write_text(self.user_config_to_json())
|
||||
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
|
||||
def save_user_creds(
|
||||
self,
|
||||
dest_path: Path,
|
||||
password: str | None = None,
|
||||
) -> None:
|
||||
data = self.user_credentials_to_json()
|
||||
|
||||
if password:
|
||||
data = encrypt(password, data)
|
||||
|
||||
if self._is_valid_path(dest_path):
|
||||
target = dest_path / self._user_creds_file
|
||||
target.write_text(data)
|
||||
target.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
|
||||
def save(
|
||||
self,
|
||||
dest_path: Path | None = None,
|
||||
creds: bool = False,
|
||||
password: str | None = None,
|
||||
) -> None:
|
||||
save_path = dest_path or self._default_save_path
|
||||
|
||||
if self._is_valid_path(save_path):
|
||||
self.save_user_config(save_path)
|
||||
if creds:
|
||||
self.save_user_creds(save_path, password=password)
|
||||
|
||||
|
||||
async def save_config(config: ArchConfig) -> None:
|
||||
def preview(item: MenuItem) -> str | None:
|
||||
match item.value:
|
||||
case 'user_config':
|
||||
serialized = config_output.user_config_to_json()
|
||||
return f'{config_output.user_configuration_file}\n{serialized}'
|
||||
case 'user_creds':
|
||||
if maybe_serial := config_output.user_credentials_to_json():
|
||||
return f'{config_output.user_credentials_file}\n{maybe_serial}'
|
||||
return tr('No configuration')
|
||||
case 'all':
|
||||
output = [str(config_output.user_configuration_file)]
|
||||
config_output.user_credentials_to_json()
|
||||
output.append(str(config_output.user_credentials_file))
|
||||
return '\n'.join(output)
|
||||
return None
|
||||
|
||||
config_output = ConfigurationOutput(config)
|
||||
|
||||
items = [
|
||||
MenuItem(
|
||||
tr('Save user configuration (including disk layout)'),
|
||||
value='user_config',
|
||||
preview_action=preview,
|
||||
),
|
||||
MenuItem(
|
||||
tr('Save user credentials'),
|
||||
value='user_creds',
|
||||
preview_action=preview,
|
||||
),
|
||||
MenuItem(
|
||||
tr('Save all'),
|
||||
value='all',
|
||||
preview_action=preview,
|
||||
),
|
||||
]
|
||||
|
||||
group = MenuItemGroup(items)
|
||||
result = await Selection[str](
|
||||
group,
|
||||
allow_skip=True,
|
||||
preview_location='right',
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Skip:
|
||||
return
|
||||
case ResultType.Selection:
|
||||
save_option = result.get_value()
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
|
||||
readline.set_completer_delims('\t\n=')
|
||||
readline.parse_and_bind('tab: complete')
|
||||
|
||||
dest_path = await prompt_dir(
|
||||
tr('Enter a directory for the configuration(s) to be saved') + '\n',
|
||||
allow_skip=True,
|
||||
)
|
||||
|
||||
if not dest_path:
|
||||
return
|
||||
|
||||
header = tr('Do you want to save the configuration file(s) to {}?').format(dest_path)
|
||||
|
||||
save_result = await Confirmation(
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preset=True,
|
||||
).show()
|
||||
|
||||
match save_result.type_:
|
||||
case ResultType.Selection:
|
||||
if not save_result.get_value():
|
||||
return
|
||||
case _:
|
||||
return
|
||||
|
||||
debug(f'Saving configuration files to {dest_path.absolute()}')
|
||||
|
||||
header = tr('Do you want to encrypt the user_credentials.json file?')
|
||||
|
||||
enc_result = await Confirmation(
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preset=False,
|
||||
).show()
|
||||
|
||||
enc_password: str | None = None
|
||||
if enc_result.type_ == ResultType.Selection:
|
||||
if enc_result.get_value():
|
||||
password = await get_password(
|
||||
header=tr('Credentials file encryption password'),
|
||||
allow_skip=True,
|
||||
)
|
||||
|
||||
if password:
|
||||
enc_password = password.plaintext
|
||||
|
||||
match save_option:
|
||||
case 'user_config':
|
||||
config_output.save_user_config(dest_path)
|
||||
case 'user_creds':
|
||||
config_output.save_user_creds(dest_path, password=enc_password)
|
||||
case 'all':
|
||||
config_output.save(dest_path, creds=True, password=enc_password)
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import base64
|
||||
import ctypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from cryptography.hazmat.primitives.kdf.argon2 import Argon2id
|
||||
|
||||
from archinstall.lib.log import debug
|
||||
|
||||
libcrypt = ctypes.CDLL('libcrypt.so')
|
||||
|
||||
libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||
libcrypt.crypt.restype = ctypes.c_char_p
|
||||
|
||||
libcrypt.crypt_gensalt.argtypes = [ctypes.c_char_p, ctypes.c_ulong, ctypes.c_char_p, ctypes.c_int]
|
||||
libcrypt.crypt_gensalt.restype = ctypes.c_char_p
|
||||
|
||||
LOGIN_DEFS = Path('/etc/login.defs')
|
||||
|
||||
|
||||
def _search_login_defs(key: str) -> str | None:
|
||||
defs = LOGIN_DEFS.read_text()
|
||||
for line in defs.split('\n'):
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
|
||||
if line.startswith(key):
|
||||
value = line.split(' ')[1]
|
||||
return value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def crypt_gen_salt(prefix: str | bytes, rounds: int) -> bytes:
|
||||
if isinstance(prefix, str):
|
||||
prefix = prefix.encode('utf-8')
|
||||
|
||||
setting = libcrypt.crypt_gensalt(prefix, rounds, None, 0)
|
||||
|
||||
if setting is None:
|
||||
raise ValueError(f'crypt_gensalt() returned NULL for prefix {prefix!r} and rounds {rounds}')
|
||||
|
||||
return setting
|
||||
|
||||
|
||||
def crypt_yescrypt(plaintext: str) -> str:
|
||||
"""
|
||||
By default chpasswd in Arch uses PAM to hash the password with crypt_yescrypt
|
||||
the PAM code https://github.com/linux-pam/linux-pam/blob/master/modules/pam_unix/support.c
|
||||
shows that the hashing rounds are determined from YESCRYPT_COST_FACTOR in /etc/login.defs
|
||||
If no value was specified (or commented out) a default of 5 is chosen
|
||||
"""
|
||||
value = _search_login_defs('YESCRYPT_COST_FACTOR')
|
||||
if value is not None:
|
||||
rounds = int(value)
|
||||
if rounds < 3:
|
||||
rounds = 3
|
||||
elif rounds > 11:
|
||||
rounds = 11
|
||||
else:
|
||||
rounds = 5
|
||||
|
||||
debug(f'Creating yescrypt hash with rounds {rounds}')
|
||||
|
||||
enc_plaintext = plaintext.encode('utf-8')
|
||||
salt = crypt_gen_salt('$y$', rounds)
|
||||
|
||||
crypt_hash = libcrypt.crypt(enc_plaintext, salt)
|
||||
|
||||
if crypt_hash is None:
|
||||
raise ValueError('crypt() returned NULL')
|
||||
|
||||
return crypt_hash.decode('utf-8')
|
||||
|
||||
|
||||
def _get_fernet(salt: bytes, password: str) -> Fernet:
|
||||
# https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#argon2id
|
||||
kdf = Argon2id(
|
||||
salt=salt,
|
||||
length=32,
|
||||
iterations=1,
|
||||
lanes=4,
|
||||
memory_cost=64 * 1024,
|
||||
ad=None,
|
||||
secret=None,
|
||||
)
|
||||
|
||||
key = base64.urlsafe_b64encode(
|
||||
kdf.derive(
|
||||
password.encode('utf-8'),
|
||||
),
|
||||
)
|
||||
|
||||
return Fernet(key)
|
||||
|
||||
|
||||
def encrypt(password: str, data: str) -> str:
|
||||
salt = os.urandom(16)
|
||||
f = _get_fernet(salt, password)
|
||||
token = f.encrypt(data.encode('utf-8'))
|
||||
|
||||
encoded_token = base64.urlsafe_b64encode(token).decode('utf-8')
|
||||
encoded_salt = base64.urlsafe_b64encode(salt).decode('utf-8')
|
||||
|
||||
return f'$argon2id${encoded_salt}${encoded_token}'
|
||||
|
||||
|
||||
def decrypt(data: str, password: str) -> str:
|
||||
_, algo, encoded_salt, encoded_token = data.split('$')
|
||||
salt = base64.urlsafe_b64decode(encoded_salt)
|
||||
token = base64.urlsafe_b64decode(encoded_token)
|
||||
|
||||
if algo != 'argon2id':
|
||||
raise ValueError(f'Unsupported algorithm {algo!r}')
|
||||
|
||||
f = _get_fernet(salt, password)
|
||||
try:
|
||||
decrypted = f.decrypt(token)
|
||||
except InvalidToken:
|
||||
raise ValueError('Invalid password')
|
||||
|
||||
return decrypted.decode('utf-8')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue